{"id":17302331,"url":"https://github.com/coriolinus/transacty","last_synced_at":"2025-03-26T22:24:09.718Z","repository":{"id":79236191,"uuid":"467503436","full_name":"coriolinus/transacty","owner":"coriolinus","description":null,"archived":false,"fork":false,"pushed_at":"2022-03-08T12:40:13.000Z","size":24,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-01T04:16:20.654Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","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/coriolinus.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-08T12:30:30.000Z","updated_at":"2023-01-05T08:58:58.000Z","dependencies_parsed_at":"2023-06-01T23:30:59.810Z","dependency_job_id":null,"html_url":"https://github.com/coriolinus/transacty","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/coriolinus%2Ftransacty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coriolinus%2Ftransacty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coriolinus%2Ftransacty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coriolinus%2Ftransacty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coriolinus","download_url":"https://codeload.github.com/coriolinus/transacty/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245744305,"owners_count":20665256,"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-10-15T11:47:14.004Z","updated_at":"2025-03-26T22:24:09.702Z","avatar_url":"https://github.com/coriolinus.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Readme\n\nThis repo defines a very simple toy transaction engine, modeling certain financial transactions.\n\n## Design Decisions\n\n### Newtypes\n\nNewtypes are used extensively. It's almost not worth it for a problem of this size, but in a larger program,\nmaintainability is substantially improved when each type permits only allowed operations.\n\nThe most important newtype is `primitives::Amount`, which takes some care to discard dust and ensure that\narithmetic is not subject to floating-point errors.\n\n### Synchronicity\n\nThis code is written in a synchronous manner for simplicity and ease of development. This is an intentional choice,\nmotivated largely by the YAGNI principle.\n\n\u003e What if your\n\u003e code was bundled in a server, and these CSVs came from thousands of\n\u003e concurrent TCP streams?\n\nIn this case, we'd want to write an asynchronous version of this code. This would be a pretty straightforward\nmodification, the most complicated part of which would be to update the `StateManager` trait to deal with `Future` trait objects\n(as of this writing, async trait members are still not allowed in stable rust).\n\nIn that case we'd also need some more robust definition of the actual sequencing of events; several of the event effects\ndepend on the precise order of prior events, which would be unreliable given events flowing through thousands of\nconcurrent TCP streams.\n\n### Data Storage\n\nThis program stores global state in memory. This is not an ideal solution for a production system for obvious reasons,\nbut for a toy it should be fine. For production code, we'd likely want to maintain global state in an external data store.\nEither Redis or a SQL engine (Postgres, Sqlite) might be appropriate choices of data store depending on requirements.\n\nBecuase it was simple to, I wrote a `StateManager` trait which allows us to swap out different data backends as required.\nActually extending the program to use an external data store should be very easy.\n\n### Library-first design\n\nThis program is written first as a library, with a very thin executable wrapped around it. This design pattern is very useful\nto ensure maximum reusability of components.\n\n### Logging and Telemetry\n\n... have been omitted. YAGNI for a toy project.\n\n### Testing\n\nIntegration tests are presented as CSV files in the `inputs/` directory. Each input contains an example of the expected\noutput when run with the `--debug` flag. There is at least one simple integration test demonstrating each error type.\n\nI did not write any integration testing code which would run each of these as a distinct test, because that was more work\nthan I wanted to put in at this point. That would be an obvious next step.\n\nUnit tests appear occasionally, for complicated bits. These generally use the `proptest` crate to expand the space of\ninputs tested.\n\n### Error Handling\n\nErrors are generally handled gracefully, with some work put into ensuring stability. When run with the `--debug` flag,\nruntime errors (i.e. insufficient balance to withdraw) are reported to stderr; otherwise, they are silently suppressed.\n\nThere are no instances of `.unwrap()` in this codebase. Explicit assumptions are sometimes expressed via `.expect()`.\n\n## Assumptions\n\n- No test data will cause any `Amount` to overflow\n- Only deposits can be disputed\n- Only withdrawals are affected by locks; deposits are still permitted\n- Test data is valid CSV throughout; invalid CSV data is not a state to guard against\n- Writing to stderr never panics\n- Disputes will never result in the available balance underflowing\n\nThese assumptions are sometimes reflected in error-handling simplifications and may panic if invalidated.\n\nThe last assumption is critical, and the most likely to be invalidated.\nHowever, it's impossible to tell from the instructions given what the policy should be. Some plausible policies:\n\n- Invent a `SignedAmount` type which can be used only for the `available` balance, compatible otherwise with `Amount`. Requires a fair amount of implementation.\n- Invent a holding period during which deposits are automatically held, and after which transactions cannot be disputed. Requires a notion of time.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoriolinus%2Ftransacty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoriolinus%2Ftransacty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoriolinus%2Ftransacty/lists"}