{"id":27039938,"url":"https://github.com/0xkalvin/transfer-service","last_synced_at":"2025-04-05T03:19:50.618Z","repository":{"id":47667515,"uuid":"392016398","full_name":"0xkalvin/transfer-service","owner":"0xkalvin","description":"A completely over-engineered, Node.js bank transfer service that ~~tries to~~ leverages hexagonal architecture.","archived":false,"fork":false,"pushed_at":"2021-08-19T20:04:55.000Z","size":473,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-27T14:55:45.876Z","etag":null,"topics":["aws","banking","grpc","hexagonal-architecture","nodejs","repl","sqs","transfer"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/0xkalvin.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":"2021-08-02T16:16:49.000Z","updated_at":"2023-04-14T12:09:18.000Z","dependencies_parsed_at":"2022-08-24T08:20:51.308Z","dependency_job_id":null,"html_url":"https://github.com/0xkalvin/transfer-service","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/0xkalvin%2Ftransfer-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xkalvin%2Ftransfer-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xkalvin%2Ftransfer-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xkalvin%2Ftransfer-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0xkalvin","download_url":"https://codeload.github.com/0xkalvin/transfer-service/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247281180,"owners_count":20913123,"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":["aws","banking","grpc","hexagonal-architecture","nodejs","repl","sqs","transfer"],"created_at":"2025-04-05T03:19:50.033Z","updated_at":"2025-04-05T03:19:50.609Z","avatar_url":"https://github.com/0xkalvin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Transfer service\n\nA simple, over-engineered Node.js bank transfer system that leverages ~~or tries to~~ hexagonal architecture. It's an in progress project to play around and have fun, feel free to contribute by any means. \n\n## Requirements\n\n- Must support creating accounts and transfers.\n- Must support creating transfer through a REST API, a SQS queue, a GRPC API, and also a REPL. That's what \"Transporters\" are, alternative ways of inputting data in this application for interacting with the business entities.\n- When a transfer is processed, accounts' balances should be updated.\n- Must use a couple of databases and messaging queues for the sweet purpose of over engineering :joy:\n\n## Architecture\n\n- Two servers: one using REST and one using GRPC.\n- Two queue workers: one consuming a SQS queue and one using Kafka.\n- Databases: Postgres for transactional data, Elasticsearch for fast reads/reporting, and Redis for ephemeral data such as Idempotency keys.\n- Each Idempotency key used for creating a transfer should cease to exist after 24 hours. \n- When a transfer created, it is persisted on a Postgres's table, replicated to an Elasticseach storage  \n, and its Idempotency key is inserted in Redis Layer for 24 hours of timespan.\n- When a Transfer is created, it must be enqueued in a Kafka topic so it can be processed in background async.  \n\n\n## Folder structure \n\n```text\nsrc\n├── data-sources\n│   ├── elasticsearch\n│   │   ├── config.js\n│   │   └── index.js\n│   ├── kafka\n│   │   ├── client.js\n│   │   ├── config.js\n│   │   ├── consumer.js\n│   │   ├── index.js\n│   │   └── producer.js\n│   ├── postgres\n│   │   ├── config.js\n│   │   ├── index.js\n│   │   ├── migrations\n│   │   │   ├── 20210803012952-create-accounts.js\n│   │   │   └── 20210803013010-create-transfers.js\n│   │   └── models\n│   │       ├── account.js\n│   │       ├── index.js\n│   │       └── transfer.js\n│   ├── redis\n│   │   ├── config.js\n│   │   └── index.js\n│   └── sqs\n│       ├── client.js\n│       ├── config.js\n│       ├── index.js\n│       ├── local-setup.conf\n│       └── poller.js\n├── lib\n│   ├── business-errors.js\n│   └── logger.js\n├── repositories\n│   ├── account\n│   │   └── index.js\n│   └── transfer\n│       └── index.js\n├── services\n│   ├── account\n│   │   └── index.js\n│   └── transfer\n│       └── index.js\n└── transporters\n    ├── grpc\n    │   ├── controllers\n    │   │   ├── account.js\n    │   │   └── transfer.js\n    │   ├── entrypoint.js\n    │   └── proto\n    │       ├── account.proto\n    │       ├── shared.proto\n    │       └── transfer.proto\n    ├── kafka\n    │   └── entrypoint.js\n    ├── repl\n    │   └── entrypoint.js\n    ├── rest\n    │   ├── application.js\n    │   ├── controllers\n    │   │   ├── account\n    │   │   │   └── index.js\n    │   │   └── transfer\n    │   │       └── index.js\n    │   ├── entrypoint.js\n    │   ├── http-errors.js\n    │   └── middlewares\n    │       ├── error-handler.js\n    │       ├── http-logger.js\n    │       ├── validator-handler.js\n    │       └── wrap-async.js\n    └── sqs\n        ├── entrypoint.js\n        ├── process.js\n        └── processors\n            ├── index.js\n            └── transfers\n                └── index.js\n```\n\n## Running locally\n\nRequirements:\n- Docker\n- Docker compose\n- Unix-like OS\n\nAfter fulling all requirements, start everything up by running\n```sh\nmake\n```\n\nThen you will find running:\n- a Http server listening on port 3000\n- Postgres + Elasticsearch + redis containers \n- kafka + zookeeper + sqs containers\n\n\nCheck out the api reference below for details about how to call each endpoint :) \n\n## REPL\n\nTo start up the repl, just run \n```sh\nmake repl\n```\n\nIn the repl, you can also create a transfer as following\n\n```js\nawait createTransfer({ sourceAccountId: \"f0916334-e4eb-43cb-b9eb-a2a9e89277e3\", targetAccountId: \"b4703965-b530-4941-b6e0-c975dce98ac9\", transferAmount: 15 })\n```\n\n## REST API\n\n### POST /accounts\n\nRequest\n```sh\ncurl -XPOST http://localhost:3000/accounts \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n  \"amount\": 1,\n  \"source_account_id\": \"acc_ckqe3n57o00040jtl4yuz2jh5\",\n  \"target_account_id\": \"acc_ckqe3n83w00070jtl1tz52p8g\"\n}'\n```\nResponse (status code 201)\n```json\n{\n    \"id\": \"45ba22be-ac12-4693-9e21-e7ba3ca279d0\",\n    \"balance\": 100000,\n    \"holder_name\": \"Bojack Horseman\",\n    \"holder_document_number\": \"12345678910\"\n}\n```\n\n### GET /accounts/:id\n\nRequest\n```sh\ncurl http://localhost:3000/accounts/1\n```\n\nResponse\n```json\n{\n    \"id\": \"45ba22be-ac12-4693-9e21-e7ba3ca279d0\",\n    \"balance\": 100000,\n    \"holder_name\": \"Bojack Horseman\",\n    \"holder_document_number\": \"12345678910\"\n}\n```\n\n### GET /accounts/:id/transfers\n\nRequest\n```sh\ncurl http://localhost:3000/accounts/1/transfers\n```\n\nResponse\n```json\n[\n    {\n        \"id\": \"ce4ae758-cf95-412a-a603-275fd22f5991\",\n        \"amount\": 1,\n        \"source_account_id\": \"acc_ckqe3n57o00040jtl4yuz2jh5\",\n        \"target_account_id\": \"acc_ckqe3n83w00070jtl1tz52p8g\",\n        \"status\": \"failed\"\n    },\n    {\n        \"id\": \"ce4ae758-cf95-412a-a603-275fd22f5992\",\n        \"amount\": 1,\n        \"source_account_id\": \"acc_ckqe3n57o00040jtl4yuz2jh5\",\n        \"target_account_id\": \"acc_ckqe3n83w00070jtl1tz52p8g\",\n        \"status\": \"pending\"\n    }\n    {\n        \"id\": \"ce4ae758-cf95-412a-a603-275fd22f5993\",\n        \"amount\": 1,\n        \"source_account_id\": \"acc_ckqe3n57o00040jtl4yuz2jh5\",\n        \"target_account_id\": \"acc_ckqe3n83w00070jtl1tz52p8g\",\n        \"status\": \"settled\"\n    }\n]\n```\n\n### POST /transfers\nRequest\n```sh\ncurl -XPOST http://localhost:3000/transfers \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n  \"amount\": 1,\n  \"source_account_id\": \"acc_ckqe3n57o00040jtl4yuz2jh5\",\n  \"target_account_id\": \"acc_ckqe3n83w00070jtl1tz52p8g\"\n}'\n```\n\nResponse (status code 201)\n```json\n{\n    \"id\": \"ce4ae758-cf95-412a-a603-275fd22f599f\",\n    \"amount\": 1,\n    \"source_account_id\": \"acc_ckqe3n57o00040jtl4yuz2jh5\",\n    \"target_account_id\": \"acc_ckqe3n83w00070jtl1tz52p8g\",\n    \"status\": \"pending\"\n}\n```\n\n\n## TODO\n- decouple Kafka  transporter\n- Add database transations\n- Add schemas\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xkalvin%2Ftransfer-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0xkalvin%2Ftransfer-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xkalvin%2Ftransfer-service/lists"}