Skip to content

Instantly share code, notes, and snippets.

@octonato
Last active May 22, 2018 20:43
Show Gist options
  • Save octonato/5af2ae584f154759d2793730082306ef to your computer and use it in GitHub Desktop.
Save octonato/5af2ae584f154759d2793730082306ef to your computer and use it in GitHub Desktop.
package akka.persistence.typed.scaladsl
import akka.actor.typed.Behavior
import akka.persistence.typed.scaladsl.PersistentBehaviors.{CommandHandler, _}
class AccountSpec {
sealed trait AccountCommand
case object CreateAccount extends AccountCommand
case class Deposit(amount: Double) extends AccountCommand
case class Withdraw(amount: Double) extends AccountCommand
case object CloseAccount extends AccountCommand
sealed trait AccountEvent
case object AccountCreated extends AccountEvent
case class Deposited(amount: Double) extends AccountEvent
case class Withdrawn(amount: Double) extends AccountEvent
case object AccountClosed extends AccountEvent
sealed trait Account
case class OpenedAccount(balance: Double) extends Account
case object ClosedAccount extends Account
private def initial: CommandHandler[AccountCommand, AccountEvent, Option[Account]] =
(_, _, cmd) =>
cmd match {
case CreateAccount ⇒ Effect.persist(AccountCreated)
}
private def openedAccountHandlers(acc: OpenedAccount): CommandHandler[AccountCommand, AccountEvent, Option[Account]] =
(ctx, _, cmd) => cmd match {
case Deposit(amount) ⇒ Effect.persist(Deposited(amount))
case Withdraw(amount) if (acc.balance - amount) < 0.0 =>
throw new RuntimeException("Insufficient balance")
case Withdraw(amount) =>
Effect
.persist(Withdrawn(amount))
.andThen { st => // can't even use a PF here because this method is overloaded
st match { // need to pattern match because state is Account, Effect is invariant on State
case Some(OpenedAccount(balance)) =>
// do some side-effect using balance
println(balance)
}
}
case CloseAccount if acc.balance == 0.0 =>
Effect.persist(AccountClosed)
case CloseAccount =>
throw new RuntimeException("Account balance is not zero")
}
private def commandHandlers: CommandHandler[AccountCommand, AccountEvent, Option[Account]] =
CommandHandler.byState {
case None => CommandHandler.command {
case CreateAccount => Effect.persist(AccountCreated)
}
case Some(acc@OpenedAccount(_)) => openedAccountHandlers(acc)
case Some(ClosedAccount) => throw new RuntimeException("account already closed")
}
private def eventHandler(stateOpt: Option[Account], event: AccountEvent):Option[Account] =
(stateOpt, event) match {
case (None, AccountCreated) => Some(OpenedAccount(0.0))
case (Some(acc@OpenedAccount(_)), Deposited(amount)) =>
Some(acc.copy(balance = acc.balance + amount))
case (Some(acc@OpenedAccount(_)), Withdrawn(amount)) =>
Some(acc.copy(balance = acc.balance - amount))
case (Some(acc@OpenedAccount(_)), AccountClosed) =>
Some(ClosedAccount)
// this or compiler warning. We should not need to do it
case _ => throw new RuntimeException("this should never happen")
}
def behavior(accountNumber: String): Behavior[AccountCommand] =
PersistentBehaviors.receive[AccountCommand, AccountEvent, Option[Account]](
persistenceId = accountNumber,
initialState = None,
commandHandler = commandHandlers,
eventHandler = eventHandler
)
}
package akka.persistence.typed.scaladsl
import akka.actor.typed.Behavior
import akka.persistence.typed.scaladsl.PersistentBehaviors.{CommandHandler, _}
class AccountSpec {
sealed trait AccountCommand
case object CreateAccount extends AccountCommand
case class Deposit(amount: Double) extends AccountCommand
case class Withdraw(amount: Double) extends AccountCommand
case object CloseAccount extends AccountCommand
sealed trait AccountEvent
case object AccountCreated extends AccountEvent
case class Deposited(amount: Double) extends AccountEvent
case class Withdrawn(amount: Double) extends AccountEvent
case object AccountClosed extends AccountEvent
sealed trait Account
case class OpenedAccount(balance: Double) extends Account
case object ClosedAccount extends Account
private def openedAccountHandler(acc: OpenedAccount): CommandHandler[AccountCommand, AccountEvent, Account] =
(ctx, _, cmd) => cmd match {
case Deposit(amount) ⇒ Effect.persist(Deposited(amount))
case Withdraw(amount) if (acc.balance - amount) < 0.0 =>
throw new RuntimeException("Insufficient balance")
case Withdraw(amount) =>
Effect
.persist(Withdrawn(amount))
.andThen { st => // can't even use a PF here because this method is overloaded
st match { // need to pattern match because state is Account, Effect is invariant on State
case OpenedAccount(balance) =>
// do some side-effect using balance
println(balance)
}
}
case CloseAccount if acc.balance == 0.0 =>
Effect.persist(AccountClosed)
case CloseAccount =>
throw new RuntimeException("Account balance is not zero")
}
def openedAccountEvtHandler(acc: OpenedAccount): EventHandler[AccountEvent, Account] = {
case Deposited(amount) =>
acc.copy(balance = acc.balance + amount)
case Withdrawn(amount) =>
acc.copy(balance = acc.balance - amount)
case AccountClosed => ClosedAccount
// this or compiler warning. We should not need to do it
case _ => throw new RuntimeException("this should never happen")
}
def behavior(accountNumber: String): Behavior[AccountCommand] =
PersistentBehaviors[AccountCommand, AccountEvent, Account]
.identifiedBy(accountNumber)
.onCreation (
commandHandler = CommandHandler.command {
case CreateAccount => Effect.persist(AccountCreated)
},
eventHandler = {
case AccountCreated => OpenedAccount(0.0)
}
)
.onUpdate (
commandHandler = {
case acc: OpenedAccount => openedAccountHandler(acc)
case ClosedAccount => throw new RuntimeException("account already closed")
},
eventHandler = {
case acc: OpenedAccount => openedAccountEvtHandler(acc)
}
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment