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:
Renato Cavalcanti 2019-01-18 13:52:16 +01:00 committed by Patrik Nordwall
parent 18daa47781
commit db4f224f4a
18 changed files with 1355 additions and 199 deletions

View file

@ -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) {

View file

@ -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();
}

View file

@ -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

View file

@ -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

View file

@ -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]()
/**

View file

@ -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) {

View file

@ -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) -> {

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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();

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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) {