{"id":16534534,"url":"https://github.com/iamolegga/rebus","last_synced_at":"2025-06-30T11:33:57.961Z","repository":{"id":57641000,"uuid":"432424992","full_name":"iamolegga/rebus","owner":"iamolegga","description":"Type-safe bus generator for go","archived":false,"fork":false,"pushed_at":"2024-04-01T12:16:35.000Z","size":15,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-03T14:11:32.268Z","etag":null,"topics":["bus","clean-architecture","codegen","codegenerator","cqs","go","golang","hexagonal-architecture","layered-architecture","patterns"],"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/iamolegga.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-27T10:00:47.000Z","updated_at":"2021-12-04T22:04:16.000Z","dependencies_parsed_at":"2025-01-14T00:38:39.321Z","dependency_job_id":null,"html_url":"https://github.com/iamolegga/rebus","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/iamolegga/rebus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Frebus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Frebus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Frebus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Frebus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamolegga","download_url":"https://codeload.github.com/iamolegga/rebus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Frebus/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262765993,"owners_count":23361011,"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","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":["bus","clean-architecture","codegen","codegenerator","cqs","go","golang","hexagonal-architecture","layered-architecture","patterns"],"created_at":"2024-10-11T18:24:30.233Z","updated_at":"2025-06-30T11:33:57.933Z","avatar_url":"https://github.com/iamolegga.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rebus\nType-safe bus generator for go (which you dream of).\n\n---\n\nThere is a variety of `interface {}`-based buses in go.\nThe problem is that these libraries ships some universal interface that receives and returns structs of unknown type and users of such libraries should add extra type assertions for both inputs and outputs.\n\n`Rebus` is here to change the paradigm: instead of using such a universal bus, you can generate your own (even multiple buses, for example when you prefer CQS you can generate separate query-bus and command-bus) with proper input's/output's types and command/query handlers interfaces.\nEverything you need is just to define input/output `struct`s, and all the boilerplate code will be generated.\n\nSounds cool, right? Let's see it in action!\n\n## Install\n\nAs `rebus` is a developer tool, and it's not runtime dependency it can be installed like so:   \n\n```shell\nprintf '// +build tools\\n\\npackage tools\\nimport _ \"github.com/iamolegga/rebus\"' | gofmt \u003e tools.go\ngo mod tidy\n```\n\nThis technique keeps it as a dependency even if it's not imported anywhere in your code.\n\n## Example\n\nHere the several core parts are presented, but the full example can be found in [separate repo](https://github.com/iamolegga/rebusexample).\n\nLet's assume there is a new project, and it's started with some controller:\n\n```go\npackage router\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\nfunc New() http.Handler {\n\tr := mux.NewRouter()\n\n\tr.HandleFunc(\"/todos\", func (w http.ResponseWriter, r *http.Request) {\n\t\tpanic(\"implement me\")\n\t}).Methods(http.MethodGet)\n\n\tr.HandleFunc(\"/todos/{id}\", func (w http.ResponseWriter, r *http.Request) {\n\t\tpanic(\"implement me\")\n\t}).Methods(http.MethodPut)\n\n\tr.HandleFunc(\"/todos/{id}\", func (w http.ResponseWriter, r *http.Request) {\n\t\tpanic(\"implement me\")\n\t}).Methods(http.MethodDelete)\n\t\n\treturn r\n}\n```\nHere it's already known which routes are required for the app.\nIn each route handler, incoming HTTP request could be parsed, validated, and sent as a command via bus to the application layer.\nSo the commands' and queries' structs can be created:\n\n```go\npackage app\n\nimport \"github.com/my-org/my-proj/internal/domain\"\n\n//GetAllTodosQuery .\n// +rebus:out=../bus\ntype GetAllTodosQuery struct {}\n\ntype GetAllTodosQueryResult struct {\n    Todos []domain.Todo\t\n}\n\n//UpdateTodoCommand .\n// +rebus:out=../bus\ntype UpdateTodoCommand struct {\n    domain.Todo\n}\n\ntype UpdateTodoCommandResult struct {\n    *domain.Todo\n}\n\n//DeleteTodoCommand .\n// +rebus:out=../bus\ntype DeleteTodoCommand struct {\n    ID string\n}\n```\n\nIn the code above there are _marker comments_ that are used to mark any struct as a bus command/query.\nOnly commands/queries should have marker comments, result structs should have the same name as a command/query with `Result` suffix, and will be marked automatically. Also, a result struct is optional, a command can be without result.\n\nThe signature of markers is next: `+rebus:out=./relative-path-from/current-file's-dir/to/dir-with-generated-code`\n\n_Marker comments are used for code generation in such projects as [kubernetes operators](https://sdk.operatorframework.io/docs/building-operators/golang/references/markers/) and [swaggo/swag](https://github.com/swaggo/swag#declarative-comments-format), here k8s style of marker comments is used._\n\nNow bus can be generated with the command:\n\n```shell\ngo run github.com/iamolegga/rebus .\n```\n\nAnd finally, generated code can be used for controller:\n\n```go\npackage router\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/my-org/my-proj/internal/app\"\n\t\"github.com/my-org/my-proj/internal/bus\"\n)\n\n//New should be changed a bit:\n//add a Bus interface from a generated package\n//as an argument for the controller constructor,\n//so it can be used now\nfunc New(b bus.Bus) http.Handler {\n\t// ...\n\n\tr.HandleFunc(\"/todos\", func(w http.ResponseWriter, r *http.Request) {\n\t\tresult, err := b.ExecGetAllTodosQuery(app.GetAllTodosQuery{})\n\t\tif err != nil {\n\t\t\t// ...\t\n\t\t}\n\t\tlog.Println(result.Todos)\n\t\t// ...\t\n\t}).Methods(http.MethodGet)\n\n\t// ...\n\n\treturn r\n}\n```\n\n`Bus` interface contains `Exec\u003cMyCommandName\u003e` method for each command/query with proper result type. It always returns` error` as the last (or single) result.\nIf a more granular interface for each separate command is required `bus.\u003cMyCommandName\u003eExecutor` interface can be used instead of `bus.Bus`.\n\nOK, so the bus can be used to call commands. But how does it handle commands?\n\nFor each command there should be a separate handler, that implements generated `bus.\u003cMyCommandName\u003eHandler`:\n\n```go\npackage app\n\n//MyGetAllTodosQueryHandlerImpl (can be any name that is preferred) is a struct\n//that implements generated bus.GetAllTodosQueryHandler,\n//so empty... (continue in next comment)\ntype MyGetAllTodosQueryHandlerImpl struct{}\n\n//Handle method can be generated by your IDE\n//(for example in GoLand: right click on the struct -\u003e generate -\u003e implement methods -\u003e\n//start typing: \u003cMyCommand...\u003e -\u003e choose bus.\u003cMyCommandName\u003eHandler)\nfunc (h *MyGetAllTodosQueryHandlerImpl) Handle(query GetAllTodosQuery) (GetAllTodosQueryResult, error) {\n\t// Now the only thing left to do is to implement it.\n\t// Write business logic not boilerplate!\n}\n```\n\nThe last thing that is left is registering this handler with the `Register\u003cMyCommandName\u003eHandler` method:\n\n```go\npackage main\n\nfunc main() {\n\t// ...\n\tb := bus.New()\n\tb.RegisterGetAllTodosQueryHandler(\u0026app.MyGetAllTodosQueryHandlerImpl{})\n\thandler := router.New(b)\n\t// ...\n}\n```\n\nTo make separate buses just set different values for `+rebus:out=` comment.\nAlso, the optional `+rebus:pkg=\u003ccustom-name-for-generated-package\u003e` comment can be used to change the name of the generated package from a directory name.\n\n## API\n\nTo mark a struct as a command/query add a comment line above it:\n```go\n// +rebus:out=./path-to-directory\n```\n\nIf command/query has a result it should be placed in the same package and named the same as the command/query with the `Result`-Suffix.\n\nTo change the name of the generated package add a comment line above each command struct that should be handled by this package:\n```go\n// +rebus:pkg=mybuspkg\n```\n\nTo run code generation:\n```shell\ngo run github.com/iamolegga/rebus directory/that-will-be-checked-recuresively/for-rebus-tags\n```\n\nGenerated package will have next exported types:\n\n```go\npackage bus\n\n//All such executors will be combined to `Bus` interface,\n//so it can be called without name collisions\n//from the single interface\ntype \u003cMyCommand\u003eExecutor interface {\n\t//if \u003cMyCommand\u003eResult exists in the same package:\n\tExec\u003cMyCommand\u003e(\u003cMyCommand\u003e) (\u003cMyCommand\u003eResult, error)\n\t//if not:\n\tExec\u003cMyCommand\u003e(\u003cMyCommand\u003e) error\n}\n\n//All such handlers are useful to force splitting of command/query handlers\n//to separate structs (because each handler should have the same\n//`Handle`-method, but with different argument types). Also, it can be used\n//in IDE for quick code generation of handler implementation.\ntype \u003cMyCommand\u003eHandler interface {\n\t//if \u003cMyCommand\u003eResult exists in the same package:\n\tHandle(\u003cMyCommand\u003e) (\u003cMyCommand\u003eResult, error)\n\t//if not:\n\tHandle(\u003cMyCommand\u003e) error\n}\n\n//Bus is used for two things:\n//- as a command/query executor\n//- to register handlers' implementations\ntype Bus interface {\n\t\u003cMyCommand\u003eExecutor\n\tRegister\u003cMyCommand\u003eHandler(\u003cMyCommand\u003eHandler)\n}\n```\n\n## Todo\n\n- Add godoc\n- Add tests on code generation\n- Change generation command from `go run ...` to `go generate`. But that requires adding extra complexity because each file, in that case, will be handled separately and code generation will be one-by-on without the possibility to accumulate cache and do generation in the end. Instead, generation should make incremental updates of generated code by parsing existing generated code from the previous step.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamolegga%2Frebus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiamolegga%2Frebus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamolegga%2Frebus/lists"}