diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2b5de01c989a4cfab60d17af82888d27bd14f606 --- /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 0000000000000000000000000000000000000000..f915dab285fc29f2650f1ff6d76b07cf767f5433 --- /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 0000000000000000000000000000000000000000..7f40b1be5132b473668ff98a060954b7010ca7ed --- /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 0000000000000000000000000000000000000000..5cb62e061d81f7ea6eda928cae02fc9e1f89f280 --- /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 0000000000000000000000000000000000000000..4d8b552c339c3ba46efe03e257fb3ee47159cb42 --- /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 0000000000000000000000000000000000000000..c8fcab543a9cfc5c5c21bb0b6cc80414275625ef --- /dev/null +++ b/bank-actors/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.6.2