{"id":19736195,"url":"https://github.com/arnauld/moneytransfer","last_synced_at":"2025-11-09T17:32:04.197Z","repository":{"id":44221329,"uuid":"214852277","full_name":"Arnauld/moneytransfer","owner":"Arnauld","description":null,"archived":false,"fork":false,"pushed_at":"2022-02-11T00:25:54.000Z","size":88,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-10T18:59:07.408Z","etag":null,"topics":["java","vertx-web","vertx3"],"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/Arnauld.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}},"created_at":"2019-10-13T16:16:56.000Z","updated_at":"2019-10-14T16:30:21.000Z","dependencies_parsed_at":"2022-09-03T12:13:50.009Z","dependency_job_id":null,"html_url":"https://github.com/Arnauld/moneytransfer","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/Arnauld%2Fmoneytransfer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Fmoneytransfer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Fmoneytransfer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arnauld%2Fmoneytransfer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Arnauld","download_url":"https://codeload.github.com/Arnauld/moneytransfer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241062522,"owners_count":19902905,"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":["java","vertx-web","vertx3"],"created_at":"2024-11-12T01:05:39.866Z","updated_at":"2025-11-09T17:32:04.158Z","avatar_url":"https://github.com/Arnauld.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Design\n\n**TLDR;**\n\nMoney transfer design has been motivated for strong resilience and operations\nrepeatability.\nThus, it leaded to a strong separation of 'Credit' and 'Debit' operations. \nAll operations have been designed to handle multiple submissions.\n\nThis allows strong idempotency, repeatability and resilience of all operations.\nIf an operation fails, it will be reattempted until the feedback loop is complete.\n\n## Transaction Id and idempotency\n\nMoney transfer is uniquely identified by a `transaction-id`. Such identifier\nprotects also against double submit operation (as we would do with a 'formToken').\nTo simplify current implementation, `transaction-id` is actually provided\nby the submitter, one strongly suggest UUID type 4 generation.\nIn next implementation, we could ask first for a new `transaction-id`\nbefore submitting the request.\n\nMoney transfer example:\n\n```json\n{\"transaction-id\": \"b000e8c5-541c-45e3-9367-c785931e94f4\",\n \"source-id\": \"16b6877a-e7f1-4569-a4f4-f8bf7b4d648f\",\n \"destination-id\": \"895abadf-71d3-41ea-bdc5-4282c72eef35\",\n \"amount\":  \"500.56\"}\n```\n\n*Note* : Since all amounts are stored in `BigDecimal`, one will use its\n`String` representation as transferable representation.\n\n## Debit\n\nOnce the Money transfer has been submitted, a transaction is created on the \nsource 'account' and uniqueness is ensured.\nAs in real life, one suppose the money transfer is initiated by the (source) \naccount that will give the money (the account that will be 'Debited'). \n\nYou can submit the same transaction as many times as you want, idempotency is\nensured by transaction id.\n\nAll pending transactions are processed periodically in a serialized way,\nwithin a single thread for a given account (The actual implementation, use\na single thread for all account, but could be easy modified): \nsee [DefaultMoneyTransferService#propagateTransactions](src/main/java/banktransfer/core/account/DefaultMoneyTransferService.java#L54).\nThe source account is debited, transaction is marked `Debited` and the bank\nshould transmit the corresponding transaction to the 'destination' account's \nbank.\n\n## Credit\n\nThe 'destination' account will receive the corresponding 'Credit' transaction \nperiodically.\nThe 'source' bank will stop sending the transaction once it has been marked \n`Acknowledged`. \nThe transaction is stacked within the 'destination' account.\nOne more time, idempotency is ensured by the `transaction-id` global uniqueness,\nand will be processed using the same 'by-thread' approach.\n\nThis ensures robustness: transaction can be submitted as many times as required,\npreventing i/o error, etc.\n\n## Acknowledge\n\nThe 'destination' account will receive the 'credit' transaction as long as\nit send the 'Acknowledge' back to the 'source' account.\n\n# Implementation\n\n## Vertx/Verticles\n\nApplication has been built on [vertx](https://vertx.io/) and `Verticle`\nbuilding block.\nThe `MainVerticle` sole purpose is to start the `WebVerticle` verticle, \nwhich is responsible to handle all http request (one should starts one\nverticle per available processor minus one), and to start the `TransactionPropagationVerticle`\nverticle which is the single threaded part of the transaction processing.\n\n## Status\n\nDue to verticle approach, and to anticipate easier error handling in asynchronus\nprocessing, choice has been made to not rely on `Exception`. Exceptions\nare catched on boundary layer (`infra`), but `core` only rely on [`Status`](src/main/java/banktransfer/core/Status.java)\nconstruct.\n`Status` can be seen as a very simplified implementation of the `Either`\n(or `LeftRight`) monad.\n\n## Usage\n\n* [Usecases Tests](src/test/java/banktransfer/infra/UsecasesIntegrationTest.java#L63)\n* [Concurrency Tests](src/test/java/banktransfer/core/account/inmemory/ConcurrencyUsecaseTest.java#L36)\n\n\n# Dev's notes\n\n```\nmvn clean package\njava -jar target/bank-transfert-1.0-SNAPSHOT-fat.jar\n```\n\nAccount with initial balance of `500`.\n\n```\ncurl -d '{\"email\":\"titania@tyrna.nog\", \"initial-balance\":\"500\"}' \\\n     -H \"Content-Type: application/json\" \\\n     -X POST http://localhost:8083/account\n```\n\n```       \n{\"account-id\":\"6b827186-6cec-49b1-ab87-a16719074715\"}%\n```\n\nAccount with default initial balance (`0`)\n```\ncurl -d '{\"email\":\"oberon@tyrna.nog\"}' \\\n     -H \"Content-Type: application/json\" \\\n     -X POST http://localhost:8083/account\n```\n\n```\n{\"account-id\":\"ee438cf0-eb3c-4aad-9f33-052179e095dd\"}%\n```\n\nTransfer money between the two accounts:\n\n```\ncurl -d '{\"transaction-id\":\"99a72d4d-b862-4fee-b251-3f60d9ce7846\",\"amount\":\"150\", \"source-id\":\"6b827186-6cec-49b1-ab87-a16719074715\",\"destination-id\":\"ee438cf0-eb3c-4aad-9f33-052179e095dd\"}' \\\n     -H \"Content-Type: application/json\" \\\n     -X POST http://localhost:8083/transfer\n```\n```\n{\"transaction-id\":\"99a72d4d-b862-4fee-b251-3f60d9ce7846\"}%\n```\n\nConsult `Debited` account:\n\n```\ncurl -H \"Content-Type: application/json\" \\\n     -X GET http://localhost:8083/account/6b827186-6cec-49b1-ab87-a16719074715\n```\n```\n{\"account-id\":\"6b827186-6cec-49b1-ab87-a16719074715\",\n \"balance\":\"350\",\n \"transactions\":[{\"transaction-id\":\"99a72d4d-b862-4fee-b251-3f60d9ce7846\",\n                  \"status\":\"Acknowledged\",\n                  \"source-id\":\"6b827186-6cec-49b1-ab87-a16719074715\",\n                  \"destination-id\":\"ee438cf0-eb3c-4aad-9f33-052179e095dd\",\n                  \"amount\":\"150\"}]}%\n```\n\nConsult `Credited` account:\n\n```\ncurl -H \"Content-Type: application/json\" \\\n     -X GET http://localhost:8083/account/ee438cf0-eb3c-4aad-9f33-052179e095dd\n```\n\n```\n{\"account-id\":\"ee438cf0-eb3c-4aad-9f33-052179e095dd\",\n \"balance\":\"150\",\n \"transactions\":[{\"transaction-id\":\"99a72d4d-b862-4fee-b251-3f60d9ce7846\",\n                  \"status\":\"Credited\",\n                  \"source-id\":\"6b827186-6cec-49b1-ab87-a16719074715\",\n                  \"destination-id\":\"ee438cf0-eb3c-4aad-9f33-052179e095dd\",\n                  \"amount\":\"150\"}]\n}%\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnauld%2Fmoneytransfer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farnauld%2Fmoneytransfer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnauld%2Fmoneytransfer/lists"}