{"id":16420606,"url":"https://github.com/maxpoletaev/van","last_synced_at":"2026-01-05T17:04:04.484Z","repository":{"id":57649259,"uuid":"444455420","full_name":"maxpoletaev/van","owner":"maxpoletaev","description":"In-app command/event bus with dependency-injection","archived":false,"fork":false,"pushed_at":"2024-03-30T12:04:43.000Z","size":167,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-21T19:58:21.983Z","etag":null,"topics":["commandbus","eventbus","go","golang"],"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/maxpoletaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2022-01-04T14:45:15.000Z","updated_at":"2023-05-27T22:35:36.000Z","dependencies_parsed_at":"2024-06-20T11:02:53.056Z","dependency_job_id":"e57a0d80-950e-431a-9009-3bce453c2795","html_url":"https://github.com/maxpoletaev/van","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/maxpoletaev/van","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpoletaev%2Fvan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpoletaev%2Fvan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpoletaev%2Fvan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpoletaev%2Fvan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxpoletaev","download_url":"https://codeload.github.com/maxpoletaev/van/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpoletaev%2Fvan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28217716,"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":"2026-01-05T02:00:06.358Z","response_time":57,"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":["commandbus","eventbus","go","golang"],"created_at":"2024-10-11T07:28:33.004Z","updated_at":"2026-01-05T17:04:04.450Z","avatar_url":"https://github.com/maxpoletaev.png","language":"Go","readme":"# 🚐 Van\n\n(Yet another?) application-level reflection-based command/event bus with\nautomatic dependency-injection.\n\n## Status\n\nBETA. There still might be breaking API changes in future versions.\n\n## Idea\n\nVan introduces some variation of the CQS pattern, combined with automatic dependency\ninjection, making the application components low-coupled, highly testable and maintainable.\n\n## Commands\n\n * Command is a signal to the application to perform some action.\n * Commands are simple DTO objects without behaviour.\n * Commands are processed with command handlers.\n * Each command can be associated with one handler.\n * Commands are processed synchronously (request-response).\n * Commands are mutable allowing handlers to set the return values.\n\n```go\n// Define command struct\ntype PrintHelloCommand struct {\n\tName string\n}\n\nfunc (cmd *PrintHelloCommand) Handle(ctx *context.Context)\n\n// Register command handler\nfunc PrintHello(ctx *context.Context, cmd *PrintHelloCommand, logger Logger) error {\n    logger.Printf(\"Hello, %s!\", cmd.Name)\n    return nil\n}\n\nbus.Handle(PrintHelloCommand{}, PrintHello)\n\n// send command to the bus\nctx := context.Background()\n\ncmd := \u0026PrintHelloCommand{Name: \"Harry\"}\n\nif err := bus.Invoke(ctx, cmd); err != nil {\n\tlog.Printf(\"[ERROR] failed to invoke PrintHelloCommand: %v\", err)\n}\n```\n\n## Events\n\n * Event is a broadcast message informing that something has happened.\n * Events are simple DTO objects without behaviour.\n * Events are immutable and cannot be modified by listeners.\n * Each event may have zero to infinity number of listeners.\n\n```go\n// define event struct\ntype OrderCreatedEvent struct {\n\tOrderID\tint\n\tTs      int64\n}\n\n// register event listener\nbus.Subscribe(OrderCreatedEvent{}, func(ctx context.Context, event OrderCreatedEvent) {\n    log.Printf(\"[INFO] order %d created at %d\", event.OrderID, event.Ts)\n})\n\n// publish event to the bus\nevent := OrderCreatedEvent{\n\tOrderID: 134,\n\tTs:      time.Now().Unix(),\n}\n\nif err := bus.Publish(event); err != nil {\n\tlog.Printf(\"[ERROR] failed to publish OrderCreatedEvent: %v\", err)\n}\n```\n\n## Handlers\n\n * Handler is a function associated with a command or an event.\n * Handlers take at least two arguments: context and command/event struct.\n * Handlers may have dependencies provided in extra arguments as interfaces.\n * Command handler can return an error which will propagated to the caller as is.\n * Event handlers cannot return any values, nor can they propagate any state back\n   to the caller, including errors. Therefore, there is no indication of whether\n   the event has been processed successfully.\n * Command handlers are synchronous. Event handlers are executed in the background\n   (the order of execution is not specified).\n\n```go\nfunc PrintHelloWorld(ctx context.Context, cmd *PrintHelloWorldCommand, logger Logger, bus van.Van) error {\n\tlogger.Print(\"Hello, World!\")\n\tif err := bus.Publish(HelloWorldPrintedEvent{}); err != nil {\n\t\tlogger.Printf(\"failed to publish an event: %v\", err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc HelloWorldPrinted(ctx context.Context, event HelloWorldPrintedEvent, logger Logger) {\n\tlogger.Print(\"hello world has been printed\")\n}\n```\n\n## Providers\n\n * Provider is essentially a constructor of an arbitrary type.\n * Provider should return an interface and an error.\n * Providers can depend on other providers.\n * Providers can be either regular constructors (executed every time the dependency\n   is requested), or singletons.\n * Regular providers can depend on `context.Context`. Singleton providers cannot,\n   nor can they depend on providers that use context as a dependency.\n * There is no such thing as \"optional dependency\", provider must return an error\n   if it can’t provide one.\n\n```go\ntype Logger interface {\n\tPrintf(string, ...interface{})\n}\n\n// Singleton provider is guaranteed to be executed only once.\nfunc ProvideLogger() (Logger, error) {\n\tflags := log.LstdFlags | log.LUTC | log.Lshortfile\n\treturn log.New(os.Stdout, \"\", flags)\n}\n\n// Regular provider is executed every time the dependency is requested.\nfunc newUserRepoProvider(db *sql.DB) van.ProviderFunc {\n\treturn func(ctx context.Context, logger Logger) (UserRepo, error) {\n\t\tlogger.Printf(\"initializing new user repository\")\n\t\treturn newPostgresUserRepo(db.Conn(ctx), logger)\n\t}\n}\n\nfunc main() {\n   db := sql.Open(\"postgres\", \"...\")\n\n   bus.ProvideSingleton(ProvideLogger)\n\n   bus.Provide(newUserRepoProvider(db))\n}\n```\n\n## Dependency Injection\n\nEach command handler, event handler, or provider may have an arbitrary number of\ndependencies defined as the function arguments. The dependency must be of an interface\ntype and there should be a registered dependency provider for the given type.\n\n```go\nfunc SayHello(ctx context.Context, cmd *SayHelloCommand, logger Logger, bus van.Van) error {\n\tlogger.Print(\"Hello, World!\")\n\tbus.Publish(HelloWorldPrintedEvent{})\n\treturn nil\n}\n```\n\nIn case one has too many dependencies to be passed as function arguments, it is\npossible to pack them into a struct. Each field of that struct still needs to be\nof an interface type. You can combine any number of such structs in the function\narguments.\n\n```go\nfunc DependencySet struct {\n\tBus    *van.Van\n\tLogger Logger\n}\n\nfunc HelloWorldPrinted(ctx context.Context, event HelloWorldPrintedEvent, deps DependencySet) {\n\tdeps.Logger.Print(\"hello world has been printed\")\n}\n```\n\n## Is it fast?\n\nAlthough it tries to do most of the heavy lifting during the start-up, it’s still\nconsidered to be slow compared to \"native\" code due to reflection magic under\nthe hood used for dynamically-constructed function arguments.\n\nThe following benchmark shows that simple dynamic function calls in Go can be 10\nto 1000 times slower than static function calls, and this is even without the\ndependency-injection overhead involved.\n\n```\ngoos: darwin\ngoarch: amd64\npkg: github.com/maxpoletaev/van\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\nBenchmarkFuncCall_StaticStack-12           \t1000000000\t         0.2446 ns/op\t   0 B/op\t       0 allocs/op\nBenchmarkFuncCall_StaticHeap-12            \t54928545\t        20.68 ns/op\t      16 B/op\t       1 allocs/op\nBenchmarkFuncCall_Reflection-12            \t 4555992\t       248.2 ns/op\t      32 B/op\t       2 allocs/op\n```\n\nIf we compare a relatively large dependency graph constructed statically with\nthe one constructed dynamically using dependency injection, the difference will\nbe at about 40 to 50 times:\n\n```\nBenchmarkBus_LargeGraphTransitive-12    \t   82728\t     12986 ns/op\t    2816 B/op\t     128 allocs/op\nBenchmarkNoBus_LargeGraph-12               \t 3522093\t       336.5 ns/op\t     224 B/op\t      28 allocs/op\n```\n\nA general recommendation is not to forget using singletons whenever possible\nto reduce the number of dynamic reflection calls to the providers:\n\n```\nBenchmarkBus_LargeGraphTransitive-12    \t   82728\t     12986 ns/op\t    2816 B/op\t     128 allocs/op\nBenchmarkBus_LargeGraphSingletons-12    \t  756583\t      1590 ns/op\t     176 B/op\t      10 allocs/op\n```\n\nGiven the fact that we are still in the nanoseconds (10\u003csup\u003e−9\u003c/sup\u003e seconds)\nscale, is unlikely to introduce any visible delay in 95% of the cases. Most\nprobably, your application will spend way more time doing actual business logic,\ndatabase round trips and JSON serialization.\n\nSo, the impact of the bus on the response time of a typical go service is\nestimated at around 1% at worst.\n\n## Type Safety\n\nEven though there is a lot of reflection under the hood, Van tries to do most of\nthe type checking at the startup. That includes checking the types of the\ndependencies and the return values of the providers. It does not, however, \nprevent you from passing a wrong type to the `Invoke` method, meaning that you \ncan still get a type error in run time (not panicking, though).\n\n```go\n\n## Return Values\n\nThere are no return values, but this can be handled with the command type itself:\n\n```go\ntype SumCommand struct {\n\tA      int\n\tB      int\n\tResult int\n}\n\nfunc Sum(ctx context.Context, cmd *SumCommand) error {\n\tcmd.Result = cmd.A + cmd.B\n\treturn nil\n}\n\ncmd := \u0026SumCommand{\n\tA: 1,\n\tB: 2,\n}\n\nif err := bus.Invoke(context.TODO(), cmd); err != nil {\n\tpanic(err)\n}\n\nfmt.Println(cmd.Result) // 3\n```\n\n## Multiple Providers for the Same Type\n\nYou can achieve this by defining a new type for the same interface:\n\n```go\ntype Logger interface {\n    Printf(string)\n}\n\ntype ErrorLogger Logger\n\nbus.ProvideSingleton(func() (Logger, error) {\n\tflags := log.LstdFlags | log.LUTC\n\treturn log.New(os.Stdout, \"\", flags), nil\n})\n\nbus.ProvideSingleton(func() (ErrorLogger, error) {\n\tflags := log.LstdFlags | log.LUTC | log.Llongfile\n\treturn log.New(os.Stderr, \"[ERROR] \", flags), nil\n})\n```\n\n## Credits\n\nThe general idea and some code snippets are inspired by:\n\n * Go Dependency Injector by Uber - https://github.com/uber-go/dig\n * The CQS/CQRS Pattern - https://watermill.io/docs/cqrs/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpoletaev%2Fvan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxpoletaev%2Fvan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpoletaev%2Fvan/lists"}