A Golang-based command line tool for CI/CD pipelines (and dev machines) that helps to prevent Go spaghetti code (a.k.a. big ball of mud).
Thankfully, in the Go world, the compiler already prevents circular dependencies between packages. So this tool has to care only about additional undesired dependencies.
I gave a talk that included the motivation for this tool and some (old) usage examples:
Additionally, this tool documents the structure of a project in its configuration.
Usage of the Golang CLI Tool
You can simply call it with
go run github.com/flowdev/spaghetti-cutter from anywhere inside your project.
The possible command line options are:
Usage of spaghetti-cutter: -e don't report errors and don't exit with an error (shorthand) -noerror don't report errors and don't exit with an error -r string root directory of the project (shorthand) (default ".") -root string root directory of the project (default ".")
--root option is given the root directory is found by crawling up the directory tree starting at the current working directory. The first directory that contains the configuration file
.spaghetti-cutter.hjson will be taken as project root.
The output looks like:
2020/09/10 09:37:08 INFO - configuration 'allowOnlyIn': `github.com/hjson/**`: `x/config` ; `golang.org/x/tools**`: `parse*`, `x/pkgs*` 2020/09/10 09:37:08 INFO - configuration 'allowAdditionally': `*_test`: `parse` 2020/09/10 09:37:08 INFO - configuration 'god': `main` 2020/09/10 09:37:08 INFO - configuration 'tool': `x/*` 2020/09/10 09:37:08 INFO - configuration 'db': ... 2020/09/10 09:37:08 INFO - configuration 'size': 1024 2020/09/10 09:37:08 INFO - configuration 'noGod': false 2020/09/10 09:37:08 INFO - root package: github.com/flowdev/spaghetti-cutter 2020/09/10 09:37:08 INFO - Size of package 'x/config': 699 2020/09/10 09:37:08 INFO - Size of package 'x/pkgs': 134 2020/09/10 09:37:08 INFO - Size of package 'deps': 401 2020/09/10 09:37:08 INFO - Size of package 'parse': 109 2020/09/10 09:37:08 INFO - Size of package 'size': 838 2020/09/10 09:37:08 INFO - Size of package 'x/dirs': 86 2020/09/10 09:37:08 INFO - Size of package '/': 202 2020/09/10 09:37:08 INFO - No errors found.
First, the configuration values and the root package are reported. So you can easily ensure that the correct configuration file is taken.
All package sizes are reported and last but not least, any violations are found. Since no error was found, the return code is 0.
A typical error message would be:
2020/09/10 10:31:14 ERROR - domain package 'pkg/shopping' isn't allowed to import package 'pkg/cart'
The return code is first From the output, you can see that
- the package
pkg/shoppingis recognized as a standard domain package,
- it imports the
- there is no
allowAdditionallyconfiguration to allow this.
You can fix that by adding a bit of configuration.
Other non-zero return codes are possible for technical problems (unparsable code: 6, …). If appropriately used in the build pipeline, a non-zero return code will stop the build, and the problem has to be fixed first. So undesired imports (spaghetti relationships) are prevented.
Standard Use Case: Golang Web API
According to my own unscientific research, this tool was created with Web APIs in mind as that is what about 95% of all Gophers do.
So it offers special handling for the following cases:
- Tools: Tool packages can be used everywhere except in other tool packages. But they aren’t allowed to import any other internal packages.
- Database: DB packages can be used in other and standard (business) packages. Of course they can use tool packages but nothing else. Domain data structures can be either DB or tool packages.
- Database sub-packages: Sub-packages of DB packages are allowed to only import tool packages like DB packages. Additionally, they aren’t allowed to be used anywhere else in the project. So you should use explicit configuration with explanations as comments (what the sub-packages contain and why they exist at all).
- Tool sub-packages: Sub-packages of tool packages aren’t allowed to import any other internal package like tool packages. Additionally, they aren’t allowed to be used anywhere else in the project. So you should use explicit configuration with explanations as comments (what the sub-packages contain and why they exist).
- God: A god package can see and use everything. You should use this with great care. main is the only default god package used if no explicit configuration is given. You should only rarely add more. You can switch
mainto a standard package with the
noGodconfiguration key. This makes sense if you have got multiple
mainpackages with different dependencies.
These cases needn’t be used and can be overridden with explicit configuration.
It is mandatory to use a HJSON configuration file
.spaghetti-cutter.hjson in the root directory of your project. This serves multiple purposes:
- It helps the
spaghetti-cutterto find the root directory of your project.
- It saves you from retyping command line options again and again.
- It is valuable documentation, especially for developers new to the project.
The configuration can have the following elements:
godfor tool, database and god packages as discussed above.
allowOnlyIn: for restricting a package to be used only in some packages (allow “key” package only in “value” packages).
allowAdditionally: for allowing additional dependencies (for “key” package allow additional “value” packages).
- size: the maximum allowed size/complexity of a package. Default is 2048.
- noGod: main It won’t be god package.
The size configuration key prevents a clever developer from just throwing all of the spaghetti code into a single package. With the
spaghetti-cutter such things will become obvious and you can put them as technical dept into your back log.