Improve builders in Java Persistentce Typed, #26109
* CommandHandlersBuilder DSL refactoring * some minor improvements * removes obsolete examples * event handlers by state (follows cmd handlers design) * fix state predicates * removes obsolete matchEvent methods * minor improvements and formatting * make it compile for 2.11.x * fixes sharding tests * promote forAnyState when applicable, improved javadoc and formatting * reformatted with new java formatter * matchAny in cmd handler builder builds the command handler * make stateClass and statePredicate private fields * build() does not reset the builder * improved scaladoc
This commit is contained in:
parent
18daa47781
commit
db4f224f4a
18 changed files with 1355 additions and 199 deletions
|
|
@ -88,7 +88,8 @@ public class ClusterShardingPersistenceTest extends JUnitSuite {
|
|||
|
||||
@Override
|
||||
public CommandHandler<Command, String, String> commandHandler() {
|
||||
return commandHandlerBuilder(String.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand(Add.class, this::add)
|
||||
.matchCommand(AddWithConfirmation.class, this::addWithConfirmation)
|
||||
.matchCommand(Get.class, this::getState)
|
||||
|
|
@ -110,7 +111,10 @@ public class ClusterShardingPersistenceTest extends JUnitSuite {
|
|||
|
||||
@Override
|
||||
public EventHandler<String, String> eventHandler() {
|
||||
return eventHandlerBuilder().matchEvent(String.class, this::applyEvent).build();
|
||||
return newEventHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchEvent(String.class, this::applyEvent)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String applyEvent(String state, String evt) {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,8 @@ public class HelloWorldPersistentEntityExample {
|
|||
|
||||
@Override
|
||||
public CommandHandler<Command, Greeted, KnownPeople> commandHandler() {
|
||||
return commandHandlerBuilder(KnownPeople.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand(Greet.class, this::greet)
|
||||
.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@
|
|||
|
||||
package akka.persistence.typed.javadsl
|
||||
|
||||
import java.util.function.BiFunction
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.{ Function ⇒ JFunction }
|
||||
import java.util.Objects
|
||||
import java.util.function.{ BiFunction, Predicate, Supplier, Function ⇒ JFunction }
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.persistence.typed.internal._
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilderByState.CommandHandlerCase
|
||||
import akka.util.OptionVal
|
||||
|
||||
import scala.compat.java8.FunctionConverters._
|
||||
|
||||
/**
|
||||
* FunctionalInterface for reacting on commands
|
||||
*
|
||||
|
|
@ -23,23 +25,155 @@ trait CommandHandler[Command, Event, State] {
|
|||
}
|
||||
|
||||
object CommandHandlerBuilder {
|
||||
def builder[Command, Event, State](): CommandHandlerBuilder[Command, Event, State] =
|
||||
new CommandHandlerBuilder[Command, Event, State]
|
||||
}
|
||||
|
||||
final class CommandHandlerBuilder[Command, Event, State]() {
|
||||
|
||||
private var builders: List[CommandHandlerBuilderByState[Command, Event, State, State]] = Nil
|
||||
|
||||
/**
|
||||
* Use this method to define command handlers that are selected when the passed predicate holds true.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`
|
||||
*
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def forState(statePredicate: Predicate[State]): CommandHandlerBuilderByState[Command, Event, State, State] = {
|
||||
val builder = CommandHandlerBuilderByState.builder[Command, Event, State](statePredicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to define command handlers that are selected when the passed predicate holds true
|
||||
* for a given subtype of your model. Useful when the model is defined as class hierarchy.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`
|
||||
*
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def forState[S <: State](stateClass: Class[S], statePredicate: Predicate[S]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
val builder = new CommandHandlerBuilderByState[Command, Event, S, State](stateClass, statePredicate)
|
||||
builders = builder.asInstanceOf[CommandHandlerBuilderByState[Command, Event, State, State]] :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to define command handlers for a given subtype of your model. Useful when the model is defined as class hierarchy.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`.
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def forStateType[S <: State](stateClass: Class[S]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
val builder = CommandHandlerBuilderByState.builder[Command, Event, S, State](stateClass)
|
||||
builders = builder.asInstanceOf[CommandHandlerBuilderByState[Command, Event, State, State]] :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* The handlers defined by this builder are used when the state is `null`.
|
||||
* This variant is particular useful when the empty state of your model is defined as `null`.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def forNullState(): CommandHandlerBuilderByState[Command, Event, State, State] = {
|
||||
val predicate: Predicate[State] = asJavaPredicate(s ⇒ Objects.isNull(s))
|
||||
val builder = CommandHandlerBuilderByState.builder[Command, Event, State](predicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* The handlers defined by this builder are used for any not `null` state.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def forNonNullState(): CommandHandlerBuilderByState[Command, Event, State, State] = {
|
||||
val predicate: Predicate[State] = asJavaPredicate(s ⇒ Objects.nonNull(s))
|
||||
val builder = CommandHandlerBuilderByState.builder[Command, Event, State](predicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* The handlers defined by this builder are used for any state.
|
||||
* This variant is particular useful for models that have a single type (ie: no class hierarchy).
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
* Extra care should be taken when using [[forAnyState]] as it will match any state. Any command handler define after it will never be reached.
|
||||
*
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def forAnyState(): CommandHandlerBuilderByState[Command, Event, State, State] = {
|
||||
val predicate: Predicate[State] = asJavaPredicate(_ ⇒ true)
|
||||
val builder = CommandHandlerBuilderByState.builder[Command, Event, State](predicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
def build(): CommandHandler[Command, Event, State] = {
|
||||
|
||||
val combined =
|
||||
builders.reverse match {
|
||||
case head :: Nil ⇒ head
|
||||
case head :: tail ⇒ tail.foldLeft(head) { (acc, builder) ⇒
|
||||
acc.orElse(builder)
|
||||
}
|
||||
case Nil ⇒ throw new IllegalStateException("No matchers defined")
|
||||
}
|
||||
|
||||
combined.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object CommandHandlerBuilderByState {
|
||||
|
||||
private val _trueStatePredicate: Predicate[Any] = new Predicate[Any] {
|
||||
override def test(t: Any): Boolean = true
|
||||
}
|
||||
|
||||
private def trueStatePredicate[S]: Predicate[S] = _trueStatePredicate.asInstanceOf[Predicate[S]]
|
||||
|
||||
/**
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`
|
||||
* @return A new, mutable, command handler builder
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def builder[Command, Event, S <: State, State](stateClass: Class[S]): CommandHandlerBuilder[Command, Event, S, State] =
|
||||
new CommandHandlerBuilder(statePredicate = new Predicate[S] {
|
||||
override def test(state: S): Boolean = state != null && stateClass.isAssignableFrom(state.getClass)
|
||||
})
|
||||
def builder[Command, Event, S <: State, State](stateClass: Class[S]): CommandHandlerBuilderByState[Command, Event, S, State] =
|
||||
new CommandHandlerBuilderByState(stateClass, statePredicate = trueStatePredicate)
|
||||
|
||||
/**
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`,
|
||||
* useful for example when state type is an Optional
|
||||
* @return A new, mutable, command handler builder
|
||||
* @return A new, mutable, CommandHandlerBuilderByState
|
||||
*/
|
||||
def builder[Command, Event, State](statePredicate: Predicate[State]): CommandHandlerBuilder[Command, Event, State, State] =
|
||||
new CommandHandlerBuilder(statePredicate)
|
||||
def builder[Command, Event, State](statePredicate: Predicate[State]): CommandHandlerBuilderByState[Command, Event, State, State] =
|
||||
new CommandHandlerBuilderByState(classOf[Any].asInstanceOf[Class[State]], statePredicate)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
|
|
@ -50,34 +184,45 @@ object CommandHandlerBuilder {
|
|||
handler: BiFunction[State, Command, Effect[Event, State]])
|
||||
}
|
||||
|
||||
final class CommandHandlerBuilder[Command, Event, S <: State, State] @InternalApi private[persistence] (
|
||||
val statePredicate: Predicate[S]) {
|
||||
import CommandHandlerBuilder.CommandHandlerCase
|
||||
final class CommandHandlerBuilderByState[Command, Event, S <: State, State] @InternalApi private[persistence] (
|
||||
private val stateClass: Class[S], private val statePredicate: Predicate[S]) {
|
||||
|
||||
import CommandHandlerBuilderByState.CommandHandlerCase
|
||||
|
||||
private var cases: List[CommandHandlerCase[Command, Event, State]] = Nil
|
||||
|
||||
private def addCase(predicate: Command ⇒ Boolean, handler: BiFunction[S, Command, Effect[Event, State]]): Unit = {
|
||||
cases = CommandHandlerCase[Command, Event, State](
|
||||
commandPredicate = predicate,
|
||||
statePredicate = state ⇒ statePredicate.test(state.asInstanceOf[S]),
|
||||
statePredicate = state ⇒
|
||||
if (state == null) statePredicate.test(state.asInstanceOf[S])
|
||||
else statePredicate.test(state.asInstanceOf[S]) && stateClass.isAssignableFrom(state.getClass),
|
||||
handler.asInstanceOf[BiFunction[State, Command, Effect[Event, State]]]) :: cases
|
||||
}
|
||||
|
||||
/**
|
||||
* Match any command which the given `predicate` returns true for
|
||||
* Matches any command which the given `predicate` returns true for.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*/
|
||||
def matchCommand(predicate: Predicate[Command], handler: BiFunction[S, Command, Effect[Event, State]]): CommandHandlerBuilder[Command, Event, S, State] = {
|
||||
def matchCommand(predicate: Predicate[Command], handler: BiFunction[S, Command, Effect[Event, State]]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
addCase(cmd ⇒ predicate.test(cmd), handler)
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* Match any command which the given `predicate` returns true for.
|
||||
* Matches any command which the given `predicate` returns true for.
|
||||
*
|
||||
* Use this when then `State` is not needed in the `handler`, otherwise there is an overloaded method that pass
|
||||
* Use this when the `State` is not needed in the `handler`, otherwise there is an overloaded method that pass
|
||||
* the state in a `BiFunction`.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*/
|
||||
def matchCommand(predicate: Predicate[Command], handler: JFunction[Command, Effect[Event, State]]): CommandHandlerBuilder[Command, Event, S, State] = {
|
||||
def matchCommand(predicate: Predicate[Command], handler: JFunction[Command, Effect[Event, State]]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
addCase(cmd ⇒ predicate.test(cmd), new BiFunction[S, Command, Effect[Event, State]] {
|
||||
override def apply(state: S, cmd: Command): Effect[Event, State] = handler(cmd)
|
||||
})
|
||||
|
|
@ -85,42 +230,133 @@ final class CommandHandlerBuilder[Command, Event, S <: State, State] @InternalAp
|
|||
}
|
||||
|
||||
/**
|
||||
* Match commands that are of the given `commandClass` or subclass thereof
|
||||
* Matches commands that are of the given `commandClass` or subclass thereof
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*/
|
||||
def matchCommand[C <: Command](commandClass: Class[C], handler: BiFunction[S, C, Effect[Event, State]]): CommandHandlerBuilder[Command, Event, S, State] = {
|
||||
def matchCommand[C <: Command](commandClass: Class[C], handler: BiFunction[S, C, Effect[Event, State]]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
addCase(cmd ⇒ commandClass.isAssignableFrom(cmd.getClass), handler.asInstanceOf[BiFunction[S, Command, Effect[Event, State]]])
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* Match commands that are of the given `commandClass` or subclass thereof.
|
||||
* Matches commands that are of the given `commandClass` or subclass thereof.
|
||||
*
|
||||
* Use this when then `State` is not needed in the `handler`, otherwise there is an overloaded method that pass
|
||||
* Use this when the `State` is not needed in the `handler`, otherwise there is an overloaded method that pass
|
||||
* the state in a `BiFunction`.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*/
|
||||
def matchCommand[C <: Command](commandClass: Class[C], handler: JFunction[C, Effect[Event, State]]): CommandHandlerBuilder[Command, Event, S, State] = {
|
||||
def matchCommand[C <: Command](commandClass: Class[C], handler: JFunction[C, Effect[Event, State]]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
matchCommand[C](commandClass, new BiFunction[S, C, Effect[Event, State]] {
|
||||
override def apply(state: S, cmd: C): Effect[Event, State] = handler(cmd)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches commands that are of the given `commandClass` or subclass thereof.
|
||||
*
|
||||
* Use this when you just need to initialize the `State` without using any data from the command.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*/
|
||||
def matchCommand[C <: Command](commandClass: Class[C], handler: Supplier[Effect[Event, State]]): CommandHandlerBuilderByState[Command, Event, S, State] = {
|
||||
matchCommand[C](commandClass, new BiFunction[S, C, Effect[Event, State]] {
|
||||
override def apply(state: S, cmd: C): Effect[Event, State] = handler.get()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches any command.
|
||||
*
|
||||
* Use this to declare a command handler that will match any command. This is particular useful when encoding
|
||||
* a finite state machine in which the final state is not supposed to handle any new command.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* Extra care should be taken when using [[matchAny]] as it will match any command.
|
||||
* This method builds and returns the command handler since this will not let through any states to subsequent match statements.
|
||||
*
|
||||
* @return A CommandHandler from the appended states.
|
||||
*/
|
||||
def matchAny(handler: BiFunction[S, Command, Effect[Event, State]]): CommandHandler[Command, Event, State] = {
|
||||
addCase(_ ⇒ true, handler)
|
||||
build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches any command.
|
||||
*
|
||||
* Use this to declare a command handler that will match any command. This is particular useful when encoding
|
||||
* a finite state machine in which the final state is not supposed to handle any new command.
|
||||
*
|
||||
* Use this when you just need to return an [[Effect]] without using any data from the state.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* Extra care should be taken when using [[matchAny]] as it will match any command.
|
||||
* This method builds and returns the command handler since this will not let through any states to subsequent match statements.
|
||||
*
|
||||
* @return A CommandHandler from the appended states.
|
||||
*/
|
||||
def matchAny(handler: JFunction[Command, Effect[Event, State]]): CommandHandler[Command, Event, State] = {
|
||||
addCase(_ ⇒ true, new BiFunction[S, Command, Effect[Event, State]] {
|
||||
override def apply(state: S, cmd: Command): Effect[Event, State] = handler(cmd)
|
||||
})
|
||||
build()
|
||||
}
|
||||
/**
|
||||
* Matches any command.
|
||||
*
|
||||
* Use this to declare a command handler that will match any command. This is particular useful when encoding
|
||||
* a finite state machine in which the final state is not supposed to handle any new command.
|
||||
*
|
||||
* Use this when you just need to return an [[Effect]] without using any data from the command or from the state.
|
||||
*
|
||||
* Note: command handlers are matched in the order they are added. Once a matching is found, it's selected for handling the command
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your command handlers.
|
||||
*
|
||||
* Extra care should be taken when using [[matchAny]] as it will match any command.
|
||||
* This method builds and returns the command handler since this will not let through any states to subsequent match statements.
|
||||
*
|
||||
* @return A CommandHandler from the appended states.
|
||||
*/
|
||||
def matchAny(handler: Supplier[Effect[Event, State]]): CommandHandler[Command, Event, State] = {
|
||||
addCase(_ ⇒ true, new BiFunction[S, Command, Effect[Event, State]] {
|
||||
override def apply(state: S, cmd: Command): Effect[Event, State] = handler.get()
|
||||
})
|
||||
build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose this builder with another builder. The handlers in this builder will be tried first followed
|
||||
* by the handlers in `other`.
|
||||
*/
|
||||
def orElse[S2 <: State](other: CommandHandlerBuilder[Command, Event, S2, State]): CommandHandlerBuilder[Command, Event, S2, State] = {
|
||||
val newBuilder = new CommandHandlerBuilder[Command, Event, S2, State](other.statePredicate)
|
||||
def orElse[S2 <: State](other: CommandHandlerBuilderByState[Command, Event, S2, State]): CommandHandlerBuilderByState[Command, Event, S2, State] = {
|
||||
val newBuilder = new CommandHandlerBuilderByState[Command, Event, S2, State](other.stateClass, other.statePredicate)
|
||||
// problem with overloaded constructor with `cases` as parameter
|
||||
newBuilder.cases = other.cases ::: cases
|
||||
newBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Command Handler and resets this builder
|
||||
* Builds and returns a handler from the appended states. The returned [[CommandHandler]] will throw a [[scala.MatchError]]
|
||||
* if applied to a command that has no defined case.
|
||||
*/
|
||||
def build(): CommandHandler[Command, Event, State] = {
|
||||
val builtCases = cases.reverse.toArray
|
||||
cases = Nil
|
||||
|
||||
new CommandHandler[Command, Event, State] {
|
||||
override def apply(state: State, command: Command): Effect[Event, State] = {
|
||||
var idx = 0
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
package akka.persistence.typed.javadsl
|
||||
|
||||
import java.util.function.BiFunction
|
||||
import java.util.function.{ Function ⇒ JFunction }
|
||||
import java.util.Objects
|
||||
import java.util.function.{ BiFunction, Predicate, Supplier, Function ⇒ JFunction }
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.util.OptionVal
|
||||
|
||||
import scala.compat.java8.FunctionConverters._
|
||||
|
||||
/**
|
||||
* FunctionalInterface for reacting on events having been persisted
|
||||
*
|
||||
|
|
@ -24,6 +26,155 @@ object EventHandlerBuilder {
|
|||
def builder[State >: Null, Event](): EventHandlerBuilder[State, Event] =
|
||||
new EventHandlerBuilder[State, Event]()
|
||||
|
||||
}
|
||||
|
||||
final class EventHandlerBuilder[State >: Null, Event]() {
|
||||
|
||||
private var builders: List[EventHandlerBuilderByState[State, State, Event]] = Nil
|
||||
|
||||
/**
|
||||
* Use this method to define event handlers that are selected when the passed predicate holds true.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`
|
||||
*
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def forState(statePredicate: Predicate[State]): EventHandlerBuilderByState[State, State, Event] = {
|
||||
val builder = EventHandlerBuilderByState.builder[State, Event](statePredicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to define event handlers that are selected when the passed predicate holds true
|
||||
* for a given subtype of your model. Useful when the model is defined as class hierarchy.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`
|
||||
*
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def forState[S <: State](stateClass: Class[S], statePredicate: Predicate[S]): EventHandlerBuilderByState[S, State, Event] = {
|
||||
val builder = new EventHandlerBuilderByState[S, State, Event](stateClass, statePredicate)
|
||||
builders = builder.asInstanceOf[EventHandlerBuilderByState[State, State, Event]] :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to define command handlers for a given subtype of your model. Useful when the model is defined as class hierarchy.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`
|
||||
*
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def forStateType[S <: State](stateClass: Class[S]): EventHandlerBuilderByState[S, State, Event] = {
|
||||
val builder = EventHandlerBuilderByState.builder[S, State, Event](stateClass)
|
||||
builders = builder.asInstanceOf[EventHandlerBuilderByState[State, State, Event]] :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* The handlers defined by this builder are used when the state is `null`.
|
||||
* This variant is particular useful when the empty state of your model is defined as `null`.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def forNullState(): EventHandlerBuilderByState[State, State, Event] = {
|
||||
val predicate: Predicate[State] = asJavaPredicate(s ⇒ Objects.isNull(s))
|
||||
val builder = EventHandlerBuilderByState.builder[State, Event](predicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* The handlers defined by this builder are used for any not `null` state.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def forNonNullState(): EventHandlerBuilderByState[State, State, Event] = {
|
||||
val predicate: Predicate[State] = asJavaPredicate(s ⇒ Objects.nonNull(s))
|
||||
val builder = EventHandlerBuilderByState.builder[State, Event](predicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
/**
|
||||
* The handlers defined by this builder are used for any state.
|
||||
* This variant is particular useful for models that have a single type (ie: no class hierarchy).
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
* Extra care should be taken when using [[forAnyState]] as it will match any state. Any event handler define after it will never be reached.
|
||||
*
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def forAnyState(): EventHandlerBuilderByState[State, State, Event] = {
|
||||
val predicate: Predicate[State] = asJavaPredicate(_ ⇒ true)
|
||||
val builder = EventHandlerBuilderByState.builder[State, Event](predicate)
|
||||
builders = builder :: builders
|
||||
builder
|
||||
}
|
||||
|
||||
def build(): EventHandler[State, Event] = {
|
||||
|
||||
val combined =
|
||||
builders.reverse match {
|
||||
case head :: Nil ⇒ head
|
||||
case head :: tail ⇒ tail.foldLeft(head) { (acc, builder) ⇒
|
||||
acc.orElse(builder)
|
||||
}
|
||||
case Nil ⇒ throw new IllegalStateException("No matchers defined")
|
||||
}
|
||||
|
||||
combined.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object EventHandlerBuilderByState {
|
||||
|
||||
private val _trueStatePredicate: Predicate[Any] = new Predicate[Any] {
|
||||
override def test(t: Any): Boolean = true
|
||||
}
|
||||
|
||||
private def trueStatePredicate[S]: Predicate[S] = _trueStatePredicate.asInstanceOf[Predicate[S]]
|
||||
|
||||
/**
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def builder[S <: State, State >: Null, Event](stateClass: Class[S]): EventHandlerBuilderByState[S, State, Event] =
|
||||
new EventHandlerBuilderByState(stateClass, statePredicate = trueStatePredicate)
|
||||
|
||||
/**
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`,
|
||||
* useful for example when state type is an Optional
|
||||
* @return A new, mutable, EventHandlerBuilderByState
|
||||
*/
|
||||
def builder[State >: Null, Event](statePredicate: Predicate[State]): EventHandlerBuilderByState[State, State, Event] =
|
||||
new EventHandlerBuilderByState(classOf[Any].asInstanceOf[Class[State]], statePredicate)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
|
|
@ -33,20 +184,30 @@ object EventHandlerBuilder {
|
|||
handler: BiFunction[State, Event, State])
|
||||
}
|
||||
|
||||
final class EventHandlerBuilder[State >: Null, Event]() {
|
||||
import EventHandlerBuilder.EventHandlerCase
|
||||
final class EventHandlerBuilderByState[S <: State, State >: Null, Event](private val stateClass: Class[S], private val statePredicate: Predicate[S]) {
|
||||
|
||||
import EventHandlerBuilderByState.EventHandlerCase
|
||||
|
||||
private var cases: List[EventHandlerCase[State, Event]] = Nil
|
||||
|
||||
private def addCase(eventPredicate: Event ⇒ Boolean, handler: BiFunction[State, Event, State]): Unit = {
|
||||
cases = EventHandlerCase[State, Event](_ ⇒ true, eventPredicate, handler) :: cases
|
||||
cases = EventHandlerCase[State, Event](
|
||||
statePredicate = state ⇒
|
||||
if (state == null) statePredicate.test(state.asInstanceOf[S])
|
||||
else statePredicate.test(state.asInstanceOf[S]) && stateClass.isAssignableFrom(state.getClass),
|
||||
eventPredicate = eventPredicate,
|
||||
handler) :: cases
|
||||
}
|
||||
|
||||
/**
|
||||
* Match any event which is an instance of `E` or a subtype of `E`
|
||||
* Match any event which is an instance of `E` or a subtype of `E`.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*/
|
||||
def matchEvent[E <: Event](eventClass: Class[E], biFunction: BiFunction[State, E, State]): EventHandlerBuilder[State, Event] = {
|
||||
addCase(e ⇒ eventClass.isAssignableFrom(e.getClass), biFunction.asInstanceOf[BiFunction[State, Event, State]])
|
||||
def matchEvent[E <: Event](eventClass: Class[E], handler: BiFunction[S, E, State]): EventHandlerBuilderByState[S, State, Event] = {
|
||||
addCase(e ⇒ eventClass.isAssignableFrom(e.getClass), handler.asInstanceOf[BiFunction[State, Event, State]])
|
||||
this
|
||||
}
|
||||
|
||||
|
|
@ -55,30 +216,49 @@ final class EventHandlerBuilder[State >: Null, Event]() {
|
|||
*
|
||||
* Use this when then `State` is not needed in the `handler`, otherwise there is an overloaded method that pass
|
||||
* the state in a `BiFunction`.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*/
|
||||
def matchEvent[E <: Event](eventClass: Class[E], f: JFunction[E, State]): EventHandlerBuilder[State, Event] = {
|
||||
matchEvent[E](eventClass, new BiFunction[State, E, State] {
|
||||
override def apply(state: State, event: E): State = f(event)
|
||||
def matchEvent[E <: Event](eventClass: Class[E], handler: JFunction[E, State]): EventHandlerBuilderByState[S, State, Event] = {
|
||||
matchEvent[E](eventClass, new BiFunction[S, E, State] {
|
||||
override def apply(state: S, event: E): State = handler(event)
|
||||
})
|
||||
}
|
||||
|
||||
def matchEvent[E <: Event, S <: State](eventClass: Class[E], stateClass: Class[S],
|
||||
biFunction: BiFunction[S, E, State]): EventHandlerBuilder[State, Event] = {
|
||||
/**
|
||||
* Match any event which is an instance of `E` or a subtype of `E`.
|
||||
*
|
||||
* Use this when then `State` and the `Event` are not needed in the `handler`.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*/
|
||||
def matchEvent[E <: Event](eventClass: Class[E], handler: Supplier[State]): EventHandlerBuilderByState[S, State, Event] = {
|
||||
|
||||
cases = EventHandlerCase[State, Event](
|
||||
statePredicate = s ⇒ s != null && stateClass.isAssignableFrom(s.getClass),
|
||||
eventPredicate = e ⇒ eventClass.isAssignableFrom(e.getClass),
|
||||
biFunction.asInstanceOf[BiFunction[State, Event, State]]) :: cases
|
||||
this
|
||||
val supplierBiFunction = new BiFunction[S, E, State] {
|
||||
def apply(t: S, u: E): State = handler.get()
|
||||
}
|
||||
|
||||
matchEvent(eventClass, supplierBiFunction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Match any event
|
||||
* Match any event.
|
||||
*
|
||||
* Builds and returns the handler since this will not let through any states to subsequent match statements
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* Extra care should be taken when using [[matchAny]] as it will match any event.
|
||||
* This method builds and returns the event handler since this will not let through any states to subsequent match statements.
|
||||
*
|
||||
* @return An EventHandler from the appended states.
|
||||
*/
|
||||
def matchAny(biFunction: BiFunction[State, Event, State]): EventHandler[State, Event] = {
|
||||
addCase(_ ⇒ true, biFunction.asInstanceOf[BiFunction[State, Event, State]])
|
||||
def matchAny(handler: BiFunction[State, Event, State]): EventHandler[State, Event] = {
|
||||
addCase(_ ⇒ true, handler.asInstanceOf[BiFunction[State, Event, State]])
|
||||
build()
|
||||
}
|
||||
|
||||
|
|
@ -87,10 +267,19 @@ final class EventHandlerBuilder[State >: Null, Event]() {
|
|||
*
|
||||
* Use this when then `State` is not needed in the `handler`, otherwise there is an overloaded method that pass
|
||||
* the state in a `BiFunction`.
|
||||
*
|
||||
* Note: event handlers are matched in the order they are added. Once a matching is found, it's selected for handling the event
|
||||
* and no further lookup is done. Therefore you must make sure that their matching conditions don't overlap,
|
||||
* otherwise you risk to 'shadow' part of your event handlers.
|
||||
*
|
||||
* Extra care should be taken when using [[matchAny]] as it will match any event.
|
||||
* This method builds and returns the event handler since this will not let through any states to subsequent match statements.
|
||||
*
|
||||
* @return An EventHandler from the appended states.
|
||||
*/
|
||||
def matchAny(f: JFunction[Event, State]): EventHandler[State, Event] = {
|
||||
def matchAny(handler: JFunction[Event, State]): EventHandler[State, Event] = {
|
||||
matchAny(new BiFunction[State, Event, State] {
|
||||
override def apply(state: State, event: Event): State = f(event)
|
||||
override def apply(state: State, event: Event): State = handler(event)
|
||||
})
|
||||
build()
|
||||
}
|
||||
|
|
@ -99,8 +288,8 @@ final class EventHandlerBuilder[State >: Null, Event]() {
|
|||
* Compose this builder with another builder. The handlers in this builder will be tried first followed
|
||||
* by the handlers in `other`.
|
||||
*/
|
||||
def orElse(other: EventHandlerBuilder[State, Event]): EventHandlerBuilder[State, Event] = {
|
||||
val newBuilder = new EventHandlerBuilder[State, Event]
|
||||
def orElse[S2 <: State](other: EventHandlerBuilderByState[S2, State, Event]): EventHandlerBuilderByState[S2, State, Event] = {
|
||||
val newBuilder = new EventHandlerBuilderByState[S2, State, Event](other.stateClass, other.statePredicate)
|
||||
// problem with overloaded constructor with `cases` as parameter
|
||||
newBuilder.cases = other.cases ::: cases
|
||||
newBuilder
|
||||
|
|
@ -109,8 +298,6 @@ final class EventHandlerBuilder[State >: Null, Event]() {
|
|||
/**
|
||||
* Builds and returns a handler from the appended states. The returned [[EventHandler]] will throw a [[scala.MatchError]]
|
||||
* if applied to an event that has no defined case.
|
||||
*
|
||||
* The builder is reset to empty after build has been called.
|
||||
*/
|
||||
def build(): EventHandler[State, Event] = {
|
||||
val builtCases = cases.reverse.toArray
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
package akka.persistence.typed.javadsl
|
||||
|
||||
import java.util.function.Predicate
|
||||
import java.util.{ Collections, Optional }
|
||||
|
||||
import akka.actor.typed
|
||||
|
|
@ -65,25 +64,14 @@ abstract class EventSourcedBehavior[Command, Event, State >: Null] private[akka]
|
|||
*/
|
||||
protected def eventHandler(): EventHandler[State, Event]
|
||||
|
||||
/**
|
||||
* @param stateClass The handlers defined by this builder are used when the state is an instance of the `stateClass`
|
||||
* @return A new, mutable, command handler builder
|
||||
*/
|
||||
protected final def commandHandlerBuilder[S <: State](stateClass: Class[S]): CommandHandlerBuilder[Command, Event, S, State] =
|
||||
CommandHandlerBuilder.builder[Command, Event, S, State](stateClass)
|
||||
|
||||
/**
|
||||
* @param statePredicate The handlers defined by this builder are used when the `statePredicate` is `true`,
|
||||
* * useful for example when state type is an Optional
|
||||
* @return A new, mutable, command handler builder
|
||||
*/
|
||||
protected final def commandHandlerBuilder(statePredicate: Predicate[State]): CommandHandlerBuilder[Command, Event, State, State] =
|
||||
CommandHandlerBuilder.builder[Command, Event, State](statePredicate)
|
||||
protected final def newCommandHandlerBuilder(): CommandHandlerBuilder[Command, Event, State] = {
|
||||
CommandHandlerBuilder.builder[Command, Event, State]()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new, mutable, event handler builder
|
||||
*/
|
||||
protected final def eventHandlerBuilder(): EventHandlerBuilder[State, Event] =
|
||||
protected final def newEventHandlerBuilder(): EventHandlerBuilder[State, Event] =
|
||||
EventHandlerBuilder.builder[State, Event]()
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ import org.junit.ClassRule;
|
|||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class NullEmptyStateTest extends JUnitSuite {
|
||||
|
||||
private static final Config config =
|
||||
|
|
@ -47,17 +45,12 @@ public class NullEmptyStateTest extends JUnitSuite {
|
|||
|
||||
@Override
|
||||
public CommandHandler<String, String, String> commandHandler() {
|
||||
CommandHandlerBuilder<String, String, String, String> b1 =
|
||||
commandHandlerBuilder(Objects::isNull)
|
||||
.matchCommand("stop"::equals, command -> Effect().stop())
|
||||
.matchCommand(String.class, this::persistCommand);
|
||||
|
||||
CommandHandlerBuilder<String, String, String, String> b2 =
|
||||
commandHandlerBuilder(String.class)
|
||||
.matchCommand("stop"::equals, command -> Effect().stop())
|
||||
.matchCommand(String.class, this::persistCommand);
|
||||
|
||||
return b1.orElse(b2).build();
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand("stop"::equals, command -> Effect().stop())
|
||||
.matchCommand(String.class, this::persistCommand)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Effect<String, String> persistCommand(String command) {
|
||||
|
|
@ -66,7 +59,10 @@ public class NullEmptyStateTest extends JUnitSuite {
|
|||
|
||||
@Override
|
||||
public EventHandler<String, String> eventHandler() {
|
||||
return eventHandlerBuilder().matchEvent(String.class, this::applyEvent).build();
|
||||
return newEventHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchEvent(String.class, this::applyEvent)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String applyEvent(String state, String event) {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,8 @@ public class PersistentActorCompileOnlyTest {
|
|||
public CommandHandler<MyCommand, MyEvent, ExampleState> commandHandler() {
|
||||
|
||||
// #commonChainedEffects
|
||||
return commandHandlerBuilder(ExampleState.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(ExampleState.class)
|
||||
.matchCommand(
|
||||
Cmd.class,
|
||||
(state, cmd) ->
|
||||
|
|
@ -187,7 +188,8 @@ public class PersistentActorCompileOnlyTest {
|
|||
|
||||
@Override
|
||||
public EventHandler<ExampleState, MyEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
return newEventHandlerBuilder()
|
||||
.forStateType(ExampleState.class)
|
||||
.matchEvent(
|
||||
Evt.class,
|
||||
(state, event) -> {
|
||||
|
|
@ -312,7 +314,8 @@ public class PersistentActorCompileOnlyTest {
|
|||
|
||||
@Override
|
||||
public CommandHandler<Command, Event, EventsInFlight> commandHandler() {
|
||||
return commandHandlerBuilder(EventsInFlight.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand(
|
||||
DoSideEffect.class,
|
||||
(state, cmd) ->
|
||||
|
|
@ -334,7 +337,8 @@ public class PersistentActorCompileOnlyTest {
|
|||
|
||||
@Override
|
||||
public EventHandler<EventsInFlight, Event> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
return newEventHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchEvent(
|
||||
IntentRecord.class,
|
||||
(state, event) -> {
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ public class PersistentActorJavaDslTest extends JUnitSuite {
|
|||
|
||||
@Override
|
||||
public CommandHandler<Command, Incremented, State> commandHandler() {
|
||||
return commandHandlerBuilder(State.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand(Increment.class, this::increment)
|
||||
.matchCommand(IncrementWithConfirmation.class, this::incrementWithConfirmation)
|
||||
.matchCommand(GetValue.class, this::getValue)
|
||||
|
|
@ -269,7 +270,10 @@ public class PersistentActorJavaDslTest extends JUnitSuite {
|
|||
|
||||
@Override
|
||||
public EventHandler<State, Incremented> eventHandler() {
|
||||
return eventHandlerBuilder().matchEvent(Incremented.class, this::applyIncremented).build();
|
||||
return newEventHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchEvent(Incremented.class, this::applyIncremented)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -454,12 +458,6 @@ public class PersistentActorJavaDslTest extends JUnitSuite {
|
|||
TestProbe<Signal> signalProbe = testKit.createTestProbe();
|
||||
BehaviorInterceptor<Command, Command> tap =
|
||||
new BehaviorInterceptor<Command, Command>() {
|
||||
|
||||
@Override
|
||||
public Class<Command> interceptMessageType() {
|
||||
return Command.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior<Command> aroundReceive(
|
||||
TypedActorContext<Command> ctx, Command msg, ReceiveTarget<Command> target) {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import akka.actor.typed.Behavior;
|
|||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehavior;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class AccountExample
|
||||
extends EventSourcedBehavior<
|
||||
|
|
@ -88,15 +85,17 @@ public class AccountExample
|
|||
return new EmptyAccount();
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<AccountCommand, AccountEvent, EmptyAccount, Account>
|
||||
initialHandler() {
|
||||
return commandHandlerBuilder(EmptyAccount.class)
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, EmptyAccount, Account>
|
||||
initialCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchCommand(CreateAccount.class, (__, cmd) -> Effect().persist(new AccountCreated()));
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<AccountCommand, AccountEvent, OpenedAccount, Account>
|
||||
openedAccountHandler() {
|
||||
return commandHandlerBuilder(OpenedAccount.class)
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, OpenedAccount, Account>
|
||||
openedAccountCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, (__, cmd) -> Effect().persist(new Deposited(cmd.amount)))
|
||||
.matchCommand(
|
||||
Withdraw.class,
|
||||
|
|
@ -123,30 +122,35 @@ public class AccountExample
|
|||
});
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<AccountCommand, AccountEvent, ClosedAccount, Account>
|
||||
closedHandler() {
|
||||
return commandHandlerBuilder(ClosedAccount.class)
|
||||
.matchCommand(AccountCommand.class, (__, ___) -> Effect().unhandled());
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, ClosedAccount, Account>
|
||||
closedCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(ClosedAccount.class)
|
||||
.matchCommand(AccountCommand.class, __ -> Effect().unhandled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<AccountCommand, AccountEvent, Account> commandHandler() {
|
||||
return initialHandler().orElse(openedAccountHandler()).orElse(closedHandler()).build();
|
||||
return initialCmdHandler().orElse(openedAccountCmdHandler()).orElse(closedCmdHandler()).build();
|
||||
}
|
||||
|
||||
private EventHandlerBuilderByState<EmptyAccount, Account, AccountEvent> initialEvtHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchEvent(AccountCreated.class, () -> new OpenedAccount(0.0));
|
||||
}
|
||||
|
||||
private EventHandlerBuilderByState<OpenedAccount, Account, AccountEvent>
|
||||
openedAccountEvtHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(Deposited.class, (acc, cmd) -> new OpenedAccount(acc.balance + cmd.amount))
|
||||
.matchEvent(Withdrawn.class, (acc, cmd) -> new OpenedAccount(acc.balance - cmd.amount))
|
||||
.matchEvent(AccountClosed.class, ClosedAccount::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
.matchEvent(AccountCreated.class, EmptyAccount.class, (__, ___) -> new OpenedAccount(0.0))
|
||||
.matchEvent(
|
||||
Deposited.class,
|
||||
OpenedAccount.class,
|
||||
(acc, cmd) -> new OpenedAccount(acc.balance + cmd.amount))
|
||||
.matchEvent(
|
||||
Withdrawn.class,
|
||||
OpenedAccount.class,
|
||||
(acc, cmd) -> new OpenedAccount(acc.balance - cmd.amount))
|
||||
.matchEvent(AccountClosed.class, OpenedAccount.class, (acc, cmd) -> new ClosedAccount())
|
||||
.build();
|
||||
return initialEvtHandler().orElse(openedAccountEvtHandler()).build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class AccountExampleOneLiners
|
||||
extends EventSourcedBehavior<
|
||||
AccountExampleOneLiners.AccountCommand,
|
||||
AccountExampleOneLiners.AccountEvent,
|
||||
AccountExampleOneLiners.Account> {
|
||||
|
||||
interface AccountCommand {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand {}
|
||||
|
||||
public static class Deposit implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Deposit(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Withdraw(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand {}
|
||||
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Deposited(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Withdrawn(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
interface Account {}
|
||||
|
||||
public static class EmptyAccount implements Account {}
|
||||
|
||||
public static class OpenedAccount implements Account {
|
||||
public final double balance;
|
||||
|
||||
OpenedAccount(double balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountExampleOneLiners(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountExampleOneLiners(ActorContext<AccountCommand> context, String accountNumber) {
|
||||
super(new PersistenceId(accountNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account emptyState() {
|
||||
return new EmptyAccount();
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> createAccount() {
|
||||
return Effect().persist(new AccountCreated());
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> depositCommand(Deposit deposit) {
|
||||
return Effect().persist(new Deposited(deposit.amount));
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> withdrawCommand(OpenedAccount account, Withdraw withdraw) {
|
||||
if ((account.balance - withdraw.amount) < 0.0) {
|
||||
return Effect().unhandled(); // TODO replies are missing in this example
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(withdraw.amount))
|
||||
.thenRun(
|
||||
acc2 -> {
|
||||
// we know this cast is safe, but somewhat ugly
|
||||
OpenedAccount openAccount = (OpenedAccount) acc2;
|
||||
// do some side-effect using balance
|
||||
System.out.println(openAccount.balance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> closeCommand(OpenedAccount account, CloseAccount cmd) {
|
||||
if (account.balance == 0.0) return Effect().persist(new AccountClosed());
|
||||
else return Effect().unhandled();
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, EmptyAccount, Account>
|
||||
initialCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchCommand(CreateAccount.class, this::createAccount);
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, OpenedAccount, Account>
|
||||
openedAccountCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, this::depositCommand)
|
||||
.matchCommand(Withdraw.class, this::withdrawCommand)
|
||||
.matchCommand(CloseAccount.class, this::closeCommand);
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, ClosedAccount, Account>
|
||||
closedCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(ClosedAccount.class)
|
||||
.matchCommand(AccountCommand.class, __ -> Effect().unhandled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<AccountCommand, AccountEvent, Account> commandHandler() {
|
||||
return initialCmdHandler().orElse(openedAccountCmdHandler()).orElse(closedCmdHandler()).build();
|
||||
}
|
||||
|
||||
private OpenedAccount openAccount() {
|
||||
return new OpenedAccount(0.0);
|
||||
}
|
||||
|
||||
private OpenedAccount makeDeposit(OpenedAccount acc, Deposited deposit) {
|
||||
return new OpenedAccount(acc.balance + deposit.amount);
|
||||
}
|
||||
|
||||
private OpenedAccount makeWithdraw(OpenedAccount acc, Withdrawn withdrawn) {
|
||||
return new OpenedAccount(acc.balance - withdrawn.amount);
|
||||
}
|
||||
|
||||
private ClosedAccount closeAccount() {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
|
||||
private EventHandlerBuilderByState<EmptyAccount, Account, AccountEvent> initialEvtHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchEvent(AccountCreated.class, this::openAccount);
|
||||
}
|
||||
|
||||
private EventHandlerBuilderByState<OpenedAccount, Account, AccountEvent>
|
||||
openedAccountEvtHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(Deposited.class, this::makeDeposit)
|
||||
.matchEvent(Withdrawn.class, this::makeWithdraw)
|
||||
.matchEvent(AccountClosed.class, ClosedAccount::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
return initialEvtHandler().orElse(openedAccountEvtHandler()).build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class AccountExampleOneLinersInModel
|
||||
extends EventSourcedBehavior<
|
||||
AccountExampleOneLinersInModel.AccountCommand,
|
||||
AccountExampleOneLinersInModel.AccountEvent,
|
||||
AccountExampleOneLinersInModel.Account> {
|
||||
|
||||
interface AccountCommand {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand {}
|
||||
|
||||
public static class Deposit implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Deposit(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Withdraw(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand {}
|
||||
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Deposited(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Withdrawn(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
interface Account {}
|
||||
|
||||
public class EmptyAccount implements Account {
|
||||
|
||||
Effect<AccountEvent, Account> createAccount(CreateAccount cmd) {
|
||||
return Effect().persist(new AccountCreated());
|
||||
}
|
||||
|
||||
OpenedAccount openAccount(AccountCreated evt) {
|
||||
return new OpenedAccount(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenedAccount implements Account {
|
||||
public final double balance;
|
||||
|
||||
OpenedAccount(double balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
Effect<AccountEvent, Account> depositCommand(Deposit deposit) {
|
||||
return Effect().persist(new Deposited(deposit.amount));
|
||||
}
|
||||
|
||||
Effect<AccountEvent, Account> withdrawCommand(Withdraw withdraw) {
|
||||
if ((balance - withdraw.amount) < 0.0) {
|
||||
return Effect().unhandled(); // TODO replies are missing in this example
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(withdraw.amount))
|
||||
.thenRun(
|
||||
acc2 -> {
|
||||
// we know this cast is safe, but somewhat ugly
|
||||
OpenedAccount openAccount = (OpenedAccount) acc2;
|
||||
// do some side-effect using balance
|
||||
System.out.println(openAccount.balance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Effect<AccountEvent, Account> closeCommand(CloseAccount cmd) {
|
||||
if (balance == 0.0) return Effect().persist(new AccountClosed());
|
||||
else return Effect().unhandled();
|
||||
}
|
||||
|
||||
OpenedAccount makeDeposit(Deposited deposit) {
|
||||
return new OpenedAccount(balance + deposit.amount);
|
||||
}
|
||||
|
||||
OpenedAccount makeWithdraw(Withdrawn withdrawn) {
|
||||
return new OpenedAccount(balance - withdrawn.amount);
|
||||
}
|
||||
|
||||
ClosedAccount closeAccount(AccountClosed evt) {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountExampleOneLinersInModel(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountExampleOneLinersInModel(
|
||||
ActorContext<AccountCommand> context, String accountNumber) {
|
||||
super(new PersistenceId(accountNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account emptyState() {
|
||||
return new EmptyAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<AccountCommand, AccountEvent, Account> commandHandler() {
|
||||
CommandHandlerBuilder<AccountCommand, AccountEvent, Account> builder =
|
||||
newCommandHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchCommand(CreateAccount.class, EmptyAccount::createAccount);
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, OpenedAccount::depositCommand)
|
||||
.matchCommand(Withdraw.class, OpenedAccount::withdrawCommand)
|
||||
.matchCommand(CloseAccount.class, OpenedAccount::closeCommand);
|
||||
|
||||
builder.forStateType(ClosedAccount.class).matchAny(() -> Effect().unhandled());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
EventHandlerBuilder<Account, AccountEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchEvent(AccountCreated.class, EmptyAccount::openAccount);
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(Deposited.class, OpenedAccount::makeDeposit)
|
||||
.matchEvent(Withdrawn.class, OpenedAccount::makeWithdraw)
|
||||
.matchEvent(AccountClosed.class, OpenedAccount::closeAccount);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class AccountExampleOneLinersInModelWithNull
|
||||
extends EventSourcedBehavior<
|
||||
AccountExampleOneLinersInModelWithNull.AccountCommand,
|
||||
AccountExampleOneLinersInModelWithNull.AccountEvent,
|
||||
AccountExampleOneLinersInModelWithNull.Account> {
|
||||
|
||||
interface AccountCommand {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand {}
|
||||
|
||||
public static class Deposit implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Deposit(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Withdraw(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand {}
|
||||
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Deposited(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Withdrawn(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
interface Account {}
|
||||
|
||||
public class OpenedAccount implements Account {
|
||||
public final double balance;
|
||||
|
||||
OpenedAccount(double balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
Effect<AccountEvent, Account> depositCommand(Deposit deposit) {
|
||||
return Effect().persist(new Deposited(deposit.amount));
|
||||
}
|
||||
|
||||
Effect<AccountEvent, Account> withdrawCommand(Withdraw withdraw) {
|
||||
if ((balance - withdraw.amount) < 0.0) {
|
||||
return Effect().unhandled(); // TODO replies are missing in this example
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(withdraw.amount))
|
||||
.thenRun(
|
||||
acc2 -> {
|
||||
// we know this cast is safe, but somewhat ugly
|
||||
OpenedAccount openAccount = (OpenedAccount) acc2;
|
||||
// do some side-effect using balance
|
||||
System.out.println(openAccount.balance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Effect<AccountEvent, Account> closeCommand(CloseAccount cmd) {
|
||||
if (balance == 0.0) return Effect().persist(new AccountClosed());
|
||||
else return Effect().unhandled();
|
||||
}
|
||||
|
||||
OpenedAccount makeDeposit(Deposited deposit) {
|
||||
return new OpenedAccount(balance + deposit.amount);
|
||||
}
|
||||
|
||||
OpenedAccount makeWithdraw(Withdrawn withdrawn) {
|
||||
return new OpenedAccount(balance - withdrawn.amount);
|
||||
}
|
||||
|
||||
ClosedAccount closeAccount(AccountClosed cmd) {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(
|
||||
context -> new AccountExampleOneLinersInModelWithNull(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountExampleOneLinersInModelWithNull(
|
||||
ActorContext<AccountCommand> context, String accountNumber) {
|
||||
super(new PersistenceId(accountNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account emptyState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> createAccount(CreateAccount cmd) {
|
||||
return Effect().persist(new AccountCreated());
|
||||
}
|
||||
|
||||
private OpenedAccount openAccount() {
|
||||
return new OpenedAccount(0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<AccountCommand, AccountEvent, Account> commandHandler() {
|
||||
|
||||
CommandHandlerBuilder<AccountCommand, AccountEvent, Account> builder =
|
||||
newCommandHandlerBuilder();
|
||||
|
||||
builder.forNullState().matchCommand(CreateAccount.class, this::createAccount);
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, OpenedAccount::depositCommand)
|
||||
.matchCommand(Withdraw.class, OpenedAccount::withdrawCommand)
|
||||
.matchCommand(CloseAccount.class, OpenedAccount::closeCommand);
|
||||
|
||||
builder.forStateType(ClosedAccount.class).matchAny(() -> Effect().unhandled());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
|
||||
EventHandlerBuilder<Account, AccountEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder.forNullState().matchEvent(AccountCreated.class, this::openAccount);
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(Deposited.class, OpenedAccount::makeDeposit)
|
||||
.matchEvent(Withdrawn.class, OpenedAccount::makeWithdraw)
|
||||
.matchEvent(AccountClosed.class, OpenedAccount::closeAccount);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class AccountExampleOneLinersWithNull
|
||||
extends EventSourcedBehavior<
|
||||
AccountExampleOneLinersWithNull.AccountCommand,
|
||||
AccountExampleOneLinersWithNull.AccountEvent,
|
||||
AccountExampleOneLinersWithNull.Account> {
|
||||
|
||||
interface AccountCommand {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand {}
|
||||
|
||||
public static class Deposit implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Deposit(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand {
|
||||
public final double amount;
|
||||
|
||||
public Withdraw(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand {}
|
||||
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Deposited(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final double amount;
|
||||
|
||||
Withdrawn(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
interface Account {}
|
||||
|
||||
public static class OpenedAccount implements Account {
|
||||
public final double balance;
|
||||
|
||||
OpenedAccount(double balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountExampleOneLinersWithNull(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountExampleOneLinersWithNull(
|
||||
ActorContext<AccountCommand> context, String accountNumber) {
|
||||
super(new PersistenceId(accountNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account emptyState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> createAccount() {
|
||||
return Effect().persist(new AccountCreated());
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> depositCommand(Deposit deposit) {
|
||||
return Effect().persist(new Deposited(deposit.amount));
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> withdrawCommand(OpenedAccount account, Withdraw withdraw) {
|
||||
if ((account.balance - withdraw.amount) < 0.0) {
|
||||
return Effect().unhandled(); // TODO replies are missing in this example
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(withdraw.amount))
|
||||
.thenRun(
|
||||
acc2 -> {
|
||||
// we know this cast is safe, but somewhat ugly
|
||||
OpenedAccount openAccount = (OpenedAccount) acc2;
|
||||
// do some side-effect using balance
|
||||
System.out.println(openAccount.balance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Effect<AccountEvent, Account> closeCommand(OpenedAccount account, CloseAccount cmd) {
|
||||
if (account.balance == 0.0) return Effect().persist(new AccountClosed());
|
||||
else return Effect().unhandled();
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, Account, Account>
|
||||
initialHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forNullState()
|
||||
.matchCommand(CreateAccount.class, this::createAccount);
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, OpenedAccount, Account>
|
||||
openedAccountHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, this::depositCommand)
|
||||
.matchCommand(Withdraw.class, this::withdrawCommand)
|
||||
.matchCommand(CloseAccount.class, this::closeCommand);
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, ClosedAccount, Account>
|
||||
closedHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(ClosedAccount.class)
|
||||
.matchCommand(AccountCommand.class, __ -> Effect().unhandled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<AccountCommand, AccountEvent, Account> commandHandler() {
|
||||
return initialHandler().orElse(openedAccountHandler()).orElse(closedHandler()).build();
|
||||
}
|
||||
|
||||
private OpenedAccount openAccount() {
|
||||
return new OpenedAccount(0.0);
|
||||
}
|
||||
|
||||
private OpenedAccount makeDeposit(OpenedAccount acc, Deposited deposit) {
|
||||
return new OpenedAccount(acc.balance + deposit.amount);
|
||||
}
|
||||
|
||||
private OpenedAccount makeWithdraw(OpenedAccount acc, Withdrawn withdrawn) {
|
||||
return new OpenedAccount(acc.balance - withdrawn.amount);
|
||||
}
|
||||
|
||||
private ClosedAccount closeAccount() {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
|
||||
private EventHandlerBuilderByState<Account, Account, AccountEvent> initialEvtHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forNullState()
|
||||
.matchEvent(AccountCreated.class, this::openAccount);
|
||||
}
|
||||
|
||||
private EventHandlerBuilderByState<OpenedAccount, Account, AccountEvent>
|
||||
openedAccountEvtHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(Deposited.class, this::makeDeposit)
|
||||
.matchEvent(Withdrawn.class, this::makeWithdraw)
|
||||
.matchEvent(AccountClosed.class, ClosedAccount::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
return initialEvtHandler().orElse(openedAccountEvtHandler()).build();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,7 @@ import akka.actor.typed.Behavior;
|
|||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehavior;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class BlogPostExample {
|
||||
|
||||
|
|
@ -161,9 +158,10 @@ public class BlogPostExample {
|
|||
}
|
||||
|
||||
// #initial-command-handler
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, BlankState, BlogState>
|
||||
private CommandHandlerBuilderByState<BlogCommand, BlogEvent, BlankState, BlogState>
|
||||
initialCommandHandler() {
|
||||
return commandHandlerBuilder(BlankState.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(BlankState.class)
|
||||
.matchCommand(
|
||||
AddPost.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -178,9 +176,10 @@ public class BlogPostExample {
|
|||
// #initial-command-handler
|
||||
|
||||
// #post-added-command-handler
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, DraftState, BlogState>
|
||||
private CommandHandlerBuilderByState<BlogCommand, BlogEvent, DraftState, BlogState>
|
||||
draftCommandHandler() {
|
||||
return commandHandlerBuilder(DraftState.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(DraftState.class)
|
||||
.matchCommand(
|
||||
ChangeBody.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -205,9 +204,10 @@ public class BlogPostExample {
|
|||
});
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, PublishedState, BlogState>
|
||||
private CommandHandlerBuilderByState<BlogCommand, BlogEvent, PublishedState, BlogState>
|
||||
publishedCommandHandler() {
|
||||
return commandHandlerBuilder(PublishedState.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(PublishedState.class)
|
||||
.matchCommand(
|
||||
ChangeBody.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -222,9 +222,10 @@ public class BlogPostExample {
|
|||
});
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, BlogState, BlogState>
|
||||
private CommandHandlerBuilderByState<BlogCommand, BlogEvent, BlogState, BlogState>
|
||||
commonCommandHandler() {
|
||||
return commandHandlerBuilder(BlogState.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(BlogState.class)
|
||||
.matchCommand(AddPost.class, (state, cmd) -> Effect().unhandled());
|
||||
}
|
||||
// #post-added-command-handler
|
||||
|
|
@ -243,25 +244,31 @@ public class BlogPostExample {
|
|||
// #event-handler
|
||||
@Override
|
||||
public EventHandler<BlogState, BlogEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
.matchEvent(PostAdded.class, (state, event) -> new DraftState(event.content))
|
||||
|
||||
EventHandlerBuilder<BlogState, BlogEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forStateType(BlankState.class)
|
||||
.matchEvent(PostAdded.class, event -> new DraftState(event.content));
|
||||
|
||||
builder
|
||||
.forStateType(DraftState.class)
|
||||
.matchEvent(
|
||||
BodyChanged.class,
|
||||
DraftState.class,
|
||||
(state, chg) ->
|
||||
state.withContent(
|
||||
new PostContent(state.postId(), state.postContent.title, chg.newBody)))
|
||||
.matchEvent(Published.class, (state, event) -> new PublishedState(state.postContent));
|
||||
|
||||
builder
|
||||
.forStateType(PublishedState.class)
|
||||
.matchEvent(
|
||||
BodyChanged.class,
|
||||
PublishedState.class,
|
||||
(state, chg) ->
|
||||
state.withContent(
|
||||
new PostContent(state.postId(), state.postContent.title, chg.newBody)))
|
||||
.matchEvent(
|
||||
Published.class,
|
||||
DraftState.class,
|
||||
(state, event) -> new PublishedState(state.postContent))
|
||||
.build();
|
||||
new PostContent(state.postId(), state.postContent.title, chg.newBody)));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
// #event-handler
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ public class MovieWatchList
|
|||
|
||||
@Override
|
||||
public CommandHandler<Command, Event, MovieList> commandHandler() {
|
||||
return commandHandlerBuilder(MovieList.class)
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand(
|
||||
AddMovie.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -120,7 +121,8 @@ public class MovieWatchList
|
|||
|
||||
@Override
|
||||
public EventHandler<MovieList, Event> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
return newEventHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchEvent(MovieAdded.class, (state, event) -> state.add(event.movieId))
|
||||
.matchEvent(MovieRemoved.class, (state, event) -> state.remove(event.movieId))
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ package jdocs.akka.persistence.typed;
|
|||
import akka.Done;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehavior;
|
||||
|
||||
import java.util.Objects;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
public class NullBlogState {
|
||||
|
||||
|
|
@ -124,9 +119,10 @@ public class NullBlogState {
|
|||
|
||||
public static class BlogBehavior extends EventSourcedBehavior<BlogCommand, BlogEvent, BlogState> {
|
||||
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, BlogState, BlogState>
|
||||
private CommandHandlerBuilderByState<BlogCommand, BlogEvent, BlogState, BlogState>
|
||||
initialCommandHandler() {
|
||||
return commandHandlerBuilder(Objects::isNull)
|
||||
return newCommandHandlerBuilder()
|
||||
.forNullState()
|
||||
.matchCommand(
|
||||
AddPost.class,
|
||||
cmd -> {
|
||||
|
|
@ -137,9 +133,10 @@ public class NullBlogState {
|
|||
});
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, BlogState, BlogState>
|
||||
private CommandHandlerBuilderByState<BlogCommand, BlogEvent, BlogState, BlogState>
|
||||
postCommandHandler() {
|
||||
return commandHandlerBuilder(Objects::nonNull)
|
||||
return newCommandHandlerBuilder()
|
||||
.forNonNullState()
|
||||
.matchCommand(
|
||||
ChangeBody.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -181,15 +178,23 @@ public class NullBlogState {
|
|||
|
||||
@Override
|
||||
public EventHandler<BlogState, BlogEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
.matchEvent(PostAdded.class, event -> new BlogState(event.content, false))
|
||||
|
||||
EventHandlerBuilder<BlogState, BlogEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forNullState()
|
||||
.matchEvent(PostAdded.class, event -> new BlogState(event.content, false));
|
||||
|
||||
builder
|
||||
.forNonNullState()
|
||||
.matchEvent(
|
||||
BodyChanged.class,
|
||||
(state, chg) ->
|
||||
state.withContent(
|
||||
new PostContent(state.postId(), state.postContent.title, chg.newBody)))
|
||||
.matchEvent(Published.class, (state, event) -> new BlogState(state.postContent, true))
|
||||
.build();
|
||||
.matchEvent(Published.class, (state, event) -> new BlogState(state.postContent, true));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@ package jdocs.akka.persistence.typed;
|
|||
import akka.Done;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehavior;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -125,9 +122,11 @@ public class OptionalBlogState {
|
|||
public static class BlogBehavior
|
||||
extends EventSourcedBehavior<BlogCommand, BlogEvent, Optional<BlogState>> {
|
||||
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, Optional<BlogState>, Optional<BlogState>>
|
||||
private CommandHandlerBuilderByState<
|
||||
BlogCommand, BlogEvent, Optional<BlogState>, Optional<BlogState>>
|
||||
initialCommandHandler() {
|
||||
return commandHandlerBuilder(state -> !state.isPresent())
|
||||
return newCommandHandlerBuilder()
|
||||
.forState(state -> !state.isPresent())
|
||||
.matchCommand(
|
||||
AddPost.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -138,9 +137,11 @@ public class OptionalBlogState {
|
|||
});
|
||||
}
|
||||
|
||||
private CommandHandlerBuilder<BlogCommand, BlogEvent, Optional<BlogState>, Optional<BlogState>>
|
||||
private CommandHandlerBuilderByState<
|
||||
BlogCommand, BlogEvent, Optional<BlogState>, Optional<BlogState>>
|
||||
postCommandHandler() {
|
||||
return commandHandlerBuilder(state -> state.isPresent())
|
||||
return newCommandHandlerBuilder()
|
||||
.forState(Optional::isPresent)
|
||||
.matchCommand(
|
||||
ChangeBody.class,
|
||||
(state, cmd) -> {
|
||||
|
|
@ -182,9 +183,16 @@ public class OptionalBlogState {
|
|||
|
||||
@Override
|
||||
public EventHandler<Optional<BlogState>, BlogEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
|
||||
EventHandlerBuilder<Optional<BlogState>, BlogEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forState(state -> !state.isPresent())
|
||||
.matchEvent(
|
||||
PostAdded.class, (state, event) -> Optional.of(new BlogState(event.content, false)))
|
||||
PostAdded.class, (state, event) -> Optional.of(new BlogState(event.content, false)));
|
||||
|
||||
builder
|
||||
.forState(Optional::isPresent)
|
||||
.matchEvent(
|
||||
BodyChanged.class,
|
||||
(state, chg) ->
|
||||
|
|
@ -195,8 +203,9 @@ public class OptionalBlogState {
|
|||
blogState.postId(), blogState.postContent.title, chg.newBody))))
|
||||
.matchEvent(
|
||||
Published.class,
|
||||
(state, event) -> state.map(blogState -> new BlogState(blogState.postContent, true)))
|
||||
.build();
|
||||
(state, event) -> state.map(blogState -> new BlogState(blogState.postContent, true)));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ package jdocs.akka.persistence.typed.auction;
|
|||
import akka.Done;
|
||||
import akka.persistence.typed.ExpectingReply;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.Effect;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehavior;
|
||||
import akka.persistence.typed.javadsl.*;
|
||||
|
||||
import static jdocs.akka.persistence.typed.auction.AuctionCommand.*;
|
||||
import static jdocs.akka.persistence.typed.auction.AuctionEvent.*;
|
||||
|
|
@ -37,9 +33,10 @@ public class AuctionEntity
|
|||
}
|
||||
|
||||
// Command handler for the not started state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
private CommandHandlerBuilderByState<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
notStartedHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.NOT_STARTED)
|
||||
newCommandHandlerBuilder()
|
||||
.forState(state -> state.getStatus() == AuctionStatus.NOT_STARTED)
|
||||
.matchCommand(StartAuction.class, this::startAuction)
|
||||
.matchCommand(
|
||||
PlaceBid.class,
|
||||
|
|
@ -47,17 +44,19 @@ public class AuctionEntity
|
|||
Effect().reply(cmd, createResult(state, PlaceBidStatus.NOT_STARTED)));
|
||||
|
||||
// Command handler for the under auction state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
private CommandHandlerBuilderByState<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
underAuctionHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.UNDER_AUCTION)
|
||||
newCommandHandlerBuilder()
|
||||
.forState(state -> state.getStatus() == AuctionStatus.UNDER_AUCTION)
|
||||
.matchCommand(StartAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(PlaceBid.class, this::placeBid)
|
||||
.matchCommand(FinishBidding.class, this::finishBidding);
|
||||
|
||||
// Command handler for the completed state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
private CommandHandlerBuilderByState<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
completedHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.COMPLETE)
|
||||
newCommandHandlerBuilder()
|
||||
.forState(state -> state.getStatus() == AuctionStatus.COMPLETE)
|
||||
.matchCommand(StartAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(FinishBidding.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(
|
||||
|
|
@ -66,9 +65,10 @@ public class AuctionEntity
|
|||
Effect().reply(cmd, createResult(state, PlaceBidStatus.FINISHED)));
|
||||
|
||||
// Command handler for the cancelled state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
private CommandHandlerBuilderByState<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
cancelledHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.CANCELLED)
|
||||
newCommandHandlerBuilder()
|
||||
.forState(state -> state.getStatus() == AuctionStatus.CANCELLED)
|
||||
.matchCommand(StartAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(FinishBidding.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(CancelAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
|
|
@ -77,14 +77,16 @@ public class AuctionEntity
|
|||
(state, cmd) ->
|
||||
Effect().reply(cmd, createResult(state, PlaceBidStatus.CANCELLED)));
|
||||
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
private CommandHandlerBuilderByState<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
getAuctionHandler =
|
||||
commandHandlerBuilder(AuctionState.class)
|
||||
newCommandHandlerBuilder()
|
||||
.forStateType(AuctionState.class)
|
||||
.matchCommand(GetAuction.class, (state, cmd) -> Effect().reply(cmd, state));
|
||||
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
private CommandHandlerBuilderByState<AuctionCommand, AuctionEvent, AuctionState, AuctionState>
|
||||
cancelHandler =
|
||||
commandHandlerBuilder(AuctionState.class)
|
||||
newCommandHandlerBuilder()
|
||||
.forStateType(AuctionState.class)
|
||||
.matchCommand(CancelAuction.class, this::cancelAuction);
|
||||
// Note, an item can go from completed to cancelled, since it is the item service that controls
|
||||
// whether an auction is cancelled or not. If it cancels before it receives a bidding finished
|
||||
|
|
@ -256,13 +258,21 @@ public class AuctionEntity
|
|||
|
||||
@Override
|
||||
public EventHandler<AuctionState, AuctionEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
.matchEvent(AuctionStarted.class, (state, evt) -> AuctionState.start(evt.getAuction()))
|
||||
|
||||
EventHandlerBuilder<AuctionState, AuctionEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forState(auction -> auction.getStatus() == AuctionStatus.NOT_STARTED)
|
||||
.matchEvent(AuctionStarted.class, (state, evt) -> AuctionState.start(evt.getAuction()));
|
||||
|
||||
builder
|
||||
.forState(auction -> auction.getStatus() == AuctionStatus.UNDER_AUCTION)
|
||||
.matchEvent(BidPlaced.class, (state, evt) -> state.bid(evt.getBid()))
|
||||
.matchEvent(BiddingFinished.class, (state, evt) -> state.withStatus(AuctionStatus.COMPLETE))
|
||||
.matchEvent(
|
||||
AuctionCancelled.class, (state, evt) -> state.withStatus(AuctionStatus.CANCELLED))
|
||||
.build();
|
||||
AuctionCancelled.class, (state, evt) -> state.withStatus(AuctionStatus.CANCELLED));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private PlaceBidResult createResult(AuctionState state, PlaceBidStatus status) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue