remove reply type parameter from Command in Account example

* only makes it look more complex than it is
* probably a remaining of the early reply experiments
This commit is contained in:
Patrik Nordwall 2020-05-25 14:07:58 +02:00
parent c45e6ef39b
commit 888580e604
8 changed files with 91 additions and 79 deletions

View file

@ -38,10 +38,10 @@ public interface AccountExampleWithEventHandlersInState {
// Command
// #reply-command
interface Command<Reply> extends CborSerializable {}
interface Command extends CborSerializable {}
// #reply-command
public static class CreateAccount implements Command<OperationResult> {
public static class CreateAccount implements Command {
public final ActorRef<OperationResult> replyTo;
@JsonCreator
@ -50,7 +50,7 @@ public interface AccountExampleWithEventHandlersInState {
}
}
public static class Deposit implements Command<OperationResult> {
public static class Deposit implements Command {
public final BigDecimal amount;
public final ActorRef<OperationResult> replyTo;
@ -60,7 +60,7 @@ public interface AccountExampleWithEventHandlersInState {
}
}
public static class Withdraw implements Command<OperationResult> {
public static class Withdraw implements Command {
public final BigDecimal amount;
public final ActorRef<OperationResult> replyTo;
@ -70,7 +70,7 @@ public interface AccountExampleWithEventHandlersInState {
}
}
public static class GetBalance implements Command<CurrentBalance> {
public static class GetBalance implements Command {
public final ActorRef<CurrentBalance> replyTo;
@JsonCreator
@ -79,7 +79,7 @@ public interface AccountExampleWithEventHandlersInState {
}
}
public static class CloseAccount implements Command<OperationResult> {
public static class CloseAccount implements Command {
public final ActorRef<OperationResult> replyTo;
@JsonCreator

View file

@ -35,9 +35,9 @@ public interface AccountExampleWithMutableState {
EntityTypeKey.create(Command.class, "Account");
// Command
interface Command<Reply> extends CborSerializable {}
interface Command extends CborSerializable {}
public static class CreateAccount implements Command<OperationResult> {
public static class CreateAccount implements Command {
public final ActorRef<OperationResult> replyTo;
@JsonCreator
@ -46,7 +46,7 @@ public interface AccountExampleWithMutableState {
}
}
public static class Deposit implements Command<OperationResult> {
public static class Deposit implements Command {
public final BigDecimal amount;
public final ActorRef<OperationResult> replyTo;
@ -56,7 +56,7 @@ public interface AccountExampleWithMutableState {
}
}
public static class Withdraw implements Command<OperationResult> {
public static class Withdraw implements Command {
public final BigDecimal amount;
public final ActorRef<OperationResult> replyTo;
@ -66,7 +66,7 @@ public interface AccountExampleWithMutableState {
}
}
public static class GetBalance implements Command<CurrentBalance> {
public static class GetBalance implements Command {
public final ActorRef<CurrentBalance> replyTo;
@JsonCreator
@ -75,7 +75,7 @@ public interface AccountExampleWithMutableState {
}
}
public static class CloseAccount implements Command<OperationResult> {
public static class CloseAccount implements Command {
public final ActorRef<OperationResult> replyTo;
@JsonCreator

View file

@ -35,9 +35,9 @@ public interface AccountExampleWithNullState {
EntityTypeKey.create(Command.class, "Account");
// Command
interface Command<Reply> extends CborSerializable {}
interface Command extends CborSerializable {}
public static class CreateAccount implements Command<OperationResult> {
public static class CreateAccount implements Command {
public final ActorRef<OperationResult> replyTo;
@JsonCreator
@ -46,7 +46,7 @@ public interface AccountExampleWithNullState {
}
}
public static class Deposit implements Command<OperationResult> {
public static class Deposit implements Command {
public final BigDecimal amount;
public final ActorRef<OperationResult> replyTo;
@ -56,7 +56,7 @@ public interface AccountExampleWithNullState {
}
}
public static class Withdraw implements Command<OperationResult> {
public static class Withdraw implements Command {
public final BigDecimal amount;
public final ActorRef<OperationResult> replyTo;
@ -66,7 +66,7 @@ public interface AccountExampleWithNullState {
}
}
public static class GetBalance implements Command<CurrentBalance> {
public static class GetBalance implements Command {
public final ActorRef<CurrentBalance> replyTo;
@JsonCreator
@ -75,7 +75,7 @@ public interface AccountExampleWithNullState {
}
}
public static class CloseAccount implements Command<OperationResult> {
public static class CloseAccount implements Command {
public final ActorRef<OperationResult> replyTo;
@JsonCreator

View file

@ -26,7 +26,7 @@ class AccountExampleDocSpec
with LogCapturing {
private val eventSourcedTestKit =
EventSourcedBehaviorTestKit[AccountEntity.Command[_], AccountEntity.Event, AccountEntity.Account](
EventSourcedBehaviorTestKit[AccountEntity.Command, AccountEntity.Event, AccountEntity.Account](
system,
AccountEntity("1", PersistenceId("Account", "1")))

View file

@ -79,10 +79,8 @@ class AccountExampleSpec
}
"reject Withdraw overdraft" in {
// AccountCommand[_] is the command type, but it should also be possible to narrow it to
// AccountCommand[OperationResult]
val probe = createTestProbe[OperationResult]()
val ref = ClusterSharding(system).entityRefFor[Command[OperationResult]](AccountEntity.TypeKey, "3")
val ref = ClusterSharding(system).entityRefFor[Command](AccountEntity.TypeKey, "3")
ref ! CreateAccount(probe.ref)
probe.expectMessage(Confirmed)
ref ! Deposit(100, probe.ref)
@ -90,10 +88,12 @@ class AccountExampleSpec
ref ! Withdraw(110, probe.ref)
probe.expectMessageType[Rejected]
// Account.Command is the command type, but it should also be possible to narrow it
// ... thus restricting the entity ref from being sent other commands, e.g.:
// val ref2 = ClusterSharding(system).entityRefFor[Deposit](AccountEntity.TypeKey, "3")
// val probe2 = createTestProbe[CurrentBalance]()
// val msg = GetBalance(probe2.ref)
// ref ! msg // type mismatch: GetBalance NOT =:= AccountCommand[OperationResult]
// ref2 ! msg // type mismatch: GetBalance NOT =:= Deposit
}
"handle GetBalance" in {

View file

@ -24,14 +24,12 @@ object AccountExampleWithCommandHandlersInState {
//#account-entity
object AccountEntity {
// Command
sealed trait Command[Reply <: CommandReply] extends CborSerializable {
def replyTo: ActorRef[Reply]
}
final case class CreateAccount(replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class Deposit(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class Withdraw(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class GetBalance(replyTo: ActorRef[CurrentBalance]) extends Command[CurrentBalance]
final case class CloseAccount(replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
sealed trait Command extends CborSerializable
final case class CreateAccount(replyTo: ActorRef[OperationResult]) extends Command
final case class Deposit(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command
final case class Withdraw(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command
final case class GetBalance(replyTo: ActorRef[CurrentBalance]) extends Command
final case class CloseAccount(replyTo: ActorRef[OperationResult]) extends Command
// Reply
sealed trait CommandReply extends CborSerializable
@ -54,11 +52,11 @@ object AccountExampleWithCommandHandlersInState {
// State
sealed trait Account extends CborSerializable {
def applyCommand(cmd: Command[_]): ReplyEffect
def applyCommand(cmd: Command): ReplyEffect
def applyEvent(event: Event): Account
}
case object EmptyAccount extends Account {
override def applyCommand(cmd: Command[_]): ReplyEffect =
override def applyCommand(cmd: Command): ReplyEffect =
cmd match {
case CreateAccount(replyTo) =>
Effect.persist(AccountCreated).thenReply(replyTo)(_ => Confirmed)
@ -76,7 +74,7 @@ object AccountExampleWithCommandHandlersInState {
case class OpenedAccount(balance: BigDecimal) extends Account {
require(balance >= Zero, "Account balance can't be negative")
override def applyCommand(cmd: Command[_]): ReplyEffect =
override def applyCommand(cmd: Command): ReplyEffect =
cmd match {
case Deposit(amount, replyTo) =>
Effect.persist(Deposited(amount)).thenReply(replyTo)(_ => Confirmed)
@ -115,28 +113,33 @@ object AccountExampleWithCommandHandlersInState {
}
case object ClosedAccount extends Account {
override def applyCommand(cmd: Command[_]): ReplyEffect =
override def applyCommand(cmd: Command): ReplyEffect =
cmd match {
case c @ (_: Deposit | _: Withdraw) =>
Effect.reply(c.replyTo)(Rejected("Account is closed"))
case c: Deposit =>
replyClosed(c.replyTo)
case c: Withdraw =>
replyClosed(c.replyTo)
case GetBalance(replyTo) =>
Effect.reply(replyTo)(CurrentBalance(Zero))
case CloseAccount(replyTo) =>
Effect.reply(replyTo)(Rejected("Account is already closed"))
replyClosed(replyTo)
case CreateAccount(replyTo) =>
Effect.reply(replyTo)(Rejected("Account is already created"))
replyClosed(replyTo)
}
private def replyClosed(replyTo: ActorRef[AccountEntity.OperationResult]): ReplyEffect =
Effect.reply(replyTo)(Rejected(s"Account is closed"))
override def applyEvent(event: Event): Account =
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
}
// when used with sharding, this TypeKey can be used in `sharding.init` and `sharding.entityRefFor`:
val TypeKey: EntityTypeKey[Command[_]] =
EntityTypeKey[Command[_]]("Account")
val TypeKey: EntityTypeKey[Command] =
EntityTypeKey[Command]("Account")
def apply(persistenceId: PersistenceId): Behavior[Command[_]] = {
EventSourcedBehavior.withEnforcedReplies[Command[_], Event, Account](
def apply(persistenceId: PersistenceId): Behavior[Command] = {
EventSourcedBehavior.withEnforcedReplies[Command, Event, Account](
persistenceId,
EmptyAccount,
(state, cmd) => state.applyCommand(cmd),

View file

@ -27,17 +27,15 @@ object AccountExampleWithEventHandlersInState {
object AccountEntity {
// Command
//#reply-command
sealed trait Command[Reply <: CommandReply] extends CborSerializable {
def replyTo: ActorRef[Reply]
}
sealed trait Command extends CborSerializable
//#reply-command
final case class CreateAccount(replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class Deposit(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class CreateAccount(replyTo: ActorRef[OperationResult]) extends Command
final case class Deposit(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command
//#reply-command
final case class Withdraw(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class Withdraw(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command
//#reply-command
final case class GetBalance(replyTo: ActorRef[CurrentBalance]) extends Command[CurrentBalance]
final case class CloseAccount(replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class GetBalance(replyTo: ActorRef[CurrentBalance]) extends Command
final case class CloseAccount(replyTo: ActorRef[OperationResult]) extends Command
// Reply
//#reply-command
@ -89,20 +87,20 @@ object AccountExampleWithEventHandlersInState {
}
// when used with sharding, this TypeKey can be used in `sharding.init` and `sharding.entityRefFor`:
val TypeKey: EntityTypeKey[Command[_]] =
EntityTypeKey[Command[_]]("Account")
val TypeKey: EntityTypeKey[Command] =
EntityTypeKey[Command]("Account")
// Note that after defining command, event and state classes you would probably start here when writing this.
// When filling in the parameters of EventSourcedBehavior.apply you can use IntelliJ alt+Enter > createValue
// to generate the stub with types for the command and event handlers.
//#withEnforcedReplies
def apply(accountNumber: String, persistenceId: PersistenceId): Behavior[Command[_]] = {
def apply(accountNumber: String, persistenceId: PersistenceId): Behavior[Command] = {
EventSourcedBehavior.withEnforcedReplies(persistenceId, EmptyAccount, commandHandler(accountNumber), eventHandler)
}
//#withEnforcedReplies
private def commandHandler(accountNumber: String): (Account, Command[_]) => ReplyEffect[Event, Account] = {
private def commandHandler(accountNumber: String): (Account, Command) => ReplyEffect[Event, Account] = {
(state, cmd) =>
state match {
case EmptyAccount =>
@ -122,18 +120,26 @@ object AccountExampleWithEventHandlersInState {
case ClosedAccount =>
cmd match {
case c @ (_: Deposit | _: Withdraw) =>
Effect.reply(c.replyTo)(Rejected(s"Account $accountNumber is closed"))
case c: Deposit =>
replyClosed(accountNumber, c.replyTo)
case c: Withdraw =>
replyClosed(accountNumber, c.replyTo)
case GetBalance(replyTo) =>
Effect.reply(replyTo)(CurrentBalance(Zero))
case CloseAccount(replyTo) =>
Effect.reply(replyTo)(Rejected(s"Account $accountNumber is already closed"))
replyClosed(accountNumber, replyTo)
case CreateAccount(replyTo) =>
Effect.reply(replyTo)(Rejected(s"Account $accountNumber is already closed"))
replyClosed(accountNumber, replyTo)
}
}
}
private def replyClosed(
accountNumber: String,
replyTo: ActorRef[AccountEntity.OperationResult]): ReplyEffect[Event, Account] = {
Effect.reply(replyTo)(Rejected(s"Account $accountNumber is closed"))
}
private val eventHandler: (Account, Event) => Account = { (state, event) =>
state.applyEvent(event)
}

View file

@ -24,14 +24,12 @@ object AccountExampleWithOptionState {
//#account-entity
object AccountEntity {
// Command
sealed trait Command[Reply <: CommandReply] extends CborSerializable {
def replyTo: ActorRef[Reply]
}
final case class CreateAccount(replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class Deposit(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class Withdraw(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
final case class GetBalance(replyTo: ActorRef[CurrentBalance]) extends Command[CurrentBalance]
final case class CloseAccount(replyTo: ActorRef[OperationResult]) extends Command[OperationResult]
sealed trait Command extends CborSerializable
final case class CreateAccount(replyTo: ActorRef[OperationResult]) extends Command
final case class Deposit(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command
final case class Withdraw(amount: BigDecimal, replyTo: ActorRef[OperationResult]) extends Command
final case class GetBalance(replyTo: ActorRef[CurrentBalance]) extends Command
final case class CloseAccount(replyTo: ActorRef[OperationResult]) extends Command
// Reply
sealed trait CommandReply extends CborSerializable
@ -54,13 +52,13 @@ object AccountExampleWithOptionState {
// State
sealed trait Account extends CborSerializable {
def applyCommand(cmd: Command[_]): ReplyEffect
def applyCommand(cmd: Command): ReplyEffect
def applyEvent(event: Event): Account
}
case class OpenedAccount(balance: BigDecimal) extends Account {
require(balance >= Zero, "Account balance can't be negative")
override def applyCommand(cmd: Command[_]): ReplyEffect =
override def applyCommand(cmd: Command): ReplyEffect =
cmd match {
case Deposit(amount, replyTo) =>
Effect.persist(Deposited(amount)).thenReply(replyTo)(_ => Confirmed)
@ -99,28 +97,33 @@ object AccountExampleWithOptionState {
}
case object ClosedAccount extends Account {
override def applyCommand(cmd: Command[_]): ReplyEffect =
override def applyCommand(cmd: Command): ReplyEffect =
cmd match {
case c @ (_: Deposit | _: Withdraw) =>
Effect.reply(c.replyTo)(Rejected("Account is closed"))
case c: Deposit =>
replyClosed(c.replyTo)
case c: Withdraw =>
replyClosed(c.replyTo)
case GetBalance(replyTo) =>
Effect.reply(replyTo)(CurrentBalance(Zero))
case CloseAccount(replyTo) =>
Effect.reply(replyTo)(Rejected("Account is already closed"))
replyClosed(replyTo)
case CreateAccount(replyTo) =>
Effect.reply(replyTo)(Rejected("Account is already created"))
replyClosed(replyTo)
}
private def replyClosed(replyTo: ActorRef[AccountEntity.OperationResult]): ReplyEffect =
Effect.reply(replyTo)(Rejected(s"Account is closed"))
override def applyEvent(event: Event): Account =
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
}
// when used with sharding, this TypeKey can be used in `sharding.init` and `sharding.entityRefFor`:
val TypeKey: EntityTypeKey[Command[_]] =
EntityTypeKey[Command[_]]("Account")
val TypeKey: EntityTypeKey[Command] =
EntityTypeKey[Command]("Account")
def apply(persistenceId: PersistenceId): Behavior[Command[_]] = {
EventSourcedBehavior.withEnforcedReplies[Command[_], Event, Option[Account]](
def apply(persistenceId: PersistenceId): Behavior[Command] = {
EventSourcedBehavior.withEnforcedReplies[Command, Event, Option[Account]](
persistenceId,
None,
(state, cmd) =>
@ -135,7 +138,7 @@ object AccountExampleWithOptionState {
})
}
def onFirstCommand(cmd: Command[_]): ReplyEffect = {
def onFirstCommand(cmd: Command): ReplyEffect = {
cmd match {
case CreateAccount(replyTo) =>
Effect.persist(AccountCreated).thenReply(replyTo)(_ => Confirmed)