Skip to content

Instantly share code, notes, and snippets.

@patriknw
Last active May 23, 2018 09:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save patriknw/84af016a991aef8576db47593da0cf96 to your computer and use it in GitHub Desktop.
Save patriknw/84af016a991aef8576db47593da0cf96 to your computer and use it in GitHub Desktop.
Alternative implementation of PR 25051
object AccountExample1 {
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 val initialHandler: CommandHandler[AccountCommand, AccountEvent, Option[Account]] =
CommandHandler.command {
case CreateAccount ⇒ Effect.persist(AccountCreated)
case _ ⇒ Effect.unhandled
}
private val openedAccountHandler: CommandHandler[AccountCommand, AccountEvent, Option[Account]] = {
case (ctx, Some(acc: OpenedAccount), cmd) ⇒ cmd match {
case Deposit(amount) ⇒ Effect.persist(Deposited(amount))
case Withdraw(amount) ⇒
if ((acc.balance - amount) < 0.0)
Effect.unhandled // TODO replies are missing in this example
else {
Effect
.persist(Withdrawn(amount))
.andThen {
case Some(OpenedAccount(balance)) ⇒
// do some side-effect using balance
println(balance)
}
}
case CloseAccount if acc.balance == 0.0 ⇒
Effect.persist(AccountClosed)
case CloseAccount ⇒
Effect.unhandled
}
}
private val closedHandler: CommandHandler[AccountCommand, AccountEvent, Option[Account]] =
CommandHandler.command(_ ⇒ Effect.unhandled)
private def commandHandler: CommandHandler[AccountCommand, AccountEvent, Option[Account]] =
CommandHandler.byState {
case None ⇒ initialHandler
case Some(OpenedAccount(_)) ⇒ openedAccountHandler
case Some(ClosedAccount) ⇒ closedHandler
}
private val eventHandler: (Option[Account], AccountEvent) ⇒ Option[Account] = {
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(OpenedAccount(_)), AccountClosed) ⇒
Some(ClosedAccount)
case (state, event) ⇒ throw new RuntimeException(s"unexpected event [$event] in state [$state]")
}
def behavior(accountNumber: String): Behavior[AccountCommand] =
PersistentBehaviors.receive[AccountCommand, AccountEvent, Option[Account]](
persistenceId = accountNumber,
initialState = None,
commandHandler = commandHandler,
eventHandler = eventHandler
)
}
object AccountExample2 {
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 object EmptyAccount extends Account
case class OpenedAccount(balance: Double) extends Account
case object ClosedAccount extends Account
private val initialHandler: CommandHandler[AccountCommand, AccountEvent, Account] =
CommandHandler.command {
case CreateAccount ⇒ Effect.persist(AccountCreated)
case _ ⇒ Effect.unhandled
}
private val openedAccountHandler: CommandHandler[AccountCommand, AccountEvent, Account] = {
case (ctx, acc: OpenedAccount, cmd) ⇒ cmd match {
case Deposit(amount) ⇒ Effect.persist(Deposited(amount))
case Withdraw(amount) ⇒
if ((acc.balance - amount) < 0.0)
Effect.unhandled // TODO replies are missing in this example
else {
Effect
.persist(Withdrawn(amount))
.andThen {
case OpenedAccount(balance) ⇒
// do some side-effect using balance
println(balance)
}
}
case CloseAccount if acc.balance == 0.0 ⇒
Effect.persist(AccountClosed)
case CloseAccount ⇒
Effect.unhandled
}
}
private val closedHandler: CommandHandler[AccountCommand, AccountEvent, Account] =
CommandHandler.command(_ ⇒ Effect.unhandled)
private def commandHandler: CommandHandler[AccountCommand, AccountEvent, Account] =
CommandHandler.byState {
case EmptyAccount ⇒ initialHandler
case OpenedAccount(_) ⇒ openedAccountHandler
case ClosedAccount ⇒ closedHandler
}
private val eventHandler: (Account, AccountEvent) ⇒ Account = {
case (EmptyAccount, AccountCreated) ⇒ OpenedAccount(0.0)
case (acc @ OpenedAccount(_), Deposited(amount)) ⇒
acc.copy(balance = acc.balance + amount)
case (acc @ OpenedAccount(_), Withdrawn(amount)) ⇒
acc.copy(balance = acc.balance - amount)
case (OpenedAccount(_), AccountClosed) ⇒
ClosedAccount
case (state, event) ⇒ throw new RuntimeException(s"unexpected event [$event] in state [$state]")
}
def behavior(accountNumber: String): Behavior[AccountCommand] =
PersistentBehaviors.receive[AccountCommand, AccountEvent, Account](
persistenceId = accountNumber,
initialState = EmptyAccount,
commandHandler = commandHandler,
eventHandler = eventHandler
)
}
package akka.docs.akka.persistence.typed
import akka.Done
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import akka.persistence.typed.scaladsl.Effect
import akka.persistence.typed.scaladsl.PersistentBehaviors
import akka.persistence.typed.scaladsl.PersistentBehaviors.CommandHandler
import akka.persistence.typed.internal.SideEffect
object InDepthPersistentBehaviorSpec3 {
abstract class OptionalInitialState[Command, Event, State] {
protected def initialCommandHandler: (ActorContext[Command], Command) ⇒ Effect[Event, State]
protected def activeCommandHandler: CommandHandler[Command, Event, State]
protected def initialEventHandler: Event ⇒ State
protected def activeEventHandler: (State, Event) ⇒ State
final val initialState: Option[State] = None
final def commandHandler: CommandHandler[Command, Event, Option[State]] = {
// capture the functions once, in case they are defined as defs
val initial = initialCommandHandler
val active = activeCommandHandler
CommandHandler.byState {
case Some(_) ⇒ convertEffect(active)
case None ⇒ convertInitialEffect(initial)
}
}
private def convertEffect(handler: CommandHandler[Command, Event, State]): CommandHandler[Command, Event, Option[State]] = {
(ctx, optState, cmd) ⇒
optState match {
case Some(state) ⇒
val effect = handler(ctx, state, cmd)
convertEffect(effect)
case None ⇒
throw new IllegalStateException("Undefined state")
}
}
private def convertInitialEffect(handler: (ActorContext[Command], Command) ⇒ Effect[Event, State]): CommandHandler[Command, Event, Option[State]] = {
(ctx, _, cmd) ⇒
val effect = handler(ctx, cmd)
convertEffect(effect)
}
private def convertEffect(effect: Effect[Event, State]): Effect[Event, Option[State]] = {
// FIXME traverse all chained effects and build up new that's replacing the SideEffect
effect match {
case SideEffect(callback) ⇒ SideEffect[Event, Option[State]] {
case Some(state) ⇒ callback(state)
case None ⇒ println(s"# ignoring side effect from initialCommandHandler when no state") // FIXME
}
}
}
final def eventHandler(state: Option[State], event: Event): Option[State] =
state match {
case None ⇒ Some(initialEventHandler(event))
case Some(s) ⇒ Some(activeEventHandler(s, event))
}
}
//#event
sealed trait BlogEvent extends Serializable
final case class PostAdded(
postId: String,
content: PostContent) extends BlogEvent
final case class BodyChanged(
postId: String,
newBody: String) extends BlogEvent
final case class Published(postId: String) extends BlogEvent
//#event
//#state
object BlogState {
val empty: Option[BlogState] = None
}
final case class BlogState(content: PostContent, published: Boolean) {
def withContent(newContent: PostContent): BlogState =
copy(content = newContent)
def postId: String = content.postId
}
//#state
//#commands
sealed trait BlogCommand extends Serializable
final case class AddPost(content: PostContent, replyTo: ActorRef[AddPostDone]) extends BlogCommand
final case class AddPostDone(postId: String)
final case class GetPost(replyTo: ActorRef[PostContent]) extends BlogCommand
final case class ChangeBody(newBody: String, replyTo: ActorRef[Done]) extends BlogCommand
final case class Publish(replyTo: ActorRef[Done]) extends BlogCommand
final case object PassivatePost extends BlogCommand
final case class PostContent(postId: String, title: String, body: String)
//#commands
class BlogPostEntity(ctx: ActorContext[BlogCommand]) extends OptionalInitialState[BlogCommand, BlogEvent, BlogState] {
override protected def initialCommandHandler = { (ctx, cmd) ⇒
cmd match {
case AddPost(content, replyTo) ⇒
val evt = PostAdded(content.postId, content)
Effect.persist(evt).andThen { state2 ⇒
// After persist is done additional side effects can be performed
replyTo ! AddPostDone(content.postId)
}
case PassivatePost ⇒
Effect.stop
case _ ⇒
Effect.unhandled
}
}
override protected def activeCommandHandler = { (ctx, state, cmd) ⇒
cmd match {
case ChangeBody(newBody, replyTo) ⇒
val evt = BodyChanged(state.postId, newBody)
Effect.persist(evt).andThen { _ ⇒
replyTo ! Done
}
case Publish(replyTo) ⇒
Effect.persist(Published(state.postId)).andThen { _ ⇒
println(s"Blog post ${state.postId} was published")
replyTo ! Done
}
case GetPost(replyTo) ⇒
replyTo ! state.content
Effect.none
case _: AddPost ⇒
Effect.unhandled
case PassivatePost ⇒
Effect.stop
}
}
override protected def initialEventHandler = {
case PostAdded(postId, content) ⇒
BlogState(content, published = false)
}
override protected def activeEventHandler = { (state, event) ⇒
event match {
case BodyChanged(_, newBody) ⇒
state.withContent(state.content.copy(body = newBody))
case Published(_) ⇒
state.copy(published = true)
}
}
}
//#behavior
def behavior(entityId: String): Behavior[BlogCommand] = {
Behaviors.setup { ctx ⇒
val entity = new BlogPostEntity(ctx)
PersistentBehaviors.receive[BlogCommand, BlogEvent, Option[BlogState]](
persistenceId = "Blog-" + entityId,
initialState = entity.initialState,
commandHandler = entity.commandHandler,
eventHandler = entity.eventHandler)
}
}
//#behavior
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment