{"id":29701343,"url":"https://github.com/andrewwebber/cqrs","last_synced_at":"2025-07-23T11:10:07.820Z","repository":{"id":20736275,"uuid":"24020549","full_name":"andrewwebber/cqrs","owner":"andrewwebber","description":"cqrs framework in go","archived":false,"fork":false,"pushed_at":"2019-01-20T00:43:04.000Z","size":141,"stargazers_count":203,"open_issues_count":1,"forks_count":24,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-06-20T10:08:58.320Z","etag":null,"topics":["cqrs","cqrs-framework","event-handlers","event-sourcing","eventstore","go","golang","microservices"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrewwebber.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":"2014-09-14T11:43:11.000Z","updated_at":"2024-04-01T19:37:47.000Z","dependencies_parsed_at":"2022-09-11T07:01:37.007Z","dependency_job_id":null,"html_url":"https://github.com/andrewwebber/cqrs","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/andrewwebber/cqrs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewwebber%2Fcqrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewwebber%2Fcqrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewwebber%2Fcqrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewwebber%2Fcqrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrewwebber","download_url":"https://codeload.github.com/andrewwebber/cqrs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewwebber%2Fcqrs/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266665813,"owners_count":23964974,"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-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"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":["cqrs","cqrs-framework","event-handlers","event-sourcing","eventstore","go","golang","microservices"],"created_at":"2025-07-23T11:10:07.043Z","updated_at":"2025-07-23T11:10:07.807Z","avatar_url":"https://github.com/andrewwebber.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"CQRS framework in go\n=======\n\n[![Join the chat at https://gitter.im/andrewwebber/cqrs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/andrewwebber/cqrs?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![GoDoc](https://godoc.org/github.com/andrewwebber/cqrs?status.svg)](https://godoc.org/github.com/andrewwebber/cqrs)\n[![Build Status](https://drone.io/github.com/andrewwebber/cqrs/status.png?foo=bar)](https://drone.io/github.com/andrewwebber/cqrs/latest)\n\n# Project Summary\nThe package provides a framework for quickly implementing a CQRS style application.\nThe framework attempts to provides helpful functions to facilitate:\n- Event Sourcing\n- Command issuing and processing\n- Event publishing\n- Read model generation from published events\n\n---\n\n## Example code\n[Example test scenario (inmemory)](https://github.com/andrewwebber/cqrs/blob/master/cqrs_test.go)\n\n[Example test scenario (couchbase, rabbitmq)](https://github.com/andrewwebber/cqrs/blob/master/example/example_test.go)\n\n[Example CQRS scaleout/concurrent test](https://github.com/andrewwebber/cqrs-scaleout)\n\n## Test Scenario\nThe example test scenario is of a simple bank account that seeks to track, using event sourcing, a\ncustomers balance and login password\n\nThe are two main areas of concern at the application level, the **Write model** and **Read model**.\nThe read model is aimed to facilitate fast reads (read model projections)\nThe write model is where the business logic get executed and asynchronously notifies the read models\n\n## Write model - Using Event Sourcing\n### Account\n```go\ntype Account struct {\n  cqrs.EventSourceBased\n\n  FirstName    string\n  LastName     string\n  EmailAddress string\n  PasswordHash []byte\n  Balance      float64\n}\n```\nTo compensate for golang's lack of inheritance, a combination of type embedding and a call convention\npattern are utilized.\n\n```go\nfunc NewAccount(firstName string, lastName string, emailAddress string, passwordHash []byte, initialBalance float64) *Account {\n  account := new(Account)\n  account.EventSourceBased = cqrs.NewEventSourceBased(account)\n\n  event := AccountCreatedEvent{firstName, lastName, emailAddress, passwordHash, initialBalance}\n  account.Update(event)\n  return account\n}\n```\nThe 'attached' Update function being called above will now provide the infrastructure for routing events to event handlers.\nA function prefixed with 'Handle' and named with the name of the event expected with be called by the infrastructure.\n```go\nfunc (account *Account) HandleAccountCreatedEvent(event AccountCreatedEvent) {\n  account.EmailAddress = event.EmailAddress\n  account.FirstName = event.FirstName\n  account.LastName = event.LastName\n  account.PasswordHash = event.PasswordHash\n}\n```\nThe above code results in an account object being created with one single **pending** event namely **AccountCreatedEvent**.\nEvents will then be persisted once saved to an event sourcing repository.\nIf a repository is created with an event publisher then events saved for the purposes of event sourcing will also be published\n```go\npersistance := cqrs.NewInMemoryEventStreamRepository()\nbus := cqrs.NewInMemoryEventBus()\nrepository := cqrs.NewRepositoryWithPublisher(persistance, bus)\n...\nrepository.Save(account)\n```\n\n### Account Events\n```go\ntype AccountCreatedEvent struct {\n  FirstName      string\n  LastName       string\n  EmailAddress   string\n  PasswordHash   []byte\n  InitialBalance float64\n}\n\ntype EmailAddressChangedEvent struct {\n  PreviousEmailAddress string\n  NewEmailAddress      string\n}\n\ntype PasswordChangedEvent struct {\n  NewPasswordHash []byte\n}\n\ntype AccountCreditedEvent struct {\n  Amount float64\n}\n\ntype AccountDebitedEvent struct {\n  Amount float64\n}\n```\nEvents souring events are raised using the embedded **Update** function. These events will eventually be published to the read models indirectly via an event bus\n\n```go\nfunc (account *Account) ChangePassword(newPassword string) error {\n  if len(newPassword) \u003c 1 {\n    return errors.New(\"Invalid newPassword length\")\n  }\n\n  hashedPassword, err := GetHashForPassword(newPassword)\n  if err != nil {\n    panic(err)\n  }\n\n  account.Update(PasswordChangedEvent{hashedPassword})\n\n  return nil\n}\n\nfunc (account *Account) HandlePasswordChangedEvent(event PasswordChangedEvent) {\n  account.PasswordHash = event.NewPasswordHash\n}\n```\n\nAgain the calling convention routes our **PasswordChangedEvent** to the corresponding **HandlePasswordChangedEvent** instance function\n\n## Read Model\n### Accounts projection\n```go\ntype ReadModelAccounts struct {\n  Accounts map[string]*AccountReadModel\n}\n\ntype AccountReadModel struct {\n  ID           string\n  FirstName    string\n  LastName     string\n  EmailAddress string\n  Balance      float64\n}\n```\n### Users projection\n```go\ntype UsersModel struct {\n  Users    map[string]*User\n}\n\ntype User struct {\n  ID           string\n  FirstName    string\n  LastName     string\n  EmailAddress string\n  PasswordHash []byte\n}\n```\n\n## Infrastructure\nThere are a number of key elements to the CQRS infrastructure.\n- Event sourcing repository (a repository for event sourcing based business objects)\n- Event publisher (publishes new events to an event bus)\n- Event handler (dispatches received events to call handlers)\n- Command publisher (publishes new commands to a command bus)\n- Command handler (dispatches received commands to call handlers)\n\n### Event sourcing and integration events\nNested packages within this repository show example implementations using Couchbase Server and RabbitMQ.\nThe core library includes in-memory implementations for testing and quick prototyping\n```go\npersistance := cqrs.NewInMemoryEventStreamRepository()\nbus := cqrs.NewInMemoryEventBus()\nrepository := cqrs.NewRepositoryWithPublisher(persistance, bus)\n```\n\nWith the infrastructure implementations instantiated a stock event dispatcher is provided to route received\nevents to call handlers\n```go\nreadModel := NewReadModelAccounts()\nusersModel := NewUsersModel()\n\neventDispatcher := cqrs.NewVersionedEventDispatchManager(bus)\neventDispatcher.RegisterEventHandler(AccountCreatedEvent{}, func(event cqrs.VersionedEvent) error {\n  readModel.UpdateViewModel([]cqrs.VersionedEvent{event})\n  usersModel.UpdateViewModel([]cqrs.VersionedEvent{event})\n  return nil\n})\n```\n\nWe can also register a **global** handler to be called for all events.\nThis becomes useful when logging system wide events and when our read models are smart enough to filter out irrelevant events\n```go\nintegrationEventsLog := cqrs.NewInMemoryEventStreamRepository()\neventDispatcher.RegisterGlobalHandler(func(event cqrs.VersionedEvent) error {\n  integrationEventsLog.SaveIntegrationEvent(event)\n  readModel.UpdateViewModel([]cqrs.VersionedEvent{event})\n  usersModel.UpdateViewModel([]cqrs.VersionedEvent{event})\n  return nil\n})\n```\n\nWithin your read models the idea is that you implement the updating of your pre-pared read model based upon the\nincoming event notifications\n\n### Commands\n\nCommands are processed by command handlers similar to event handlers.\nWe can make direct changes to our write model and indirect changes to our read models by correctly processing commands and then raising integration events upon command completion.\n\n```go\ncommandBus := cqrs.NewInMemoryCommandBus()\ncommandDispatcher := cqrs.NewCommandDispatchManager(commandBus)\nRegisterCommandHandlers(commandDispatcher, repository)\n```\n\nCommands can be issued using a command bus. Typically a command is a simple struct.\nThe application layer command struct is then wrapped within a cqrs.Command using the cqrs.CreateCommand helper function\n\n```go\nchangePasswordCommand := cqrs.CreateCommand(\n  ChangePasswordCommand{accountID, \"$ThisIsANOTHERPassword\"})\ncommandBus.PublishCommands([]cqrs.Command{changePasswordCommand})\n```\n\nThe corresponding command handler for the **ChangePassword** command plays the role of a DDD aggregate root; responsible for the consistency and lifetime of aggregates and entities within the system)\n```go\ncommandDispatcher.RegisterCommandHandler(ChangePasswordCommand{}, func(command cqrs.Command) error {\n  changePasswordCommand := command.Body.(ChangePasswordCommand)\n  // Load account from storage\n  account, err := NewAccountFromHistory(changePasswordCommand.AccountID, repository)\n  if err != nil {\n    return err\n  }\n\n  account.ChangePassword(changePasswordCommand.NewPassword)\n\n  // Persist new events\n  repository.Save(account)  \n  return nil\n})\n```\n\nAs the read models become consistant, within the tests, we check at the end of the test if everything is in sync\n```go\nif account.EmailAddress != lastEmailAddress {\n  t.Fatal(\"Expected emailaddress to be \", lastEmailAddress)\n}\n\nif account.Balance != readModel.Accounts[accountID].Balance {\n  t.Fatal(\"Expected readmodel to be synced with write model\")\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewwebber%2Fcqrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrewwebber%2Fcqrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewwebber%2Fcqrs/lists"}