{"id":25195048,"url":"https://github.com/rvarago/disbursements-scala","last_synced_at":"2025-04-04T15:12:30.711Z","repository":{"id":37958023,"uuid":"503025481","full_name":"rvarago/disbursements-scala","owner":"rvarago","description":"A toy example of a backend app written in Scala","archived":false,"fork":false,"pushed_at":"2022-09-14T16:24:43.000Z","size":31,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-10T00:41:21.801Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rvarago.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-06-13T16:05:03.000Z","updated_at":"2022-06-13T16:35:52.000Z","dependencies_parsed_at":"2023-01-17T17:15:56.899Z","dependency_job_id":null,"html_url":"https://github.com/rvarago/disbursements-scala","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/rvarago%2Fdisbursements-scala","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fdisbursements-scala/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fdisbursements-scala/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fdisbursements-scala/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rvarago","download_url":"https://codeload.github.com/rvarago/disbursements-scala/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247198469,"owners_count":20900081,"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":"2025-02-10T00:41:01.463Z","updated_at":"2025-04-04T15:12:30.695Z","avatar_url":"https://github.com/rvarago.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Disbursements Backend\n\nA simple application built to calculate disbursements for orders bought by shoppers from merchants .\n\n## Implementation\n\nThe application has been [Scala](https://docs.scala-lang.org/) version 3 and relies on [cats-effect](https://typelevel.org/cats-effect/) as the async runtime where we reify computations as values of type `IO`.\n\nThe design takes a simple approach where we may acknowledge are four layers:\n\n1. Api - HTTP routes for request/response to query by date/merchant, then\n2. Algebra - Service where disbursements get accumulated given the search parameters, then\n3. Repository - A thin abstraction over a storage layer, for the sake of simplicity it's currently backed by a in-memory constant store, then\n4. Types - A set of types representing business concepts, namely the order abstraction from which disbursements get computed.\n\nUpon providing a date within the week of interest (and possibly the merchant id), we then find its corresponding week as per:\n\nWe define a week by truncating the date such that:\n_ opening time is the previous (or same) Monday (beginning)\n_ closing time is the next (or same) Sunday (ending).\n\nThen, after obtaining the list of orders (and possibly filtering by merchant id), we then accumulate the disbursements for each orders, yielding the total as the JSON response.\n\n## Api\n\nThe exposed API comprise two HTTP endpoints where one may query disbursements over time and, optionally, by merchant.\n\n- Over time:\n\n```text\n- Request:\n\n    api/v1/disbursements/$DATE\n\n    -- Example:\n\n        api/v1/disbursements/2000-01-01\n\n- Response:\n\n    -- On Success:\n\n        {\"Total\":{\"amount\":\"$DISBURSED\"}}\n\n        --- Example:\n\n            {\"Total\":{\"amount\":\"€100.90\"}}\n\n    -- On Error:\n\n        {\"NotFound\":{\"starting\":\"$START\",\"ending\":\"$END\"}}\n\n        --- Example:\n\n            {\"NotFound\":{\"starting\":\"1999-12-27T00:00:00\",\"ending\":\"2000-01-02T23:59:59.999999999\"}}\n```\n\n- Over time by merchant:\n\n```text\n- Request:\n\n    api/v1/disbursements/$DATE/$MERCHANTID\n\n    -- Example:\n\n        api/v1/disbursements/2000-01-01/123\n\n- Response:\n\n    -- On Success:\n\n        {\"Total\":{\"amount\":\"$DISBURSED\"}}\n\n        --- Example:\n\n            {\"Total\":{\"amount\":\"€50.10\"}}\n\n    -- On Error:\n\n        {\"NotFound\":{\"starting\":\"$START\",\"ending\":\"$END\",\"merchantId\":$MERCHANTID}}}}\n\n        --- Example:\n\n            {\"NotFound\":{\"starting\":\"1999-12-27T00:00:00\",\"ending\":\"2000-01-02T23:59:59.999999999\",\"merchantId\":123}}\n```\n\nWhere:\n\n```text\n* $DATE:       Date within the week one wants to query disbursements,\n* $START:      Start of aggreation period containing $DATE,\n* $END:        End of the aggreation period containing $DATE,\n* $MERCHANTID: Identifier of the merchant to which disbursements are due,\n* $DISBURSED:  Amount disbursed.\n\n```\n\n## Initial load\n\nUpon start up, the app expects a CSV file to present from which it loads orders into the data store.\n\nThe file must adhere the following schema:\n\n```text\n$ID,$MERCHANT_ID,$SHOPPER_ID,$AMOUNT,$CREATED_AT,$COMPLETED_AT\n```\n\nWhere:\n\n```text\n* There must *not* be any header,\n* Dates must be encoded as dd/MM/yyyy HH:mm:ss,\n* $COMPLETED_AT is optional,\n* Extra columns are ignored.\n\n```\n\nThe amount is mapped onto the app's default currency (EUR).\n\nExample\n\n```text\n113,3,359,268.76,04/01/2018 22:33:00,\n4,9,11,185.36,01/01/2018 03:48:00,03/01/2018 01:59:56\n```\n\n## Future improvements\n\n- Tune usage of thread pools (e.g. shift disbursements computation onto a cached, unbounded pool where blocking is fine),\n- Adjust logging to obtain relevantly useful information and nothing more,\n- Replace the in-memory DB by a more fitting storage technology (e.g. PostgreSQL),\n- Auto-generate API documentation (e.g. OpenAPI via [tapir](https://tapir.softwaremill.com/en/latest/docs/openapi.html))\n- Expose endpoints to insert/change entities,\n- Package the application into a distribution mechanism (e.g. Docker),\n- Add infrastructure for load testing (e.g. via reproducible setup, e.g. via Nix),\n- Protect endpoints with security layers (e.g. auth/authz),\n\n## Run application\n\n\u003e The setup requires (sbt)[https://www.scala-sbt.org/] available in the system.\n\n```shell\nsbt run\n```\n\nBy default, the application binds on `127.0.0.1:8080` and loads the set of orders from `orders.csv` (relative to the path of the binary).\n\n\u003e It's possible to override such defaults by setting the environment variables listed in the [template](.env.template).\n\nAfter starting it up, we may query it (e.g. via curl) as:\n\n```shell\nλ curl -v localhost:8080/api/v1/disbursements/2000-01-01\n\nλ curl -v localhost:8080/api/v1/disbursements/2000-01-01/123\n```\n\n## Run tests\n\n```shell\nsbt test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frvarago%2Fdisbursements-scala","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frvarago%2Fdisbursements-scala","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frvarago%2Fdisbursements-scala/lists"}