{"id":18147683,"url":"https://github.com/joebew42/elixir_cqrs_es_example","last_synced_at":"2025-08-14T21:07:54.579Z","repository":{"id":143027185,"uuid":"114454320","full_name":"joebew42/elixir_cqrs_es_example","owner":"joebew42","description":null,"archived":false,"fork":false,"pushed_at":"2019-06-06T17:33:22.000Z","size":155,"stargazers_count":7,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-23T22:27:06.674Z","etag":null,"topics":["cqrs-es","elixir","example-app","tdd"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joebew42.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2017-12-16T11:09:17.000Z","updated_at":"2023-09-14T14:40:50.000Z","dependencies_parsed_at":"2023-06-26T00:04:42.476Z","dependency_job_id":null,"html_url":"https://github.com/joebew42/elixir_cqrs_es_example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/joebew42/elixir_cqrs_es_example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joebew42%2Felixir_cqrs_es_example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joebew42%2Felixir_cqrs_es_example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joebew42%2Felixir_cqrs_es_example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joebew42%2Felixir_cqrs_es_example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joebew42","download_url":"https://codeload.github.com/joebew42/elixir_cqrs_es_example/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joebew42%2Felixir_cqrs_es_example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270482333,"owners_count":24591340,"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-08-14T02:00:10.309Z","response_time":75,"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":["cqrs-es","elixir","example-app","tdd"],"created_at":"2024-11-01T22:07:21.552Z","updated_at":"2025-08-14T21:07:54.572Z","avatar_url":"https://github.com/joebew42.png","language":"Elixir","readme":"# ElixirCqrsEsExample\n\nThis is an Elixir port of the [cqrs with erlang](https://github.com/bryanhunter/cqrs-with-erlang).\n\nI tried to understand how this system can be tested, so if you are interested about the testing take a look a the tests.\n\n## Installation\n\n```\nmix deps.get\n```\n\n## Run all tests\n\n```\nmix test --include acceptance\n```\n\n## Run only acceptance tests\n\n```\nmix test --only acceptance\n```\n\n## Run the application\n\n```\niex -S mix\n```\n\nA server will listen at port `4001`\n\nCreate an account via HTTP:\n\n```\ncurl -v -X POST --data '{\"name\": \"Foo\"}' -H \"Content-Type: application/json\" http://localhost:4001/accounts\n```\n\nYou can also use the [`Bank.Client`](lib/bank/client.ex) from `iex` to use the application:\n\n```\niex(1)\u003e alias Bank.Client\nBank.Client\niex(2)\u003e Client.create_account(\"Foo\")\n:ok\niex(3)\u003e Client.status(\"Foo\")\n%{\n  account_balance: 0,\n  available_balance: 0,\n  id: \"0b0924ad-b928-5633-9352-7b01bc2046cc\",\n  name: \"Foo\"\n}\niex(4)\u003e Client.deposit(\"Foo\", 100)\n:ok\niex(5)\u003e Client.status(\"Foo\")\n%{\n  account_balance: 100,\n  available_balance: 100,\n  id: \"0b0924ad-b928-5633-9352-7b01bc2046cc\",\n  name: \"Foo\"\n}\n```\n\n## DOING\n\n- Decouple the `EventStore` from its storage implementation (`AppendOnlyStore`)\n\n## Questions \u0026 TODOs\n\n- Introduce the `EventStoreConcurrencyException`\n- Complete the HTTP\n- Replace `id` with `aggregate_id` (or `source_id`) in the `Event` definition\n- Probably the `EventHandler` is the [`AccountProjections`](https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/ReadModel.cs) that listen to some specific events in order to update the view (can we reuse the same pattern adopted for the `TransferOperation`s?)\n- Add a new projection that provide the list of all the available accounts with the current account balance\n- Add a new projection that provide the list of all operations made on a specific account\n- Probably the `Account` is identified by a code. The person (e.g. the name) should be a reference to an external entity (e.g. the `Owner`, `owner_id`)\n- Whenever I want to confirm or complete a transfer operation there is no check at aggregate level (e.g. from the payer perspective: is there a `TransferOperationOpened` when the aggregate receive a `CompleteTransferOperation`?)\n- At the moment there is no check for the state transition for a transfer operation in the `TransferOperationProcessManager`\n- How to deal with the state of the Process Manager when replaying events?\n  - [Process Managers should persist their state](https://tech.just-eat.com/2015/05/26/process-managers/)\n- Should the `CommandBus` [raise exceptions](https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/FakeBus.cs)?\n  - How to define this with TypeSpec?\n- What about the idea to use a `ProcessId` (or `CommandId`) to identify or remember the [originator of the command in the event](http://danielwhittaker.me/2014/10/18/6-code-smells-cqrs-events-avoid/)?\n- Inject collaborators instead of using functions\n- Try to add a policy for event conflicts resolution\n  - https://tech.zilverline.com/2012/08/08/simple-event-sourcing-conflict-resolution-part-4\n  - https://medium.com/@teivah/event-sourcing-and-concurrent-updates-32354ec26a4c\n  - http://danielwhittaker.me/2014/09/29/handling-concurrency-issues-cqrs-event-sourced-system/\n  - https://dzone.com/articles/the-good-of-event-sourcing-conflict-handling-repli\n- There is some duplicated code in the command handlers tests (e.g., `expect_never` and some aliases and imports)\n- Could we consider to introduce an AccountRepository to hide the detail about the EventStore in the command handlers?\n- Should the EventDescriptor have the aggregateId?\n- Consider to return the changes from the first to the latest, and also following this order in the event store\n- Probably the InMemoryEventStore is the EventStore itself. What should change is where the event descriptors are stored. Think about it!\n- EventHandler is too big and quite difficult to test.\n  - Probably is better to decouple the logic from the implementation (GenServer)\n  - Have different event handler based on the event\n  - Consider to use [Task](https://hexdocs.pm/elixir/Task.html)s\n- Consider to save the latest version as a detail for the read model\n- Improve the setup of the acceptance test\n- Consider to run a `mix format` to see what happen :)\n\n## DONE\n\n- Replace \"Someone\" with \"A PAYER ID\" or \"A PAYEE ID\"\n- Introduce an HTTP layer to create an account :)\n- Introduce the use of a GUID for the aggregateId\n  - In this case probably is the `Bank.Client` that have to generate the GUID based on the `name` (to guarantee the uniqueness of `name`)?\n- Update the `Account` view on `TransferOperationCompleted`\n- Add the `CompleteTransferOperation` command handler\n- Create and example of how a money transfer between two bank accounts could be\n  - Introduce a `TransferOperationManager` ...\n    - Try to [separate the implementation from the domain logic](https://pragdave.me/blog/2017/07/13/decoupling-interface-and-implementation-in-elixir.html)\n      - `TransferOperationProcess` (implementation: GenServer + State)\n      - `TransferOperationManager` (domain logic)\n  - Now that I can send a `TransferOperationOpened` I could be able to finalize the transaction\n    - Should I use the concept of Process Managers ? Where we have a Process Manager for each operation_id?\n    - Is the money transfer a long running\n  - References about Process Manager\n    - [EIP](https://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html)\n    - [Process Manager and Events Flow](https://www.infoq.com/news/2017/07/process-managers-event-flows)\n- `CommandBus.publish` should be `send`, better to extract a behaviour for the commandbus\n  - Maybe we don't need a command bus to subscribe on. Think about ...\n- Introduce the concept of `Account Balance` and `Available Balance`\n- Do not use the task supervisor for now\n- Move default_handlers as configuration\n- Consider to use [Supervised Tasks](https://hexdocs.pm/elixir/Task.html#module-supervised-tasks)s to run commands\n- Introduce Task to run commands\n- CommandHandler is too big and quite difficult to test.\n- What to test for the `CommandHandler`?\n- Have different command handler based on the command\n- Decouple the logic of command handlers from the GenServer implementation\n- CommandHandler handles cast\n- Account should not be a process\n  - it should act as a set of transition functions (fn(current_state, action) -\u003e [events])\n  - also, we are not cleaning up the uncommitted changes\n  - and also, the account processes are created as child of the command handler! Ouch!\n- Provide a read model (view/projection) for the BankAccount\n- How to handle concurrent issue in the `EventStore.append_to_stream`?\n- Handle the `expected_version` when trying to append new events `EventStore.append_to_stream`\n- Based on the [source](https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/EventStore.cs), another responsability of the event store is to publish events once they are saved. Do we need to move this responsability elsewhere? Or we can proceed to maintain it there?\n  - At the moment we say that is a responsability of the EventStore to publish the events once they are stored\n- Provide an implementation of the `EventPublisher` to publish events via `EventBus`\n- Publish the events to the EventBus once the events have been stored\n- Implement an InMemory `EventStore` ([source](https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/EventStore.cs))\n- Probably we could consider to review the tests of the InMemoryEventStore. Test the behaviour and not the functions!\n- Check that the events are stored in the correct order\n- Check that the version follows the correct numerical progression\n- Remove EventStream\n- Use Mox instead of Mock\n- [!] Remove `BankService`\n- Write an Acceptance Test\n- `AccountRepository` should deal with the `id` and not with the `pid`\n- We may have to introduce an `EventBus`\n- `Account.load_from_event_stream` is not tested\n- `Account` may be able to create named processes, so that we can easily identiy `Account`s by their names intead of `pid`s\n- Introduce a `Registry` for `Account` named process\n- Introduce an `AccountRepository` that will act as a repository for `Account`, and it will be used by the `BankService`\n  - Move the withdrawn out of the BankService\n- `EventStore.append_to_stream` should return `:ok` and not `{:ok}`\n- Consider to return an `EventStream` instead of a list when doing `Account.changes(...)`\n- `Accounts` should be a `BankService`. It is stateless and will collaborate with the `AccountRepository`\n\n## TRASHED / NOT NEEDED\n\n- In order to reduce the concurrency exception, one solution could be to serialize the execution of commands related to the same aggregate id\n  - At the moment the TaskSupervisor is disabled\n- Should the `CommandHandler` return errors?\n- `Bank` will act as a client that will send commands\n- When handle the `deposit_money` command we should check if the `account` process is running\n- `EventBus` and `CommandBus` are quite similar\n- What about an `AccountRepository` to `find` and `save` accounts?\n- When to flush all the `changes` of the `Account`?\n- Does `Account`s may to be supervised?\n- Extract the `via_registry` out from `Account` (the business logic should be decoupled from the genserver)\n- Elixir: Is it possible to configure the application through environment variables?\n- Maybe the responsabilities to `create` and `find` an `Account` should be delegated to the `AccountRepository`, and we may think to rename it as `Accounts`?\n- [?] Implement an `EventStoreAccountRepository`\n\n## FINAL NOTES\n\n- At the moment `Account.confirm_transfer_operation` is deliberately simplified. It does not take care of the `payer` and `operation_id`. So, there is no check if the operation is already confirmed, or not.\n\n## Extras\n\nRun multiple calls:\n\n```\nEnum.each(1..10, fn(i) -\u003e Task.start(fn() -\u003e Process.sleep(:rand.uniform(5) * 100); Bank.Client.deposit(\"ACCOUNT\", 1) end) end)\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoebew42%2Felixir_cqrs_es_example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoebew42%2Felixir_cqrs_es_example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoebew42%2Felixir_cqrs_es_example/lists"}