{"id":18638205,"url":"https://github.com/dehasi/sd-p11-r","last_synced_at":"2025-11-04T15:30:36.430Z","repository":{"id":235864321,"uuid":"790355182","full_name":"dehasi/sd-p11-r","owner":"dehasi","description":null,"archived":false,"fork":false,"pushed_at":"2024-04-22T18:19:49.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-27T09:22:47.665Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","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/dehasi.png","metadata":{"files":{"readme":"README.adoc","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}},"created_at":"2024-04-22T18:19:10.000Z","updated_at":"2024-04-24T16:32:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"77044c0d-7a53-46b8-9c44-666bcaff7f16","html_url":"https://github.com/dehasi/sd-p11-r","commit_stats":null,"previous_names":["dehasi/sd-p11-r"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehasi%2Fsd-p11-r","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehasi%2Fsd-p11-r/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehasi%2Fsd-p11-r/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehasi%2Fsd-p11-r/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dehasi","download_url":"https://codeload.github.com/dehasi/sd-p11-r/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239435480,"owners_count":19638137,"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":[],"created_at":"2024-11-07T05:39:48.657Z","updated_at":"2025-11-04T15:30:36.369Z","avatar_url":"https://github.com/dehasi.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Practice #11\n:toc:\n\n== Prerequisites\n\nTo be able to work with code, you need:\n\n* Java 21\n* Maven\n* SQLite\n\n\n== Refactoring to EventSouring\n\nWe will work on an application that emulates bank accounts.\nYou can create an account, deposit, withdraw money, or request a balance.\nAlso, a bank can apply fees.\n\n*Everything is oversimplified.*\nMany obvious features are omitted, like validation, id-generation, decimals, currencies, etc.\nWe aim to learn an event-sourcing idea, not create a bank application.\n\n\nThe domain model is simple — a bank account is two integers: `id` and `amount`.\n\n.BankAccount entity\n[source, java]\n----\npublic class BankAccount {\n    public final int id;\n    public final int balance;\n}\n----\n\nFor this exercise, we don't need more\n\n=== State-based approach\n\nSo far, we have an application that does basic operations on accounts.\nAfter each operation, the application updates the state and saves it into a database.\nSuch an approach called CRUD is widely used; you're likely familiar with it.\nIn most cases, it works, and you don't need more. It is our case we'll need more :)\n\n.example of working program\n----\n\u003e help\n(c)reate id\n(b)alance id\n(d)eposit id amount\n(w)ithdraw id amount\n(f)ee id amount\n\u003e c 42\ncreated account, id 42, balance $0\n\u003e b 42\naccount 42, balance: $0\n\u003e d 42 500\ndeposited: $500, new balance: $500\n\u003e w 42 100\nwithdrew: $100, new balance: $400\n\u003e f 42 4\nfee: $4, new balance: $396\n\u003e b 42\naccount 42, balance: $396\n\u003e exit\n----\n\nAs we keep only the last state, we don't know how we ended up in this state.\nWas it a series of withdrawals or fees or both?\n\n*PROBLEM:* Clients accuse us that we steal their money =\u003e we need to keep history.\n\n=== Introducing events\nIn DDD world, _event_ is a fact that something has happened.\nIt seems there are four events in our domain.\n\n.what's happened?\n* Account has been created\n* Money has been deposited\n* Money has been withdrawn\n* Fee has been applied\n\nWe won't store account's state anymore. We will store events =\u003e what has happened with the account.\nWhen we need an account in the latest state, we have to read all events and apply them in the historical order.\nSuch operation is called _replaying events_.\n\n.what you need to do\n* [ ] Create events (tip: it's convenient when all events implement one interface i.e.`DomainEvent`)\n* [ ] Add to `BankAccount` a constructor from events\n* [ ] Modify `BankAccount` for returning events\n* [ ] Refactor `BankAccountRepository` to work with events\n* [ ] Create `CashMovementsService`, add `history` method that returns cash movements as in the example below\n* [ ] Update `UserInputController` to call `history`\n* [ ] Make sure that unit tests pass\n\n.when we call history\n----\n\u003e h 42\ncreated, id:42\ndeposited: $5\ndeposited: $5\ndeposited: $50\nwithdrew: $9\nfee: $4\n----\n\n*NEXT PROBLEM:* Bank has applied a new rule, $1 fee should be automatically applied for deposits more than $25 =\u003e make `BankAccount` \"cause\" a few events per operation.\n\n=== Few events\nIt is normal when one operation (_command_) leads to a few events.\nSometimes even handling an event can lead to another event.\n\nIn our case, most work was already done in the previous step. We only need to refactoe\n\n.what you need to do\n* [ ] Modify `BankAccount` for causing a few events (i.e. `List\u003cDomainEvent\u003e events`)\n* [ ] Add logic to add $1 fee for depositing more than $25\n* [ ] Refactor `BankAccountRepository`, if necessary\n* [ ] Make sure that unit tests pass\n\n\n*NEXT PROBLEM:* Tax department wants to see summary: how much was totally deposited/withdrawn =\u003e process events to calculate all deposits, withdrawals, fees.\n\n=== Projections\nThe ordered list of events is called _stream_. Previously, we _replayed_ a _stream_ to build our _entity_ - `BankAccount`.\nHowever, we can process our _stream_ in a different way. Or use only a subset of events.\nSuch operation, when we derive a new state from event stream, is called _projection_.\n\nIn our case, the projection will be an account summary.\n\n.what you need to do\n* [ ] Modify `CashMovementsService`, add `summary` method to calculate sums of all deposits, withdrawals, fees.\n* [ ] Update `UserInputController` to call `summary`\n* [ ] Make sure that unit tests pass\n\n.when we call summary\n----\n\u003e s 42\nid: 42\ntotal deposited: $60\ntotal withdrew: $9\ntotal fees: $5\n----\n\n*NEXT PROBLEM:* When the application restarts, we lose data =\u003e store events in a database\n\n=== Store events in DB\nIn all previous examples we kept events in memory, using popular `HashMap`-based \"storage\".\nIn the real world, we need more durable storage.\nThere are even special event storages, i.e.,\nhttps://www.eventstore.com/[EventStore] or https://martendb.io/[Marten] (both use PostgreSQL underneath btw).\nHowever, in most cases, just one dedicated table in any relation database is enough.\n\n\nWe don't know all events in advance. Moreover, some events can change structure in the future (it is beyond our exercise).\nThat's why it's practical to not only store an event's `data` but the event's `type` as well.\nWe need to serialize and deserialize events somehow. Nowadays, the most popular format is JSON.\nWe also need to maintain order; that's why we need `version`.\nIf all of it sounds confusing, see the example below.\n\n.what you need to do\n* [ ] Create a table for events in your database\n* [ ] Implement `BankAccountRepository` to store events in the database\n* [ ] Make sure that unit tests pass\n\n.schema\n[source, sql]\n----\nCREATE TABLE events (\n  id              INTEGER NOT NULL PRIMARY KEY,\n  entity_id       INTEGER NOT NULL,\n  version         INTEGER NOT NULL,\n  type            VARCHAR NOT NULL,\n  data            VARCHAR NOT NULL,\n  created_at      DATETIME(6)  NOT NULL,\n\n  UNIQUE (entity_id, version)\n);\n----\n\n.events for two bank account entities, with ids 21 and 42\n|===\n| id | entity_id | version | type | data | created_at\n\n|1|42|1|AccountCreated|{\"id\":42}|2024-04-20T00:45:01\n|2|42|2|MoneyDeposited|{\"amount\":50}|2024-04-20T00:46:02\n|3|42|3|FeeApplied|{\"amount\":1}|2024-04-20T00:47:03\n|4|42|4|MoneyWithdrew|{\"amount\":9}|2024-04-20T00:48:04\n|5|42|5|FeeApplied|{\"amount\":4}|2024-04-20T00:49:05\n|6|21|1|AccountCreated|{\"id\":21}|2024-04-20T22:50:06\n|7|21|2|MoneyDeposited|{\"amount\":5}|2024-04-20T22:51:07\n|8|21|3|MoneyDeposited|{\"amount\":5}|2024-04-20T22:52:08\n|9|21|4|MoneyDeposited|{\"amount\":50}|2024-04-20T22:53:09\n|10|21|5|FeeApplied|{\"amount\":1}|2024-04-20T22:54:10\n|11|21|6|MoneyWithdrew|{\"amount\":9}|2024-04-20T22:55:11\n\n\n|===\n*NEXT PROBLEM:* It's too long to replay too many events =\u003e implement snapshots (it is beyond our exercise).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehasi%2Fsd-p11-r","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdehasi%2Fsd-p11-r","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehasi%2Fsd-p11-r/lists"}