{"id":19016291,"url":"https://github.com/akhansari/esbankaccount","last_synced_at":"2025-04-23T02:41:56.916Z","repository":{"id":45235706,"uuid":"392392000","full_name":"akhansari/EsBankAccount","owner":"akhansari","description":"Bank Account kata and Functional Event Sourcing in F#","archived":false,"fork":false,"pushed_at":"2021-12-29T10:43:32.000Z","size":43229,"stargazers_count":80,"open_issues_count":0,"forks_count":9,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-04-17T18:23:29.496Z","etag":null,"topics":["bolero","dotnet","elmish","event-sourcing","eventsourcing","fsharp","functional-event-sourcing"],"latest_commit_sha":null,"homepage":"https://akhansari.github.io/EsBankAccount/","language":"F#","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/akhansari.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-08-03T17:03:05.000Z","updated_at":"2024-09-27T15:04:20.000Z","dependencies_parsed_at":"2022-08-27T06:41:00.988Z","dependency_job_id":null,"html_url":"https://github.com/akhansari/EsBankAccount","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/akhansari%2FEsBankAccount","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhansari%2FEsBankAccount/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhansari%2FEsBankAccount/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhansari%2FEsBankAccount/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akhansari","download_url":"https://codeload.github.com/akhansari/EsBankAccount/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250360242,"owners_count":21417716,"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":["bolero","dotnet","elmish","event-sourcing","eventsourcing","fsharp","functional-event-sourcing"],"created_at":"2024-11-08T19:42:58.685Z","updated_at":"2025-04-23T02:41:56.895Z","avatar_url":"https://github.com/akhansari.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bank account kata and Functional Event Sourcing\n\nF# template/POC about Functional Event Sourcing, Onion Architecture and WebAssembly.\n\nWanna file an issue? a suggestion? Please feel free to [create a new issue](https://github.com/akhansari/EsBankAccount/issues/new) and / or [a pull request](https://github.com/akhansari/EsBankAccount/compare).\\\nOr [start a new discussion](https://github.com/akhansari/EsBankAccount/discussions/new) for questions, ideas, etc.\n\n## Why?\n\n### F#\nEmpowers everyone to write succinct, robust and performant code.\\\nIt enables you to write backend (taking advantage of .Net ecosystem) as well as frontend (transpiled to JS or compiled to Wasm) applications.\n\n### Functional Event Sourcing\nFully embrace immutability and expressions, in addition to other more traditional ES perks.\n\n### Onion Architecture\nLeads to more maintainable applications since it emphasizes separation of concerns throughout the system.\\\nIt's even quite natural with F#, i.e. compositions and higher-order functions.\n\n### WebAssembly\nFacilitate the development of powerful UIs and back office apps with minimal effort.\\\nNote that for the sake of simplicity in this demo, the view and the business logic have both been put in the same project in order to make this application \"hostable\" on GitHub. \\\nWhen deployed to an actual real-world production environment, they are often located in separate projects with different lifecycles.\n\n## Setup\n\n- Install .Net SDK [6.0](https://dotnet.microsoft.com/download/dotnet/6.0) (Linux / Windows / macOS)\n- To [B|T]DD: `dotnet watch test --project EsBankAccount.sln`\n- To watch: `dotnet watch run --project EsBankAccount/EsBankAccount.fsproj`\n\nEditors: [Vim](https://github.com/ionide/Ionide-vim) / [VSCode](https://marketplace.visualstudio.com/items?itemName=Ionide.Ionide-fsharp) / [VS Windows](https://visualstudio.microsoft.com/vs/community/) / [VS macOS](https://visualstudio.microsoft.com/vs/mac/)\n\n## Kata\n\nYou can simply clone the `kata-start` branch and start practicing.\\\nFollow the instructions in `BankAccountTests.fs` and `BankAccountTests.state.fs`.\n\n## Decider\n\n[Deciders](https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider) should have, at least, an initial state and two functions:\n\n- `evolve: 'State -\u003e 'Event -\u003e 'State`\\\n  Given the current state and what happened, evolve to a new state.\n\n  - From new events: `fold evolve currentState newEvents`\n  - From the history: `fold evolve initialState history`\n\n- `decide: 'Command -\u003e 'State -\u003e 'Outcome`\\\n  Given what has been requested and the current state, decide what should happen.\n\nThey are composable:\n\n\u003cimg src=\"assets/decider.png\" alt=\"decider\" /\u003e\n\n### Decider Tests\n\nIt's very convenient to create [Given-When-Then](EsBankAccount.Tests/Domain/BankAccountTests.fs) tests.\n\n```fsharp\n[\u003cFact\u003e]\nlet ``close the account and withdraw the remaining amount`` () =\n    spec {\n        Given // history\n            [ Deposited { Amount = 100m; Date = DateTime.MinValue } ]\n        When  // command\n            ( Close DateTime.MinValue )\n        // what should happen\n        Then // assert scenario\n            ( function Ok events -\u003e Assert.NotEmpty events | _ -\u003e () )\n        Then // true or false scenario\n            ( function Ok [ Withdrawn _; Closed _ ] -\u003e true | _ -\u003e false )\n        Then // equality then structural diff scenario\n            [ Withdrawn { Amount = 100m; Date = DateTime.MinValue }\n              Closed DateTime.MinValue ]\n    }\n```\n\nThere are two kinds of them:\n1. Test what has been done (mandatory).\n   - We don't mind how we come up with the outcome.\n   - But, we do need to make sure that the outcome has to be correct under the given condition.\n2. Test how it has been done (optional).\n   - We aren't too concerned about the outcome.\n   - But, we need to build the state in a particular way.\n\nIt should be noted that the BDD DSL style brings more readability and neat helpers but it isn't mandatory.\\\nIn your test files you can have different kind of unit tests. For instance a test could be as simple as [this](https://github.com/thinkbeforecoding/UnoCore/blob/solution/Uno.Tests/Tests.fs).\n\n### Decider Structure\n\nIt's possible to organize the Decider into five sections:\n1. types\n1. state logic\n1. decision logic\n1. validation (optional)\n1. decision pipeline\n\nKeep it in one file until it hurts and then decide the best split(s) at the last responsible moment.\n\n### Decision Outcome\n\nThere are usually, at least, two categories of Deciders:\n1. System `-\u003e 'Event list`\\\n   Silent, if nothing has happened, then it will return an empty list. No need for validation.\n1. Frontal `-\u003e Result\u003c'Event list, 'Error\u003e`\\\n   When validation is required. For instance called from an API.\\\n   Could also be `-\u003e Validation\u003c'Event list, 'Error list\u003e`.\n\n### Validations\n\nWe could have different types of validation in each layer:\n1. Domain: Enforce constraints on new events, business validation.\n1. Application: Anti-corruption, validate infrastructures data.\n1. Startup: Secure and validate data shape.\n\n## Onion Architecture\n\n- _Inner_ layers \"aren't aware\" of _outer_ layers.\n- Domain is pure (i.e. think functional programming 101).\n- App only has a reference to the domain.\n- Infra only has references to other infrastructures.\n- Startup has references to the App and the Infra. Infra are injected to the App.\n- We usually start to code from the inside to the right (i.e. output), then again from the inside to the left (i.e. input).\n\n\u003cimg src=\"assets/onion.png\" alt=\"onion architecture\" /\u003e\n\n## Resources\n\n- [Functional Event Sourcing](https://thinkbeforecoding.com/category/Event-Sourcing) by Jérémie Chassaing\n- [State from Events or Events as State?](https://verraes.net/2019/08/eventsourcing-state-from-events-vs-events-as-state/) by Mathias Verraes\n- [Temporal Modelling](https://verraes.net/2019/06/talk-temporal-modelling/) by Mathias Verraes\n- [Expectations for an Event Store](https://github.com/ylorph/RandomThoughts/blob/master/2019.08.09_expectations_for_an_event_store.md) by Yves Lorphelin\n- [Effective F#, tips and tricks](https://gist.github.com/swlaschin/31d5a0a2c4478e82e3ed60d653c0206b) by Scott Wlaschin\n- [Equinox](https://github.com/jet/equinox) by Jet and Ruben Bartelink\n- [Event Sourcing in .NET tutorials](https://github.com/oskardudycz/EventSourcing.NetCore) by Oskar Dudycz\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakhansari%2Fesbankaccount","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakhansari%2Fesbankaccount","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakhansari%2Fesbankaccount/lists"}