{"id":13564159,"url":"https://github.com/mishudark/eventhus","last_synced_at":"2026-01-29T04:09:16.891Z","repository":{"id":53181248,"uuid":"77766416","full_name":"mishudark/eventhus","owner":"mishudark","description":"Go - CQRS / Event Sourcing made easy - Go","archived":false,"fork":false,"pushed_at":"2021-11-23T11:27:54.000Z","size":111,"stargazers_count":465,"open_issues_count":8,"forks_count":92,"subscribers_count":18,"default_branch":"master","last_synced_at":"2024-11-04T17:47:05.385Z","etag":null,"topics":["cqrs","event-sourcing","event-store","framework","go","metallica","microservices","toolkit"],"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/mishudark.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":"2017-01-01T04:56:45.000Z","updated_at":"2024-09-06T19:52:38.000Z","dependencies_parsed_at":"2022-09-14T14:21:52.862Z","dependency_job_id":null,"html_url":"https://github.com/mishudark/eventhus","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mishudark%2Feventhus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mishudark%2Feventhus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mishudark%2Feventhus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mishudark%2Feventhus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mishudark","download_url":"https://codeload.github.com/mishudark/eventhus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247082804,"owners_count":20880722,"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":["cqrs","event-sourcing","event-store","framework","go","metallica","microservices","toolkit"],"created_at":"2024-08-01T13:01:27.344Z","updated_at":"2026-01-29T04:09:16.813Z","avatar_url":"https://github.com/mishudark.png","language":"Go","readme":"# Eventhus\n\nCQRS/ES toolkit for Go.\n\n**CQRS** stands for Command Query Responsibility Segregation. It's a pattern that I first heard described by Greg Young. At its heart is the notion that you can use a different model to update information than the model you use to read information.\n\nThe mainstream approach people use for interacting with an information system is to treat it as a CRUD datastore. By this I mean that we have mental model of some record structure where we can create new records, read records, update existing records, and delete records when we're done with them. In the simplest case, our interactions are all about storing and retrieving these records.\n\n**Event Sourcing** ensures that every change to the state of an application is captured in an event object, and that these event objects are themselves stored in the sequence they were applied for the same lifetime as the application state itself.\n\n# Examples\n\n[bank account](https://github.com/mishudark/eventhus/blob/master/examples/bank) shows a full example with `deposits` and `withdrawls`.\n\n# Usage\n\nThere are 3 basic units of work: `event`, `command` and `aggregate`.\n\n## Command\n\nA command describes an **action** that should be performed; it's always named in the imperative tense such as `PerformDeposit` or `CreateAccount`.\n\nLet’s start with some code:\n\n```go\nimport \"github.com/mishudark/eventhus\"\n\n// PerformDeposit to an account\ntype PerformDeposit struct {\n\teventhus.BaseCommand\n\tAmount int\n}\n```\n\nAt the beginning, we create the `PerformDeposit` command. It contains an anonymous struct field of type `eventhus.BaseCommand`. This means `PerformDeposit` automatically acquires all the methods of `eventhus.BaseCommand`.\n\nYou can also define custom fields, in this case `Amount` contains a quantity to be deposited into an account.\n\n## Event\n\nAn event is the notification that something happened in the past. You can view an event as the representation of the reaction to **a command after being executed**. All events should be represented as verbs in the past tense such as `CustomerRelocated`, `CargoShipped` or `InventoryLossageRecorded`.\n\nWe create the `DepositPerformed` event; it's a pure go struct, and it's the past equivalent to the previous command `PerformDeposit`:\n\n```go\n// DepositPerformed event\ntype DepositPerformed struct {\n\tAmount int\n}\n```\n\n## Aggregate\n\nThe aggregate is a logical boundary for things that can change in a business transaction of a given context. In the **Eventhus** context, it simplifies the process the commands and produce events.\n\nShow me the code!\n\n```go\nimport \"github.com/mishudark/eventhus\"\n\n//Account of bank\ntype Account struct {\n\teventhus.BaseAggregate\n\tOwner   string\n\tBalance int\n}\n```\n\nWe create the `Account` aggregate. It contains an anonymous struct field of type `eventhus.BaseAggregate`. This means `Account` automatically acquires all the methods of `eventhus.BaseAggregate`.\n\nAdditionally `Account` has the fields `Balance` and `Owner` that represent the basic info of this context.\n\nNow that we have our `aggregate`, we need to process the `PerformDeposit` command that we created earlier:\n\n```go\n// HandleCommand create events and validate based on such command\nfunc (a *Account) HandleCommand(command eventhus.Command) error {\n\tevent := eventhus.Event{\n\t\tAggregateID:   a.ID,\n\t\tAggregateType: \"Account\",\n\t}\n\n\tswitch c := command.(type) {\n\tcase CreateAccount:\n\t\tevent.AggregateID = c.AggregateID\n\t\tevent.Data = \u0026AccountCreated{c.Owner}\n\n\tcase PerformDeposit:\n\t\tevent.Data = \u0026DepositPerformed{\n\t\t\tc.Amount,\n\t\t}\n\t}\n\n\ta.BaseAggregate.ApplyChangeHelper(a, event, true)\n\treturn nil\n}\n```\n\nFirst, we create an `event` with the basic info `AggregateID` as an identifier and `AggregateType` with the same name as our `aggregate`. Next, we use a switch to determine the type of the `command` and produce an `event` as a result.\n\nFinally, the event should be applied to our aggregate; we use the helper `BaseAggregate.ApplyChangeHelper` with the params `aggregate`, `event` and the last argument set to `true`, meaning it should be stored and published via `event store` and `event publisher`.\n\nNote: `eventhus.BaseAggregate` has some helper methods to make our life easier, we use `HandleCommand` to process a `command` and produce the respective `event`.\n\nThe last step in the aggregate journey is to apply the `events` to our `aggregate`:\n\n```go\n// ApplyChange to account\nfunc (a *Account) ApplyChange(event eventhus.Event) {\n\tswitch e := event.Data.(type) {\n\tcase *AccountCreated:\n\t\ta.Owner = e.Owner\n\t\ta.ID = event.AggregateID\n\tcase *DepositPerformed:\n\t\ta.Balance += e.Amount\n\t}\n}\n```\n\nAlso, we use a switch-case format to determine the type of the `event` (note that events are pointers), and apply the respective changes.\n\nNote: The aggregate is never saved in its current state. Instead, it is stored as a series of `events` that can recreate the aggregate in its last state.\n\nSaving the events, publishing them, and recreating an aggregate from `event store` is made by **Eventhus** out of the box.\n\n# Config\n\n`Eventhus` needs to be configured to manage events and commands, and to know where to store and publish events.\n\n## Event Store\n\nCurrently, it has support for `MongoDB`. `Rethinkdb` is in the scope to be added.\n\nWe create an `event store` with `config.Mongo`; it accepts `host`, `port` and `table` as arguments:\n\n```go\nimport \"github.com/mishudark/eventhus/config\"\n...\n\nconfig.Mongo(\"localhost\", 27017, \"bank\") // event store\n```\n\n## Event Publisher\n\n`RabbitMQ` and `Nats.io` are supported.\n\nWe create an `eventbus` with `config.Nats`, it accepts `url data config` and `useSSL` as arguments:\n\n```go\nimport \t\"github.com/mishudark/eventhus/config\"\n...\n\nconfig.Nats(\"nats://ruser:T0pS3cr3t@localhost:4222\", false) // event bus\n```\n\n## Wire it all together\n\nNow that we have all the pieces, we can register our `events`, `commands` and `aggregates`:\n\n```go\nimport (\n\t\"github.com/mishudark/eventhus\"\n\t\"github.com/mishudark/eventhus/commandhandler/basic\"\n\t\"github.com/mishudark/eventhus/config\"\n\t\"github.com/mishudark/eventhus/examples/bank\"\n)\n\nfunc getConfig() (eventhus.CommandBus, error) {\n\t// register events\n\treg := eventhus.NewEventRegister()\n\treg.Set(bank.AccountCreated{})\n\treg.Set(bank.DepositPerformed{})\n\treg.Set(bank.WithdrawalPerformed{})\n\n    // wire all parts together\n\treturn config.NewClient(\n\t\tconfig.Mongo(\"localhost\", 27017, \"bank\"),                    // event store\n\t\tconfig.Nats(\"nats://ruser:T0pS3cr3t@localhost:4222\", false), // event bus\n\t\tconfig.AsyncCommandBus(30),                                  // command bus\n\t\tconfig.WireCommands(\n\t\t\t\u0026bank.Account{},          // aggregate\n\t\t\tbasic.NewCommandHandler,  // command handler\n\t\t\t\"bank\",                   // event store bucket\n\t\t\t\"account\",                // event store subset\n\t\t\tbank.CreateAccount{},     // command\n\t\t\tbank.PerformDeposit{},    // command\n\t\t\tbank.PerformWithdrawal{}, // command\n\t\t),\n\t)\n}\n```\n\nNow you are ready to process commands:\n\n```go\nuuid, _ := utils.UUID()\n\n// 1) Create an account\nvar account bank.CreateAccount\naccount.AggregateID = uuid\naccount.Owner = \"mishudark\"\n\ncommandBus.HandleCommand(account)\n```\n\nFirst, we generate a new `UUID`. This is because is a new account and we need a unique identifier. After we created the basic structure of our `CreateAccount` command, we only need to send it using the `commandbus` created in our config.\n\n## Event consumer\n\nYou should listen to your `eventbus`, the format of the event is always the same, only the `data` key changes in the function of your event struct.\n\n```json\n{\n  \"id\": \"0000XSNJG0SB2WDBTATBYEC51P\",\n  \"aggregate_id\": \"0000XSNJG0N0ZVS3YXM4D7ZZ9Z\",\n  \"aggregate_type\": \"Account\",\n  \"version\": 1,\n  \"type\": \"AccountCreated\",\n  \"data\": {\n    \"owner\": \"mishudark\"\n  }\n}\n```\n\n## Prior Art\n\n- [looplab/eventhorizon](https://github.com/looplab/eventhorizon)\n- [andrewwebber/cqrs](https://github.com/andrewwebber/cqrs)\n","funding_links":[],"categories":["Go","\u003ca name=\"Go\"\u003e\u003c/a\u003eGo"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmishudark%2Feventhus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmishudark%2Feventhus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmishudark%2Feventhus/lists"}