Merge pull request #25137 from akka/wip-24856-EventHandler-alias-patriknw

add EventHandler type alias, #24856
This commit is contained in:
Patrik Nordwall 2018-06-05 10:54:12 +02:00 committed by GitHub
commit 7f8f3c122b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 36 deletions

View file

@ -29,7 +29,7 @@ private[persistence] final class EventsourcedSetup[C, E, S](
val persistenceId: String,
val emptyState: S,
val commandHandler: PersistentBehaviors.CommandHandler[C, E, S],
val eventHandler: (S, E) S,
val eventHandler: PersistentBehaviors.EventHandler[S, E],
val writerIdentity: WriterIdentity,
val recoveryCompleted: (ActorContext[C], S) Unit,
val onSnapshot: (ActorContext[C], SnapshotMetadata, Try[Done]) Unit,

View file

@ -35,7 +35,7 @@ private[akka] final case class PersistentBehaviorImpl[Command, Event, State](
persistenceId: String,
emptyState: State,
commandHandler: PersistentBehaviors.CommandHandler[Command, Event, State],
eventHandler: (State, Event) State,
eventHandler: PersistentBehaviors.EventHandler[State, Event],
journalPluginId: Option[String] = None,
snapshotPluginId: Option[String] = None,
recoveryCompleted: (ActorContext[Command], State) Unit = ConstantFun.scalaAnyTwoToUnit,

View file

@ -16,24 +16,47 @@ import scala.util.Try
object PersistentBehaviors {
// we use this type internally, however it's easier for users to understand the function, so we use it in external api
/**
* Type alias for the command handler function for reacting on events having been persisted.
*
* The type alias is not used in API signatures because it's easier to see (in IDE) what is needed
* when full function type is used. When defining the handler as a separate function value it can
* be useful to use the alias for shorter type signature.
*/
type CommandHandler[Command, Event, State] = (ActorContext[Command], State, Command) Effect[Event, State]
/**
* Type alias for the event handler function defines how to act on commands.
*
* The type alias is not used in API signatures because it's easier to see (in IDE) what is needed
* when full function type is used. When defining the handler as a separate function value it can
* be useful to use the alias for shorter type signature.
*/
type EventHandler[State, Event] = (State, Event) State
/**
* Create a `Behavior` for a persistent actor.
*/
def receive[Command, Event, State](
persistenceId: String,
emptyState: State,
commandHandler: CommandHandler[Command, Event, State],
commandHandler: (ActorContext[Command], State, Command) Effect[Event, State],
eventHandler: (State, Event) State): PersistentBehavior[Command, Event, State] =
PersistentBehaviorImpl(persistenceId, emptyState, commandHandler, eventHandler)
/**
* The `CommandHandler` defines how to act on commands.
* The `CommandHandler` defines how to act on commands. A `CommandHandler` is
* a function:
*
* {{{
* (ActorContext[Command], State, Command) Effect[Event, State]
* }}}
*
* Note that you can have different command handlers based on current state by using
* [[CommandHandler#byState]].
*
* The [[CommandHandler#command]] is useful for simple commands that don't need the state
* and context.
*/
object CommandHandler {
@ -42,14 +65,16 @@ object PersistentBehaviors {
*
* @see [[Effect]] for possible effects of a command.
*/
def command[Command, Event, State](commandHandler: Command Effect[Event, State]): CommandHandler[Command, Event, State] =
def command[Command, Event, State](commandHandler: Command Effect[Event, State]): (ActorContext[Command], State, Command) Effect[Event, State] =
(_, _, cmd) commandHandler(cmd)
/**
* Select different command handlers based on current state.
*/
def byState[Command, Event, State](choice: State CommandHandler[Command, Event, State]): CommandHandler[Command, Event, State] =
def byState[Command, Event, State](
choice: State (ActorContext[Command], State, Command) Effect[Event, State]): (ActorContext[Command], State, Command) Effect[Event, State] = {
new ByStateCommandHandler(choice)
}
}

View file

@ -36,7 +36,7 @@ object PersistentActorCompileOnlyTest {
//#command-handler
//#event-handler
val eventHandler: (ExampleState, SimpleEvent) (ExampleState) = {
val eventHandler: (ExampleState, SimpleEvent) ExampleState = {
case (state, Evt(data)) state.copy(data :: state.events)
}
//#event-handler
@ -341,30 +341,35 @@ object PersistentActorCompileOnlyTest {
if (currentState == newMood) Effect.none
else Effect.persist(MoodChanged(newMood))
val commandHandler: CommandHandler[Command, Event, Mood] = { (_, state, cmd)
cmd match {
case Greet(whom)
println(s"Hi there, I'm $state!")
Effect.none
case CheerUp(sender)
changeMoodIfNeeded(state, Happy)
.andThen {
sender ! Ack
}
case Remember(memory)
// A more elaborate example to show we still have full control over the effects
// if needed (e.g. when some logic is factored out but you want to add more effects)
val commonEffects: Effect[Event, Mood] = changeMoodIfNeeded(state, Happy)
Effect.persist(commonEffects.events :+ Remembered(memory), commonEffects.sideEffects)
}
}
private val eventHandler: EventHandler[Mood, Event] = {
case (_, MoodChanged(to)) to
case (state, Remembered(_)) state
}
PersistentBehaviors.receive[Command, Event, Mood](
persistenceId = "myPersistenceId",
emptyState = Sad,
commandHandler = (_, state, cmd)
cmd match {
case Greet(whom)
println(s"Hi there, I'm $state!")
Effect.none
case CheerUp(sender)
changeMoodIfNeeded(state, Happy)
.andThen {
sender ! Ack
}
case Remember(memory)
// A more elaborate example to show we still have full control over the effects
// if needed (e.g. when some logic is factored out but you want to add more effects)
val commonEffects: Effect[Event, Mood] = changeMoodIfNeeded(state, Happy)
Effect.persist(commonEffects.events :+ Remembered(memory), commonEffects.sideEffects)
},
eventHandler = {
case (_, MoodChanged(to)) to
case (state, Remembered(_)) state
})
commandHandler,
eventHandler)
}

View file

@ -5,6 +5,7 @@
package docs.akka.persistence.typed
import akka.Done
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.{ ActorRef, Behavior }
import akka.persistence.typed.scaladsl.PersistentBehaviors
import akka.persistence.typed.scaladsl.PersistentBehaviors.CommandHandler
@ -52,7 +53,7 @@ object InDepthPersistentBehaviorSpec {
//#commands
//#initial-command-handler
private def initial: CommandHandler[BlogCommand, BlogEvent, BlogState] =
private val initial: (ActorContext[BlogCommand], BlogState, BlogCommand) Effect[BlogEvent, BlogState] =
(ctx, state, cmd)
cmd match {
case AddPost(content, replyTo)
@ -69,7 +70,7 @@ object InDepthPersistentBehaviorSpec {
//#initial-command-handler
//#post-added-command-handler
private def postAdded: CommandHandler[BlogCommand, BlogEvent, BlogState] = {
private val postAdded: (ActorContext[BlogCommand], BlogState, BlogCommand) Effect[BlogEvent, BlogState] = {
(ctx, state, cmd)
cmd match {
case ChangeBody(newBody, replyTo)
@ -94,14 +95,15 @@ object InDepthPersistentBehaviorSpec {
//#post-added-command-handler
//#by-state-command-handler
private def commandHandler: CommandHandler[BlogCommand, BlogEvent, BlogState] = CommandHandler.byState {
case state if state.isEmpty initial
case state if !state.isEmpty postAdded
}
private val commandHandler: (ActorContext[BlogCommand], BlogState, BlogCommand) Effect[BlogEvent, BlogState] =
CommandHandler.byState {
case state if state.isEmpty initial
case state if !state.isEmpty postAdded
}
//#by-state-command-handler
//#event-handler
private def eventHandler(state: BlogState, event: BlogEvent): BlogState =
private val eventHandler: (BlogState, BlogEvent) BlogState = { (state, event)
event match {
case PostAdded(postId, content)
state.withContent(content)
@ -115,6 +117,7 @@ object InDepthPersistentBehaviorSpec {
case Published(_)
state.copy(published = true)
}
}
//#event-handler
//#behavior