{"id":19506806,"url":"https://github.com/calvinlfer/account-experiment","last_synced_at":"2026-06-13T12:02:23.328Z","repository":{"id":68736701,"uuid":"108218144","full_name":"calvinlfer/account-experiment","owner":"calvinlfer","description":null,"archived":false,"fork":false,"pushed_at":"2017-10-25T04:21:12.000Z","size":13,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-05T19:56:44.717Z","etag":null,"topics":["actor-model","akka","domain-driven-design","functional-programming","scala"],"latest_commit_sha":null,"homepage":null,"language":"Scala","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/calvinlfer.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":"2017-10-25T04:05:41.000Z","updated_at":"2018-05-01T14:37:20.000Z","dependencies_parsed_at":"2023-04-22T00:30:04.315Z","dependency_job_id":null,"html_url":"https://github.com/calvinlfer/account-experiment","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/calvinlfer/account-experiment","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Faccount-experiment","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Faccount-experiment/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Faccount-experiment/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Faccount-experiment/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/calvinlfer","download_url":"https://codeload.github.com/calvinlfer/account-experiment/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calvinlfer%2Faccount-experiment/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34283391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["actor-model","akka","domain-driven-design","functional-programming","scala"],"created_at":"2024-11-10T22:38:29.381Z","updated_at":"2026-06-13T12:02:23.284Z","avatar_url":"https://github.com/calvinlfer.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Account experiments\nModelling accounts (albeit naively) using Functional and Reactive Domain Driven Design techniques.\n\nThe aim of this approach is two-fold:\n\n- develop an equationally reasonable and functional domain model that can be verified in isolation\n- decouple the aforementioned domain model from communication protocols, execution concerns, and state management\n\nThis follows the Simple Domain Object Pattern by Dr. Roland Kuhn and the construction of the functional \ndomain model from Debasish Ghosh.\n\n\n### Domain Modelling\nThe domain algebra (domain model) follows Domain Driven Design and defined in terms of the Ubiquitous Language of the \ndomain experts. For accounts, you expect to see, credits, debits, etc. \n\nWe define the operations that take place in the domain in terms of functions and defer giving meaning to the types that\nwe operate on implying that the functions are polymorphic. For example:\n\n```scala\ntrait AccountService[Money, Account, Effect[_]] {\n\n  // ... \n  def credit(amount: Money): Account =\u003e Account\n\n  def debit(amount: Money): Account =\u003e Effect[Account]\n\n  // ...\n}\n```\n\nWe also have the notation of an effect (a Higher Kinded Type) in order to represent a context that can represent \nasynchronous boundaries, failures, etc. to indicate that these operations can cause side-effects in the system.\n \nWe define this collection of domain functions to be an Algebra. Please note that there are no implementation details in\nan algebra. However, given this compositional form, we can write derived combinators that can use existing domain \nfunctions to procure new behavior without providing an implementation. For example:\n\n```scala\ntrait AccountService[Money, Account, Effect[_]] {\n  // ... \n  def preAuth(amount: Money): Account =\u003e Effect[Account]\n  \n  def cancel(amount: Money): Account =\u003e Effect[Account]\n  \n  def credit(amount: Money): Account =\u003e Account\n\n  def debit(amount: Money): Account =\u003e Effect[Account]\n\n  // derived combinator\n  def capture(amount: Money): Account =\u003e Effect[Account] = account =\u003e\n    for {\n      accWithMoney \u003c- cancel(amount)(account)       // remove the hold\n      accResult    \u003c- debit(amount)(accWithMoney)   // debit the account\n    } yield accResult\n\n  // ...\n}\n```\n\nNote that I have removed some boilerplate. In order to use the for-comprehension, you must impose that the \nFirst-Order-Higher-Kinded-Type Effect has a Monad implementation in order to gain `flatMap` and `map` behavior so you\nmay compose those effectful domain functions.\n\nNotice that `capture` uses the existing algebra to create new behavior. This means if you implement the core functions, \nyou get this additional behavior for free. \n\nYou can develop an interpreter (implementation) for this algebra and verify that the implementation is correct through\nproperty-based testing. For example:\n\n```scala\n  property(\"A credit followed by a debit of the same amount will not change the account balances\") =\n    forAll(accountGen) { account =\u003e\n      val amount = 100\n      val s1 = credit(amount)(account)\n      val updatedAccount = debit(amount)(s1)\n      updatedAccount.isRight \u0026\u0026\n        updatedAccount.right.get == account\n    }\n```\n\nThis covers the domain model. Now let's address reactivity by using actors and layering on the communication protocol\nand state management. \n\n### Communication protocols, concurrency and state management\n\nNow that we have our domain model, it is time to deal with state and communication. We use the actor model to adhere to\nthe reactive manifesto and provide a solution to concurrent access. Notice that functions like `capture` rely on\nthe `cancel` and `debit` operations to be executed atomically which is one of the key features that the Actor model \nprovides. We also need to be able to store the most up to date state of the Account and be able to determine the \nsequence of events that lead up to the current state of the account balances. This is accomplished by Event Sourcing. \n\nIn order to use the domain model, we encapsulate it with an Actor and define Commands and Events in order to interact\nwith the Actor which will make use of the domain model. For example:\n\n```scala\nobject Account {\n  sealed trait Command\n  // ...\n  case class Credit(amount: MoneyBD) extends Command\n  case class Debit(amount: MoneyBD) extends Command\n  // ...\n  case object BalancesQuery extends Command\n\n  sealed trait Event\n  case class AccountCredited(amount: MoneyBD) extends Event\n  case class AccountDebited(amount: MoneyBD) extends Event\n  // ...\n\n  case class ValidationError(message: String)\n  case class BalanceResponse(balance: MoneyBD, heldBalance: MoneyBD)\n\n  def props: Props = Props[Account]\n}\n\nclass Account extends PersistentActor with ActorLogging {\n  // the most up-to-date in-memory state\n  var currentAcctState: SimpleAccount = SimpleAccount()\n\n  // use events to (re)construct the in-memory model\n  def updateState(event: Event): Unit = event match {\n    case AccountCredited(amount) =\u003e currentAcctState = credit(amount)(currentAcctState)\n    case AccountDebited(amount) =\u003e currentAcctState = debit(amount)(currentAcctState).right.get\n  }\n\n  override def persistenceId: String = s\"calvin-account\"\n\n  // Commands go here\n  override def receiveCommand: Receive = LoggingReceive {\n    // ... \n    case Credit(amount) =\u003e\n      persist(AccountCredited(amount)) { event =\u003e\n        updateState(event)\n      }\n\n    case Debit(amount) =\u003e\n      debit(amount)(currentAcctState).fold(\n        error =\u003e sender() ! ValidationError(error),\n        updatedAccount =\u003e\n          persist(AccountDebited(amount)) { event =\u003e\n            currentAcctState = updatedAccount\n          }\n      )\n\n    case BalancesQuery =\u003e\n      sender() ! BalanceResponse(currentAcctState.balance, currentAcctState.balanceHeld)\n  }\n\n  // During recovery, Events from the journal will come here\n  override def receiveRecover: Receive = {\n    case e: Event =\u003e updateState(e)\n  }\n}\n```\n\nThis shows a basic implementation of layering on the communication protocol and state management. This also demonstrates\nhow the Actor uses the domain model to change the state of the account. The main idea is that Commands are sent to the \nActor which are then validated. If the validation succeeds then Events are generated and persisted to the event journal,\nonce that takes place then we update the in-memory model of the Account. If the Actor dies, it can bring itself up to\ndate by playing back all the Events from the event journal to restore itself to the most current in-memory model. \n\nPlease note, we have not spoken of concerns like Schema Evolution, Snapshots, Cluster Sharding, etc. which are critical \nto the application running correctly.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalvinlfer%2Faccount-experiment","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcalvinlfer%2Faccount-experiment","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalvinlfer%2Faccount-experiment/lists"}