From 070d598244f754cfa114b6dd4a8b35ea7159fc4d Mon Sep 17 00:00:00 2001 From: Martin Ring <martin.ring@encoway.de> Date: Tue, 24 May 2022 10:25:01 +0200 Subject: [PATCH] add bank example --- .gitignore | 7 +++ bank-actors/Account.scala | 32 ++++++++++++++ bank-actors/Bank.scala | 64 ++++++++++++++++++++++++++++ bank-actors/WireTransfer.scala | 37 ++++++++++++++++ bank-actors/build.sbt | 11 +++++ bank-actors/project/build.properties | 1 + 6 files changed, 152 insertions(+) create mode 100644 .gitignore create mode 100644 bank-actors/Account.scala create mode 100644 bank-actors/Bank.scala create mode 100644 bank-actors/WireTransfer.scala create mode 100644 bank-actors/build.sbt create mode 100644 bank-actors/project/build.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b5de01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.bloop +.metals +target +project/project +metals.sbt +.bsp +.vscode \ No newline at end of file diff --git a/bank-actors/Account.scala b/bank-actors/Account.scala new file mode 100644 index 0000000..f915dab --- /dev/null +++ b/bank-actors/Account.scala @@ -0,0 +1,32 @@ +import akka.actor.typed.ActorRef +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.Behaviors + +object Account: + enum Message: + case GetBalance(sender: ActorRef[Balance]) + case Deposit(amount: Int, client: ActorRef[MovementResponse]) + case Withdraw(amount: Int, client: ActorRef[MovementResponse]) + + + + case class Balance(balance: Int) + enum MovementResponse: + case Ok + case NotOk + + def behavior(number: Int, balance: Int): Behavior[Message] = Behaviors.receiveMessage { + case Message.GetBalance(sender) => + sender ! Balance(balance) + Behaviors.same + case Message.Deposit(amount, client) => + client ! MovementResponse.Ok + behavior(number,balance + amount) + case Message.Withdraw(amount, client) => + if balance >= amount then + client ! MovementResponse.Ok + behavior(number,balance - amount) + else + client ! MovementResponse.NotOk + Behaviors.same + } \ No newline at end of file diff --git a/bank-actors/Bank.scala b/bank-actors/Bank.scala new file mode 100644 index 0000000..7f40b1b --- /dev/null +++ b/bank-actors/Bank.scala @@ -0,0 +1,64 @@ +import akka.actor.typed.ActorSystem +import akka.actor.typed.{ActorRef,Behavior} +import akka.actor.typed.scaladsl.Behaviors + +object Bank: + enum Message: + case CreateAccount(sender: ActorRef[Response.AccountCreated]) + case AccountMessage(account: Int, msg: Account.Message) + case Transfer(from: Int, to: Int, amount: Int, client: ActorRef[Account.MovementResponse]) + + enum Response: + case AccountCreated(number: Int) + + def behavior(accounts: Map[Int,ActorRef[Account.Message]]): Behavior[Message] = Behaviors.setup { context => + Behaviors.receiveMessage { + case Message.CreateAccount(sender) => + val number = accounts.size + val accountActor = context.spawn(Account.behavior(number, 0), s"account-$number") + sender ! Response.AccountCreated(number) + behavior(accounts + (number -> accountActor)) + case Message.AccountMessage(account, msg) => + accounts.get(account).foreach(_ ! msg) + Behaviors.same + case Message.Transfer(fromId,toId,amount,client) => + val transferActor = for { + from <- accounts.get(fromId) + to <- accounts.get(toId) + } yield context.spawnAnonymous(WireTransfer.init(from,to,amount,client)) + transferActor.getOrElse(client ! Account.MovementResponse.NotOk) + Behaviors.same + } + } + + + +@main def main = + val testBehavior = Behaviors.setup[Bank.Response | Account.Balance | Account.MovementResponse] { ctx => + val bank = ctx.spawn(Bank.behavior(Map.empty), "bank") + bank ! Bank.Message.CreateAccount(ctx.self) + Behaviors.receiveMessage { + case Bank.Response.AccountCreated(x) => + ctx.log.info(s"account $x was created") + bank ! Bank.Message.CreateAccount(ctx.self) + Behaviors.receiveMessage { + case Bank.Response.AccountCreated(y) => + ctx.log.info(s"account $y was created") + bank ! Bank.Message.AccountMessage(x, Account.Message.Deposit(1000, ctx.self)) + bank ! Bank.Message.AccountMessage(y, Account.Message.Withdraw(500, ctx.self)) + bank ! Bank.Message.Transfer(x,y,5,ctx.self) + Behaviors.same + case Account.Balance(x) => + ctx.log.info(s"the balance is $x") + Behaviors.same + case Account.MovementResponse.Ok => + ctx.log.info(s"ok") + bank ! Bank.Message.AccountMessage(x, Account.Message.GetBalance(ctx.self)) + Behaviors.same + case Account.MovementResponse.NotOk => + ctx.log.info(s"not ok") + Behaviors.same + } + } + } + val bank = ActorSystem(testBehavior, "test-system") \ No newline at end of file diff --git a/bank-actors/WireTransfer.scala b/bank-actors/WireTransfer.scala new file mode 100644 index 0000000..5cb62e0 --- /dev/null +++ b/bank-actors/WireTransfer.scala @@ -0,0 +1,37 @@ +import akka.actor.typed.Behavior +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.Behaviors + +object WireTransfer: + def init( + from: ActorRef[Account.Message], + to: ActorRef[Account.Message], + amount: Int, + client: ActorRef[Account.MovementResponse] + ): Behavior[Account.MovementResponse] = Behaviors.setup { ctx => + def waitForWithdrawal: Behavior[Account.MovementResponse] = Behaviors.receiveMessage { + case Account.MovementResponse.Ok => + to ! Account.Message.Deposit(amount, ctx.self) + waitForDeposit + case Account.MovementResponse.NotOk => + client ! Account.MovementResponse.NotOk + Behaviors.stopped + } + def waitForDeposit: Behavior[Account.MovementResponse] = Behaviors.receiveMessage { + case Account.MovementResponse.Ok => + client ! Account.MovementResponse.Ok + Behaviors.stopped + case Account.MovementResponse.NotOk => + from ! Account.Message.Deposit(amount, ctx.self) + client ! Account.MovementResponse.NotOk + waitForCancel + } + def waitForCancel: Behavior[Account.MovementResponse] = Behaviors.receiveMessage { + case Account.MovementResponse.Ok => + Behaviors.stopped + case Account.MovementResponse.NotOk => + sys.error("wire transfer did not succeed. money stuck in transient state.") + } + from ! Account.Message.Withdraw(amount, ctx.self) + waitForWithdrawal + } diff --git a/bank-actors/build.sbt b/bank-actors/build.sbt new file mode 100644 index 0000000..4d8b552 --- /dev/null +++ b/bank-actors/build.sbt @@ -0,0 +1,11 @@ +name := "lecture-04.5" +scalaVersion := "3.1.1" +val AkkaVersion = "2.6.19" +val Slf4jVersion = "1.7.36" +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion, + "com.typesafe.akka" %% "akka-actor-testkit-typed" % AkkaVersion, + "org.slf4j" % "slf4j-api" % Slf4jVersion, + "org.slf4j" % "slf4j-simple" % Slf4jVersion +) +Compile / scalaSource := baseDirectory.value \ No newline at end of file diff --git a/bank-actors/project/build.properties b/bank-actors/project/build.properties new file mode 100644 index 0000000..c8fcab5 --- /dev/null +++ b/bank-actors/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.2 -- GitLab