{"id":34568053,"url":"https://github.com/flowdev/spaghetti-analyzer","last_synced_at":"2025-12-24T09:12:23.554Z","repository":{"id":38784664,"uuid":"435253422","full_name":"flowdev/spaghetti-analyzer","owner":"flowdev","description":"Documentation side of the spaghetti cutter.","archived":false,"fork":false,"pushed_at":"2022-10-14T20:17:24.000Z","size":488,"stargazers_count":20,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-06-20T22:35:52.230Z","etag":null,"topics":["codeanalysis","go","golang","hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flowdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-05T19:00:39.000Z","updated_at":"2023-04-27T16:38:05.000Z","dependencies_parsed_at":"2022-07-10T13:30:17.188Z","dependency_job_id":null,"html_url":"https://github.com/flowdev/spaghetti-analyzer","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/flowdev/spaghetti-analyzer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowdev%2Fspaghetti-analyzer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowdev%2Fspaghetti-analyzer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowdev%2Fspaghetti-analyzer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowdev%2Fspaghetti-analyzer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flowdev","download_url":"https://codeload.github.com/flowdev/spaghetti-analyzer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowdev%2Fspaghetti-analyzer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27999522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codeanalysis","go","golang","hacktoberfest"],"created_at":"2025-12-24T09:12:22.891Z","updated_at":"2025-12-24T09:12:23.533Z","avatar_url":"https://github.com/flowdev.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![spaghetti analyzer](./spaghetti-analyzer.jpg \"spaghetti analyzer\")\n\n# spaghetti-analyzer - Analyze And Document Your Package Structure\n\n![CircleCI](https://img.shields.io/circleci/build/github/flowdev/spaghetti-analyzer/master)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/91d98c13ac5390ba6116/test_coverage)](https://codeclimate.com/github/flowdev/spaghetti-analyzer/test_coverage)\n[![Maintainability](https://api.codeclimate.com/v1/badges/91d98c13ac5390ba6116/maintainability)](https://codeclimate.com/github/flowdev/spaghetti-analyzer/maintainability)\n[![Go Report Card](https://goreportcard.com/badge/github.com/flowdev/spaghetti-analyzer)](https://goreportcard.com/report/github.com/flowdev/spaghetti-analyzer)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/flowdev/spaghetti-analyzer)\n![Twitter URL](https://img.shields.io/twitter/url?style=social\u0026url=https%3A%2F%2Fgithub.com%2Fflowdev%2Fspaghetti-analyzer)\n\n\n## Overview\n\n`spaghetti-analyzer` is a command line tool for devevelopers (and CI/CD pipelines)\nthat helps to analyze and document the Go package structure of a project.\n\nI gave a talk that includes the motivation for this tool and some usage examples:\n[![Microservices - The End of Software Design](https://img.youtube.com/vi/ev0dD12bxmg/0.jpg)](https://www.youtube.com/watch?v=ev0dD12bxmg \"Microservices - The End of Software Design\")\n\nAdditionally this tool documents the structure of a project in the\n[configuration](./.spaghetti-analyzer.hjson) and the\n[package dependencies](./package_dependencies.md).\n\n\u003c!-- spaghetti-analyzer: start \nTestMarkDown table for generated documentation:\n\n| | C o l u m n / h e a d e r / 1 | C o l u m n / h e a d e r / 2 | C o l u m n / h e a d e r / 3 | C o l u m n / h e a d e r / 4 | C o l u m n / h e a d e r / 5 | C o l u m n / h e a d e r / 6 | C o l u m n / h e a d e r / 7 | C o l u m n / h e a d e r / 8 | C o l u m n / h e a d e r / 9 | C o l u m n / h e a d e r / 1 0 | C o l u m n / h e a d e r / 1 1 | C o l u m n / h e a d e r / 1 2 | C o l u m n / h e a d e r / 1 3 | C o l u m n / h e a d e r / 1 4 | C o l u m n / h e a d e r / 1 5 | C o l u m n / h e a d e r / 1 6 | C o l u m n / h e a d e r / 1 7 | C o l u m n / h e a d e r / 1 8 | C o l u m n / h e a d e r / 1 9 | C o l u m n / h e a d e r / 2 0 | C o l u m n / h e a d e r / 2 1 | C o l u m n / h e a d e r / 2 2 | C o l u m n / h e a d e r / 2 3 | C o l u m n / h e a d e r / 2 4 | C o l u m n / h e a d e r / 2 5 | C o l u m n / h e a d e r / 2 6 | C o l u m n / h e a d e r / 2 7 | C o l u m n / h e a d e r / 2 8 | C o l u m n / h e a d e r / 2 9 | C o l u m n / h e a d e r / 3 0 |\n| :- | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |\n| `Row / header / 1` | X | X |  |  |  |  | X | X |  |  | X | X |  |  |  |  | X | X |  |  | X | X |  |  |  |  | X | X |  |  |\n| `Row / header / 1 / sub_package` |   |   |  | X | X |  |  |  |  | X | X |  |  | X | X |  |  |  |  | X | X |  |  | X | X |  |  |  |  | X |\n--\u003e\n\u003c!-- spaghetti-analyzer: end --\u003e\n\n\n## Usage\n\nYou can simply call it with `go run github.com/flowdev/spaghetti-analyzer`\nfrom anywhere inside your project.\n\nThe possible command line options are:\n```\nUsage of spaghetti-analyzer:\n  -r string\n        root directory of the project (shorthand) (default \".\")\n  -root string\n        root directory of the project (default \".\")\n```\n\nIf no `--root` option is given the root directory is found\nby crawling up the directory tree starting at the current working directory.\nThe first directory that contains the configuration file `.spaghetti-analyzer.hjson`\nwill be taken as project root.\n\nThe output looks like:\n```\n2020/09/10 09:37:08 INFO - configuration 'allowOnlyIn': `github.com/hjson/**`: `x/config` ; `golang.org/x/tools**`: `parse*`, `x/pkgs*`\n2020/09/10 09:37:08 INFO - configuration 'allowAdditionally': `*_test`: `parse`\n2020/09/10 09:37:08 INFO - configuration 'god': `main`\n2020/09/10 09:37:08 INFO - configuration 'tool': `x/*`\n2020/09/10 09:37:08 INFO - configuration 'db': ...\n2020/09/10 09:37:08 INFO - configuration 'size': 1024\n2020/09/10 09:37:08 INFO - configuration 'noGod': false\n2020/09/10 09:37:08 INFO - root package: github.com/flowdev/spaghetti-analyzer\n2020/09/10 09:37:08 INFO - Size of package 'x/config': 699\n2020/09/10 09:37:08 INFO - Size of package 'x/pkgs': 134\n2020/09/10 09:37:08 INFO - Size of package 'deps': 401\n2020/09/10 09:37:08 INFO - Size of package 'parse': 109\n2020/09/10 09:37:08 INFO - Size of package 'size': 838\n2020/09/10 09:37:08 INFO - Size of package 'x/dirs': 86\n2020/09/10 09:37:08 INFO - Size of package '/': 202\n2020/09/10 09:37:08 INFO - No errors found.\n```\n\nFirst the configuration values and the root package are reported.\nSo you can easily ensure that the correct configuration file is taken.\n\nAll package sizes are reported and last but not least any violations found.\nSince no error was found the return code is 0.\n\nA typical error message would be:\n```\n2020/09/10 10:31:14 ERROR - domain package 'pkg/shopping' isn't allowed to import package 'pkg/cart'\n```\n\nThe return code is 1.\nFrom the output you can see that\n- the package `pkg/shopping` is recognized as standard domain package,\n- it imports the `pkg/cart` package and\n- there is no `allowAdditionally` configuration to allow this.\n\nYou can fix that by adding a bit of configuration.\n\nOther non-zero return codes are possible for technical problems (unparsable code: 6, ...).\nIf used properly in the build pipeline a non-zero return code will stop the\nbuild and the problem has to be fixed first.\nSo undesired imports (spaghetti) are prevented.\n\n\n## Standard Use Case: Web API\n\nThis tool was especially created with Web APIs in mind as that is what about\n95% of all Gophers do according to my own totally unscientifical research.\n\nSo it offers special handling for the following cases:\n- Tools: Tool packages are allowed to be used everywhere else except in other\n  tool packages. But they aren't allowed to import any other internal packages.\n- Tool sub-packages: Sub-packages of tool packages aren't allowed to import any\n  other internal package like tool packages. Additionally they aren't allowed\n  to be used anywhere else in the project. So you should use explicit\n  configuration with explanations as comments (what the sub-packages contain\n  and why they exist at all).\n- Database: DB packages are allowed to be used in other DB packages and\n  standard (business) packages. Of course they can use tool packages but\n  nothing else.  Domain data structures can be either DB or tool packages.\n- Database sub-packages: Sub-packages of DB packages are allowed to only import\n  tool packages like DB packages. Additionally they aren't allowed to be used\n  anywhere else in the project. So you should use explicit configuration with\n  explanations as comments (what the sub-packages contain and why they exist at\n  all).\n- God: A god package can see and use everything. You should use this with great\n  care. `main` is the only default god package used if no explicit\n  configuration is given. You should only rarely add more.  You can switch\n  `main` to a standard package with the `noGod` configuration key. This makes\n  sense if you have got multiple `main` packages with different dependencies.\n\nThese cases needn't be used and can be overwritten with explicit configuration.\n\n\n## Configuration\n\nIt is mandatory to use a HJSON configuration file `.spaghetti-analyzer.hjson` in\nthe root directory of your project.\nThis serves multiple purposes:\n- It helps the `spaghetti-analyzer` to find the root directory of your project.\n- It saves you from retyping command line options again and again.\n- It is valuable documentation especially for developers new to the project.\n\nThe configuration can have the following elements:\n- `tool`, `db` and `god` for tool, database and god packages as discussed above.\n- `allowOnlyIn`: for restricting a package to be used only in some packages\n  (allow \"key\" package only in \"value\" packages).\n- `allowAdditionally`: for allowing additional dependencies (for \"key\" package\n  allow additionally \"value\" packages).\n- `size`: the maximum allowed size/complexity of a package. Default is `2048`.\n- `noGod`: `main` won't be god package.\n\nThe size configuration key prevents a clever developer from just thowing all of\nthe spaghetti code into a single package.\nWith the `spaghetti-analyzer` such things will become obvious and you can put\nthem as technical dept into your back log.\n\nThis is a simple example configuration file:\n```hjson\n{\n\t\"tool\": [\"x/*\"]\n}\n```\nAll packages directly under `x` are tool packages that can be used everywhere else in the project.\n\nA slightly different variant is:\n```hjson\n{\n\t\"tool\": [\"x/**\"]\n}\n```\nAll packages under `x` are tool packages that can be used everywhere else in the project.\nSo the `**` makes all sub-packages tool packages, too.\nIn most cases one level is enough.\n\nMultiple values are possible for a single key.\nSo this is another valid configuration file:\n```hjson\n{\n\t\"tool\": [\"x/*\", \"parse\"]\n}\n```\n\n`*`, `**` and multiple values are allowed for the `tool`, `db`, `god`,\n`allowOnlyIn` and `allowAdditionally` values.\n`*` and `**` are supported for `allowOnlyIn` and `allowAdditionally` keys, too.\n\nSo a full example looks like this:\n```hjson\n{\n\t\"allowOnlyIn\": {\n\t\t\"github.com/lib/pq\": [\"main\"]\n\t\t\"github.com/jmoiron/sqlx\": [\"pkg/model\", \"pkg/postgres\"]\n\t},\n\t\"allowAdditonally\": {\"pkg/shopping\": [\"pkg/catalogue\", \"pkg/cart\"]},\n\t\"tool\": [\"pkg/x/*\"],\n\t\"db\": [\"pkg/model\", \"pkg/postgres\"],\n\t\"god\": [\"cmd/**\"],\n\t\"size\": 1024\n}\n```\nThe `god` line shouldn't be necessary as all packages under `cmd/` should be `main` packages.\n\nThe case with multiple executables with different dependencies is interesting, too:\n```hjson\n{\n\t\"tool\": [\"pkg/x/*\"],\n\t\"db\": [\"pkg/model\", \"pkg/postgres\"],\n\t\"allowAdditionally\": {\n\t\t\"cmd/front-end\": [\"pkg/shopping\"],\n\t\t\"cmd/back-end\": [\"pkg/catalogue\"],\n\t\t\"pkg/shopping\": [\"pkg/catalogue\", \"pkg/cart\"]\n\t},\n\t\"noGod\": true,\n\t\"size\": 1024\n}\n```\nHere we have got a front-end application for the shopping experience and a\nback-end application for updating the catalogue.\n\n\n## Installation\n\nOf course you can just head over to the\n[latest release](https://github.com/flowdev/spaghetti-analyzer/releases/latest)\nand grab a pre-built binary for your OS.\nBut that is difficult to keep in sync when collaborating with others in a team.\n\nA much better approach for teams goes this way:\n\nFirst include the latest version in your `go.mod` file, e.g.:\n```\nrequire (\n\tgithub.com/flowdev/spaghetti-analyzer v0.9\n)\n```\n\nNow add a file like the following to your main package.\n\n```Go\n//+build tools\n\npackage main\n\nimport (\n    _ \"github.com/flowdev/spaghetti-analyzer\"\n)\n```\n\nOr add the import line to an existing file with similar build comment.\nThis ensures that the package is indeed fetched and built but not included in\nthe main or test executables. This is the\n[canonical workaround](https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module)\nto keep everything in sync and lean.\nHere is a [talk by Robert Radestock](https://www.youtube.com/watch?v=PhBhwgYFuw0)\nabout this topic.\n\nFinally you can run `go mod vendor` if that is what you like.\n\n\n## Open Decisions\n\nAccording to feedback from the community I am open to change things.\nCurrently there are no features in discussion.\n\n\n## Best Practices\n\nFor web APIs it is useful to split into independent business packages at router level.\nThe router itself should be in a central (god) package. The split can be done in two ways:\n1. The central router calls handlers that reside in the business packages.\n1. The central router composes itself from subrouters in business packages.\n\nThe second option minimizes the API surface of the business package and helps\nto ensure that all routes handled by a business package share a common URL path root.\nBut it also adds the concept of subrouters that isn't used so widely and\nincreasing cognitive load this way.\nPlus it makes it harder to find the implementation for a route.\n\nSo I recommend to start with the first option and switch to the second when the\ncentral router becomes too big to handle.\n\n\n### Criteria For When To Split A Service\n\nA common reason to split a service is when different parts of the service have\nto scale very differently.\nA shop front-end that has to serve many thousand customers needs to scale much\nmore than the shop back-end that only has to serve a few employees.\nOf course this isn't useful as long as you have got only a single instance of\nthe shop front-end running.\nPlease remember that Go is often used to consolidate many servers written in\nsome script language. Often replacing ten script servers with a single Go\ninstance saving a lot of hosting costs and operational work.\nOften a second main package for the front-end is the easiest way to go.\n\nAnother good reason to split a service is when the data the different parts of\nthe service work on is very or even completely different.\nPlease remember that overlaping data will lead to redundancies and you have to\nensure consistency on your own.\nAfter such a split the overall system is usually only eventual consistent.\n\nThe last and weakest indicator is that the service is growing unbounded like cancer.\nIt is completely normal that a service is growing.\nWhen the tests run for too long it is better to find a tool for handling\nmonorepos that helps you to run only the necessary tests.\nUnfortunately I can't point you to one. But I know that this is a problem that\nhas been solved multiple times.\nSuch additional tools can go a long way before it makes sense to split a service.\n\n\n### Recommendation How To Split A Service If Really Useful\n\nI recommend to split a service if it is sure to be really useful by first\nlooking at the package structure in `.spaghetti-analyzer.hjson`.\nYou would be careful to separate packages into own services if one domain package depends\non another domain package per `allowAdditionally` directive.\n`tool` packages are a bit simpler since they tend to change less often.\nThey should just serve a single purpose well.\nSo they can be easily extracted into an external library or they can be copied\nif the reusage isn't driven by business needs but more accidental.\nIf some `tool` packages won't be used by all the services after the split you\nshould take advantage of that.\n\nNext it is important to look at the DB usage. Packages that share a lot of data\nor methods to access data should not be split into separate services.\nNow it is time to find the weakest link between future services.\nYou should consider all three types of links:\n- `tool` packages (least important),\n- database usage (quite important) and\n- `allow` directives between domain packages (most important).\n\nWhen the weakest links are found it is great if you can even minimize these links.\nOver time they tend to accumulate and some aren't really necessary anymore.\nIt is often great to get a perspective from the business side about this.\n\nNow it is time to replace the remaining internal calls between packages that\nwill become separate services with external calls.\nRESTful HTTP requests and gRPC are used most often for this.\nMessaging between services can give you more scalability and decoupling but is\nharder to debug and some additional technology to master.\nOften you already have got a company wide standard for communication between services.\n\nCreating multiple main packages for the different services should be rather simple.\nEach should just be a subset of the old main package.\nIf you have got more god packages than just `main` you should split them now of course.\nNow you already have multiple separately deployable services.\n\nFinally you can do the split.\n\nYou can minimize the necessary work a lot by always watching dependencies grow\nand minimizing links as soon as possible.\nThe `spaghetti-analyzer` can be your companion on the way.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowdev%2Fspaghetti-analyzer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflowdev%2Fspaghetti-analyzer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowdev%2Fspaghetti-analyzer/lists"}