Add and improve javadsl Persistence Typed samples (#26287)
* Complete the javadsl account examples in Persistence Typed docs * corresponding style variations as in the scaladsl samples * adding mutable state sample for Java (not interesting for Scala) * cleanup the first example in Persistence Typed docs * move ActorContext snip in Persistence Typed docs
This commit is contained in:
parent
84bab0fbca
commit
dbcc54b0b2
15 changed files with 1562 additions and 1052 deletions
|
|
@ -16,10 +16,13 @@ of the account; `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.
|
|||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #account-entity }
|
||||
|
||||
TODO include corresponding example in Java
|
||||
Java
|
||||
: @@snip [AccountExampleWithEventHandlersInState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #account-entity }
|
||||
|
||||
Notice how the `eventHandler` delegates to the `applyEvent` in the `Account` (state), which is implemented
|
||||
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.
|
||||
@scala[Notice how the `eventHandler` delegates to the `applyEvent` in the `Account` (state), which is implemented
|
||||
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||
@java[Notice how the `eventHandler` delegates to methods in the concrete `Account` (state) classes;
|
||||
`EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||
|
||||
## Command handlers in the state
|
||||
|
||||
|
|
@ -28,10 +31,13 @@ We can take the previous bank account example one step further by handling the c
|
|||
Scala
|
||||
: @@snip [AccountExampleWithCommandHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithCommandHandlersInState.scala) { #account-entity }
|
||||
|
||||
TODO include corresponding example in Java
|
||||
Java
|
||||
: @@snip [AccountExampleWithCommandHandlersInState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithCommandHandlersInState.java) { #account-entity }
|
||||
|
||||
Notice how the command handler is delegating to `applyCommand` in the `Account` (state), which is implemented
|
||||
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.
|
||||
@scala[Notice how the command handler is delegating to `applyCommand` in the `Account` (state), which is implemented
|
||||
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||
@java[Notice how the command handler delegates to methods in the concrete `Account` (state) classes;
|
||||
`EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||
|
||||
## Optional initial state
|
||||
|
||||
|
|
@ -48,4 +54,21 @@ is then used in command and event handlers at the outer layer before delegating
|
|||
Scala
|
||||
: @@snip [AccountExampleWithOptionState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithOptionState.scala) { #account-entity }
|
||||
|
||||
TODO include corresponding example in Java
|
||||
Java
|
||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithNullState.java) { #account-entity }
|
||||
|
||||
@@@ div { .group-java }
|
||||
## Mutable state
|
||||
|
||||
The state can be mutable or immutable. When it is immutable the event handler returns a new instance of the state
|
||||
for each change.
|
||||
|
||||
When using mutable state it's important to not send the full state instance as a message to another actor,
|
||||
e.g. as a reply to a command. Messages must be immutable to avoid concurrency problems.
|
||||
|
||||
The above examples are using immutable state classes and below is corresponding example with mutable state.
|
||||
|
||||
Java
|
||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithMutableState.java) { #account-entity }
|
||||
|
||||
@@@
|
||||
|
|
|
|||
|
|
@ -87,48 +87,51 @@ The same event handler is also used when the entity is started up to recover its
|
|||
It is not recommended to perform side effects
|
||||
in the event handler, as those are also executed during recovery of an persistent actor
|
||||
|
||||
## Basic example
|
||||
### Completing the example
|
||||
|
||||
Let's fill in the details of the example.
|
||||
|
||||
Command and event:
|
||||
|
||||
Scala
|
||||
: @@snip [PersistentActorCompileOnyTest.scala](/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PersistentActorCompileOnlyTest.scala) { #command }
|
||||
: @@snip [BasicPersistentBehaviorCompileOnly.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala) { #command }
|
||||
|
||||
Java
|
||||
: @@snip [PersistentActorCompileOnyTest.java](/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorCompileOnlyTest.java) { #command }
|
||||
: @@snip [BasicPersistentBehaviorTest.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/BasicPersistentBehaviorTest.java) { #command }
|
||||
|
||||
State is a List containing all the events:
|
||||
State is a List containing the 5 latest items:
|
||||
|
||||
Scala
|
||||
: @@snip [PersistentActorCompileOnyTest.scala](/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PersistentActorCompileOnlyTest.scala) { #state }
|
||||
: @@snip [BasicPersistentBehaviorCompileOnly.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala) { #state }
|
||||
|
||||
Java
|
||||
: @@snip [PersistentActorCompileOnyTest.java](/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorCompileOnlyTest.java) { #state }
|
||||
: @@snip [BasicPersistentBehaviorTest.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/BasicPersistentBehaviorTest.java) { #state }
|
||||
|
||||
The command handler persists the `Cmd` payload in an `Evt`@java[. In this simple example the command handler is defined using a lambda, for the more complicated example below a `CommandHandlerBuilder` is used]:
|
||||
The command handler persists the `Add` payload in an `Added` event:
|
||||
|
||||
Scala
|
||||
: @@snip [PersistentActorCompileOnyTest.scala](/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PersistentActorCompileOnlyTest.scala) { #command-handler }
|
||||
: @@snip [BasicPersistentBehaviorCompileOnly.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala) { #command-handler }
|
||||
|
||||
Java
|
||||
: @@snip [PersistentActorCompileOnyTest.java](/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorCompileOnlyTest.java) { #command-handler }
|
||||
: @@snip [BasicPersistentBehaviorTest.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/BasicPersistentBehaviorTest.java) { #command-handler }
|
||||
|
||||
The event handler appends the event to the state. This is called after successfully
|
||||
persisting the event in the database @java[. As with the command handler the event handler is defined using a lambda, see below for a more complicated example using the `EventHandlerBuilder`]:
|
||||
The event handler appends the item to the state and keeps 5 items. This is called after successfully
|
||||
persisting the event in the database:
|
||||
|
||||
Scala
|
||||
: @@snip [PersistentActorCompileOnyTest.scala](/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PersistentActorCompileOnlyTest.scala) { #event-handler }
|
||||
: @@snip [BasicPersistentBehaviorCompileOnly.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala) { #event-handler }
|
||||
|
||||
Java
|
||||
: @@snip [PersistentActorCompileOnyTest.java](/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorCompileOnlyTest.java) { #event-handler }
|
||||
: @@snip [BasicPersistentBehaviorTest.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/BasicPersistentBehaviorTest.java) { #event-handler }
|
||||
|
||||
These are used to create a `EventSourcedBehavior`:
|
||||
@scala[These are used to create a `EventSourcedBehavior`:]
|
||||
@java[These are defined in an `EventSourcedBehavior`:]
|
||||
|
||||
Scala
|
||||
: @@snip [PersistentActorCompileOnyTest.scala](/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PersistentActorCompileOnlyTest.scala) { #behavior }
|
||||
: @@snip [BasicPersistentBehaviorCompileOnly.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala) { #behavior }
|
||||
|
||||
Java
|
||||
: @@snip [PersistentActorCompileOnyTest.java](/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorCompileOnlyTest.java) { #behavior }
|
||||
: @@snip [BasicPersistentBehaviorTest.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/BasicPersistentBehaviorTest.java) { #behavior }
|
||||
|
||||
## Cluster Sharding and persistence
|
||||
|
||||
|
|
@ -150,10 +153,10 @@ If the persistent behavior needs to use the `ActorContext`, for example to spawn
|
|||
wrapping construction with `Behaviors.setup`:
|
||||
|
||||
Scala
|
||||
: @@snip [PersistentActorCompileOnyTest.scala](/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PersistentActorCompileOnlyTest.scala) { #actor-context }
|
||||
: @@snip [BasicPersistentBehaviorCompileOnly.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala) { #actor-context }
|
||||
|
||||
Java
|
||||
: @@snip [PersistentActorCompileOnyTest.java](/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorCompileOnlyTest.java) { #actor-context }
|
||||
: @@snip [BasicPersistentBehaviorTest.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/BasicPersistentBehaviorTest.java) { #actor-context }
|
||||
|
||||
|
||||
|
||||
|
|
@ -305,30 +308,36 @@ there will be compilation errors if the returned effect isn't a `ReplyEffect`, w
|
|||
created with @scala[`Effect.reply`]@java[`Effects().reply`], @scala[`Effect.noReply`]@java[`Effects().noReply`],
|
||||
@scala[`Effect.thenReply`]@java[`Effects().thenReply`], or @scala[`Effect.thenNoReply`]@java[`Effects().thenNoReply`].
|
||||
|
||||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #withEnforcedReplies }
|
||||
|
||||
Java
|
||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #withEnforcedReplies }
|
||||
|
||||
The commands must implement `ExpectingReply` to include the @scala[`ActorRef[ReplyMessageType]`]@java[`ActorRef<ReplyMessageType>`]
|
||||
in a standardized way.
|
||||
|
||||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #reply-command }
|
||||
|
||||
Java
|
||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #reply-command }
|
||||
|
||||
The `ReplyEffect` is created with @scala[`Effect.reply`]@java[`Effects().reply`], @scala[`Effect.noReply`]@java[`Effects().noReply`],
|
||||
@scala[`Effect.thenReply`]@java[`Effects().thenReply`], or @scala[`Effect.thenNoReply`]@java[`Effects().thenNoReply`].
|
||||
|
||||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #reply }
|
||||
|
||||
Java
|
||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #reply }
|
||||
|
||||
These effects will send the reply message even when @scala[`EventSourcedBehavior.withEnforcedReplies`]@java[`EventSourcedBehaviorWithEnforcedReplies`]
|
||||
is not used, but then there will be no compilation errors if the reply decision is left out.
|
||||
|
||||
Note that the `noReply` is a way of making conscious decision that a reply shouldn't be sent for a specific
|
||||
command or the reply will be sent later, perhaps after some asynchronous interaction with other actors or services.
|
||||
|
||||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #reply-command }
|
||||
|
||||
TODO include corresponding example in Java
|
||||
|
||||
When using the reply effect the commands must implement `ExpectingReply` to include the @scala[`ActorRef[ReplyMessageType]`]@java[`ActorRef<ReplyMessageType>`]
|
||||
in a standardized way.
|
||||
|
||||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #reply }
|
||||
|
||||
TODO include corresponding example in Java
|
||||
|
||||
Scala
|
||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #withEnforcedReplies }
|
||||
|
||||
TODO include corresponding example in Java
|
||||
|
||||
## Serialization
|
||||
|
||||
The same @ref:[serialization](../serialization.md) mechanism as for untyped
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ public class PersistentActorCompileOnlyTest {
|
|||
}
|
||||
// #event-wrapper
|
||||
|
||||
// #command
|
||||
public static class SimpleCommand {
|
||||
public final String data;
|
||||
|
||||
|
|
@ -59,9 +58,7 @@ public class PersistentActorCompileOnlyTest {
|
|||
this.data = data;
|
||||
}
|
||||
}
|
||||
// #command
|
||||
|
||||
// #event
|
||||
static class SimpleEvent {
|
||||
private final String data;
|
||||
|
||||
|
|
@ -69,9 +66,7 @@ public class PersistentActorCompileOnlyTest {
|
|||
this.data = data;
|
||||
}
|
||||
}
|
||||
// #event
|
||||
|
||||
// #state
|
||||
static class SimpleState {
|
||||
private final List<String> events;
|
||||
|
||||
|
|
@ -89,9 +84,7 @@ public class PersistentActorCompileOnlyTest {
|
|||
return new SimpleState(newEvents);
|
||||
}
|
||||
}
|
||||
// #state
|
||||
|
||||
// #behavior
|
||||
public static EventSourcedBehavior<SimpleCommand, SimpleEvent, SimpleState> pb =
|
||||
new EventSourcedBehavior<SimpleCommand, SimpleEvent, SimpleState>(new PersistenceId("p1")) {
|
||||
|
||||
|
|
@ -100,19 +93,15 @@ public class PersistentActorCompileOnlyTest {
|
|||
return new SimpleState();
|
||||
}
|
||||
|
||||
// #command-handler
|
||||
@Override
|
||||
public CommandHandler<SimpleCommand, SimpleEvent, SimpleState> commandHandler() {
|
||||
return (state, cmd) -> Effect().persist(new SimpleEvent(cmd.data));
|
||||
}
|
||||
// #command-handler
|
||||
|
||||
// #event-handler
|
||||
@Override
|
||||
public EventHandler<SimpleState, SimpleEvent> eventHandler() {
|
||||
return (state, event) -> state.addEvent(event);
|
||||
}
|
||||
// #event-handler
|
||||
|
||||
// #install-event-adapter
|
||||
@Override
|
||||
|
|
@ -121,8 +110,6 @@ public class PersistentActorCompileOnlyTest {
|
|||
}
|
||||
// #install-event-adapter
|
||||
};
|
||||
|
||||
// #behavior
|
||||
}
|
||||
|
||||
abstract static class WithAck {
|
||||
|
|
@ -287,14 +274,10 @@ public class PersistentActorCompileOnlyTest {
|
|||
what.thenApply(r -> new AcknowledgeSideEffect(r.correlationId)).thenAccept(sender::tell);
|
||||
}
|
||||
|
||||
// #actor-context
|
||||
public Behavior<Command> behavior(PersistenceId persistenceId) {
|
||||
return Behaviors.setup(ctx -> new MyPersistentBehavior(persistenceId, ctx));
|
||||
}
|
||||
|
||||
// #actor-context
|
||||
|
||||
// #actor-context
|
||||
class MyPersistentBehavior
|
||||
extends EventSourcedBehavior<Command, Event, RecoveryComplete.EventsInFlight> {
|
||||
|
||||
|
|
@ -305,7 +288,6 @@ public class PersistentActorCompileOnlyTest {
|
|||
super(persistenceId);
|
||||
this.ctx = ctx;
|
||||
}
|
||||
// #actor-context
|
||||
|
||||
@Override
|
||||
public EventsInFlight emptyState() {
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* 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 AccountExample
|
||||
extends EventSourcedBehavior<
|
||||
AccountExample.AccountCommand, AccountExample.AccountEvent, AccountExample.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 AccountExample(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountExample(ActorContext<AccountCommand> context, String accountNumber) {
|
||||
super(new PersistenceId(accountNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account emptyState() {
|
||||
return new EmptyAccount();
|
||||
}
|
||||
|
||||
private CommandHandlerBuilderByState<AccountCommand, AccountEvent, EmptyAccount, Account>
|
||||
initialCmdHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchCommand(CreateAccount.class, (__, cmd) -> Effect().persist(new AccountCreated()));
|
||||
}
|
||||
|
||||
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,
|
||||
(acc, cmd) -> {
|
||||
if ((acc.balance - cmd.amount) < 0.0) {
|
||||
return Effect().unhandled(); // TODO replies are missing in this example
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(cmd.amount))
|
||||
.thenRun(
|
||||
acc2 -> { // FIXME in scaladsl it's named thenRun, change javadsl also?
|
||||
// we know this cast is safe, but somewhat ugly
|
||||
OpenedAccount openAccount = (OpenedAccount) acc2;
|
||||
// do some side-effect using balance
|
||||
System.out.println(openAccount.balance);
|
||||
});
|
||||
}
|
||||
})
|
||||
.matchCommand(
|
||||
CloseAccount.class,
|
||||
(acc, cmd) -> {
|
||||
if (acc.balance == 0.0) return Effect().persist(new AccountClosed());
|
||||
else return 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 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 initialEvtHandler().orElse(openedAccountEvtHandler()).build();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
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.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
||||
import akka.persistence.typed.javadsl.ReplyEffect;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
||||
* account - event handlers that delegate to methods in the state classes - command handlers that
|
||||
* delegate to methods in the state classes - replies of various types, using ExpectingReply and
|
||||
* EventSourcedBehaviorWithEnforcedReplies
|
||||
*/
|
||||
public interface AccountExampleWithCommandHandlersInState {
|
||||
|
||||
// #account-entity
|
||||
public class AccountEntity
|
||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||
|
||||
// Command
|
||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CreateAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Deposit implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Deposit(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Withdraw(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.amount = amount;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetBalance implements AccountCommand<CurrentBalance> {
|
||||
private final ActorRef<CurrentBalance> replyTo;
|
||||
|
||||
public GetBalance(ActorRef<CurrentBalance> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<CurrentBalance> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CloseAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
// Reply
|
||||
interface AccountCommandReply {}
|
||||
|
||||
interface OperationResult extends AccountCommandReply {}
|
||||
|
||||
enum Confirmed implements OperationResult {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static class Rejected implements OperationResult {
|
||||
public final String reason;
|
||||
|
||||
public Rejected(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CurrentBalance implements AccountCommandReply {
|
||||
public final BigDecimal balance;
|
||||
|
||||
public CurrentBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Deposited(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Withdrawn(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
// State
|
||||
interface Account {}
|
||||
|
||||
public class EmptyAccount implements Account {
|
||||
ReplyEffect<AccountEvent, Account> createAccount(CreateAccount command) {
|
||||
return Effect()
|
||||
.persist(new AccountCreated())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
OpenedAccount openedAccount() {
|
||||
return new OpenedAccount(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenedAccount implements Account {
|
||||
public final BigDecimal balance;
|
||||
|
||||
public OpenedAccount(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
ReplyEffect<AccountEvent, Account> deposit(Deposit command) {
|
||||
return Effect()
|
||||
.persist(new Deposited(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
ReplyEffect<AccountEvent, Account> withdraw(Withdraw command) {
|
||||
if (!canWithdraw(command.amount)) {
|
||||
return Effect()
|
||||
.reply(command, new Rejected("not enough funds to withdraw " + command.amount));
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
ReplyEffect<AccountEvent, Account> getBalance(GetBalance command) {
|
||||
return Effect().reply(command, new CurrentBalance(balance));
|
||||
}
|
||||
|
||||
ReplyEffect<AccountEvent, Account> closeAccount(CloseAccount command) {
|
||||
if (balance.equals(BigDecimal.ZERO)) {
|
||||
return Effect()
|
||||
.persist(new AccountClosed())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
} else {
|
||||
return Effect().reply(command, new Rejected("balance must be zero for closing account"));
|
||||
}
|
||||
}
|
||||
|
||||
OpenedAccount makeDeposit(BigDecimal amount) {
|
||||
return new OpenedAccount(balance.add(amount));
|
||||
}
|
||||
|
||||
boolean canWithdraw(BigDecimal amount) {
|
||||
return (balance.subtract(amount).compareTo(BigDecimal.ZERO) >= 0);
|
||||
}
|
||||
|
||||
OpenedAccount makeWithdraw(BigDecimal amount) {
|
||||
if (!canWithdraw(amount))
|
||||
throw new IllegalStateException("Account balance can't be negative");
|
||||
return new OpenedAccount(balance.subtract(amount));
|
||||
}
|
||||
|
||||
ClosedAccount closedAccount() {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountEntity(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::deposit)
|
||||
.matchCommand(Withdraw.class, OpenedAccount::withdraw)
|
||||
.matchCommand(GetBalance.class, OpenedAccount::getBalance)
|
||||
.matchCommand(CloseAccount.class, OpenedAccount::closeAccount);
|
||||
|
||||
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, (account, event) -> account.openedAccount());
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(
|
||||
Deposited.class,
|
||||
(account, deposited) -> {
|
||||
account.makeDeposit(deposited.amount);
|
||||
return account;
|
||||
})
|
||||
.matchEvent(
|
||||
Withdrawn.class,
|
||||
(account, withdrawn) -> {
|
||||
account.makeWithdraw(withdrawn.amount);
|
||||
return account;
|
||||
})
|
||||
.matchEvent(AccountClosed.class, (account, closed) -> account.closedAccount());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
// #account-entity
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
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.ReplyEffect;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
||||
* account - event handlers that delegate to methods in the state classes - command handlers that
|
||||
* delegate to methods in the EventSourcedBehavior class - replies of various types, using
|
||||
* ExpectingReply and EventSourcedBehaviorWithEnforcedReplies
|
||||
*/
|
||||
public interface AccountExampleWithEventHandlersInState {
|
||||
|
||||
// #account-entity
|
||||
// #withEnforcedReplies
|
||||
public class AccountEntity
|
||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||
// #withEnforcedReplies
|
||||
|
||||
// Command
|
||||
// #reply-command
|
||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||
// #reply-command
|
||||
|
||||
public static class CreateAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CreateAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Deposit implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Deposit(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Withdraw(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.amount = amount;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetBalance implements AccountCommand<CurrentBalance> {
|
||||
private final ActorRef<CurrentBalance> replyTo;
|
||||
|
||||
public GetBalance(ActorRef<CurrentBalance> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<CurrentBalance> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CloseAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
// Reply
|
||||
// #reply-command
|
||||
interface AccountCommandReply {}
|
||||
|
||||
interface OperationResult extends AccountCommandReply {}
|
||||
|
||||
enum Confirmed implements OperationResult {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static class Rejected implements OperationResult {
|
||||
public final String reason;
|
||||
|
||||
public Rejected(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
// #reply-command
|
||||
|
||||
public static class CurrentBalance implements AccountCommandReply {
|
||||
public final BigDecimal balance;
|
||||
|
||||
public CurrentBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Deposited(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Withdrawn(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
// State
|
||||
interface Account {}
|
||||
|
||||
public static class EmptyAccount implements Account {
|
||||
OpenedAccount openedAccount() {
|
||||
return new OpenedAccount(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpenedAccount implements Account {
|
||||
private final BigDecimal balance;
|
||||
|
||||
public OpenedAccount(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
OpenedAccount makeDeposit(BigDecimal amount) {
|
||||
return new OpenedAccount(balance.add(amount));
|
||||
}
|
||||
|
||||
boolean canWithdraw(BigDecimal amount) {
|
||||
return (balance.subtract(amount).compareTo(BigDecimal.ZERO) >= 0);
|
||||
}
|
||||
|
||||
OpenedAccount makeWithdraw(BigDecimal amount) {
|
||||
if (!canWithdraw(amount))
|
||||
throw new IllegalStateException("Account balance can't be negative");
|
||||
return new OpenedAccount(balance.subtract(amount));
|
||||
}
|
||||
|
||||
ClosedAccount closedAccount() {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountEntity(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, this::createAccount);
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, this::deposit)
|
||||
.matchCommand(Withdraw.class, this::withdraw)
|
||||
.matchCommand(GetBalance.class, this::getBalance)
|
||||
.matchCommand(CloseAccount.class, this::closeAccount);
|
||||
|
||||
builder.forStateType(ClosedAccount.class).matchAny(() -> Effect().unhandled());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> createAccount(
|
||||
EmptyAccount account, CreateAccount command) {
|
||||
return Effect()
|
||||
.persist(new AccountCreated())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> deposit(OpenedAccount account, Deposit command) {
|
||||
return Effect()
|
||||
.persist(new Deposited(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
// #reply
|
||||
private ReplyEffect<AccountEvent, Account> withdraw(OpenedAccount account, Withdraw command) {
|
||||
if (!account.canWithdraw(command.amount)) {
|
||||
return Effect()
|
||||
.reply(command, new Rejected("not enough funds to withdraw " + command.amount));
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
}
|
||||
// #reply
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> getBalance(
|
||||
OpenedAccount account, GetBalance command) {
|
||||
return Effect().reply(command, new CurrentBalance(account.balance));
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> closeAccount(
|
||||
OpenedAccount account, CloseAccount command) {
|
||||
if (account.balance.equals(BigDecimal.ZERO)) {
|
||||
return Effect()
|
||||
.persist(new AccountClosed())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
} else {
|
||||
return Effect().reply(command, new Rejected("balance must be zero for closing account"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
EventHandlerBuilder<Account, AccountEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchEvent(AccountCreated.class, (account, created) -> account.openedAccount());
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(
|
||||
Deposited.class, (account, deposited) -> account.makeDeposit(deposited.amount))
|
||||
.matchEvent(
|
||||
Withdrawn.class, (account, withdrawn) -> account.makeWithdraw(withdrawn.amount))
|
||||
.matchEvent(AccountClosed.class, (account, closed) -> account.closedAccount());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
// #account-entity
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
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.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
||||
import akka.persistence.typed.javadsl.ReplyEffect;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
||||
* account - mutable state - event handlers that delegate to methods in the state classes - command
|
||||
* handlers that delegate to methods in the EventSourcedBehavior class - replies of various types,
|
||||
* using ExpectingReply and EventSourcedBehaviorWithEnforcedReplies
|
||||
*/
|
||||
public interface AccountExampleWithMutableState {
|
||||
|
||||
// #account-entity
|
||||
public class AccountEntity
|
||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||
|
||||
// Command
|
||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CreateAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Deposit implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Deposit(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Withdraw(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.amount = amount;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetBalance implements AccountCommand<CurrentBalance> {
|
||||
private final ActorRef<CurrentBalance> replyTo;
|
||||
|
||||
public GetBalance(ActorRef<CurrentBalance> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<CurrentBalance> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CloseAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
// Reply
|
||||
interface AccountCommandReply {}
|
||||
|
||||
interface OperationResult extends AccountCommandReply {}
|
||||
|
||||
enum Confirmed implements OperationResult {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static class Rejected implements OperationResult {
|
||||
public final String reason;
|
||||
|
||||
public Rejected(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CurrentBalance implements AccountCommandReply {
|
||||
public final BigDecimal balance;
|
||||
|
||||
public CurrentBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Deposited(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Withdrawn(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
// State
|
||||
interface Account {}
|
||||
|
||||
public static class EmptyAccount implements Account {
|
||||
OpenedAccount openedAccount() {
|
||||
return new OpenedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpenedAccount implements Account {
|
||||
private BigDecimal balance = BigDecimal.ZERO;
|
||||
|
||||
public BigDecimal getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
void makeDeposit(BigDecimal amount) {
|
||||
balance = balance.add(amount);
|
||||
}
|
||||
|
||||
boolean canWithdraw(BigDecimal amount) {
|
||||
return (balance.subtract(amount).compareTo(BigDecimal.ZERO) >= 0);
|
||||
}
|
||||
|
||||
void makeWithdraw(BigDecimal amount) {
|
||||
if (!canWithdraw(amount))
|
||||
throw new IllegalStateException("Account balance can't be negative");
|
||||
balance = balance.subtract(amount);
|
||||
}
|
||||
|
||||
ClosedAccount closedAccount() {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountEntity(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, this::createAccount);
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchCommand(Deposit.class, this::deposit)
|
||||
.matchCommand(Withdraw.class, this::withdraw)
|
||||
.matchCommand(GetBalance.class, this::getBalance)
|
||||
.matchCommand(CloseAccount.class, this::closeAccount);
|
||||
|
||||
builder.forStateType(ClosedAccount.class).matchAny(() -> Effect().unhandled());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> createAccount(
|
||||
EmptyAccount account, CreateAccount command) {
|
||||
return Effect()
|
||||
.persist(new AccountCreated())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> deposit(OpenedAccount account, Deposit command) {
|
||||
return Effect()
|
||||
.persist(new Deposited(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> withdraw(OpenedAccount account, Withdraw command) {
|
||||
if (!account.canWithdraw(command.amount)) {
|
||||
return Effect()
|
||||
.reply(command, new Rejected("not enough funds to withdraw " + command.amount));
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> getBalance(
|
||||
OpenedAccount account, GetBalance command) {
|
||||
return Effect().reply(command, new CurrentBalance(account.balance));
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> closeAccount(
|
||||
OpenedAccount account, CloseAccount command) {
|
||||
if (account.getBalance().equals(BigDecimal.ZERO)) {
|
||||
return Effect()
|
||||
.persist(new AccountClosed())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
} else {
|
||||
return Effect().reply(command, new Rejected("balance must be zero for closing account"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
EventHandlerBuilder<Account, AccountEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder
|
||||
.forStateType(EmptyAccount.class)
|
||||
.matchEvent(AccountCreated.class, (account, event) -> account.openedAccount());
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(
|
||||
Deposited.class,
|
||||
(account, deposited) -> {
|
||||
account.makeDeposit(deposited.amount);
|
||||
return account;
|
||||
})
|
||||
.matchEvent(
|
||||
Withdrawn.class,
|
||||
(account, withdrawn) -> {
|
||||
account.makeWithdraw(withdrawn.amount);
|
||||
return account;
|
||||
})
|
||||
.matchEvent(AccountClosed.class, (account, closed) -> account.closedAccount());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
// #account-entity
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
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.EventHandler;
|
||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
||||
import akka.persistence.typed.javadsl.ReplyEffect;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
||||
* account - null as emptyState - event handlers that delegate to methods in the state classes -
|
||||
* command handlers that delegate to methods in the EventSourcedBehavior class - replies of various
|
||||
* types, using ExpectingReply and EventSourcedBehaviorWithEnforcedReplies
|
||||
*/
|
||||
public interface AccountExampleWithNullState {
|
||||
|
||||
// #account-entity
|
||||
public class AccountEntity
|
||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||
|
||||
// Command
|
||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||
|
||||
public static class CreateAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CreateAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Deposit implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Deposit(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdraw implements AccountCommand<OperationResult> {
|
||||
public final BigDecimal amount;
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public Withdraw(BigDecimal amount, ActorRef<OperationResult> replyTo) {
|
||||
this.amount = amount;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GetBalance implements AccountCommand<CurrentBalance> {
|
||||
private final ActorRef<CurrentBalance> replyTo;
|
||||
|
||||
public GetBalance(ActorRef<CurrentBalance> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<CurrentBalance> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseAccount implements AccountCommand<OperationResult> {
|
||||
private final ActorRef<OperationResult> replyTo;
|
||||
|
||||
public CloseAccount(ActorRef<OperationResult> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<OperationResult> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
// Reply
|
||||
interface AccountCommandReply {}
|
||||
|
||||
interface OperationResult extends AccountCommandReply {}
|
||||
|
||||
enum Confirmed implements OperationResult {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
public static class Rejected implements OperationResult {
|
||||
public final String reason;
|
||||
|
||||
public Rejected(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CurrentBalance implements AccountCommandReply {
|
||||
public final BigDecimal balance;
|
||||
|
||||
public CurrentBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
interface AccountEvent {}
|
||||
|
||||
public static class AccountCreated implements AccountEvent {}
|
||||
|
||||
public static class Deposited implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Deposited(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Withdrawn implements AccountEvent {
|
||||
public final BigDecimal amount;
|
||||
|
||||
Withdrawn(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccountClosed implements AccountEvent {}
|
||||
|
||||
// State
|
||||
interface Account {}
|
||||
|
||||
public static class OpenedAccount implements Account {
|
||||
public final BigDecimal balance;
|
||||
|
||||
public OpenedAccount() {
|
||||
this.balance = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
public OpenedAccount(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
OpenedAccount makeDeposit(BigDecimal amount) {
|
||||
return new OpenedAccount(balance.add(amount));
|
||||
}
|
||||
|
||||
boolean canWithdraw(BigDecimal amount) {
|
||||
return (balance.subtract(amount).compareTo(BigDecimal.ZERO) >= 0);
|
||||
}
|
||||
|
||||
OpenedAccount makeWithdraw(BigDecimal amount) {
|
||||
if (!canWithdraw(amount))
|
||||
throw new IllegalStateException("Account balance can't be negative");
|
||||
return new OpenedAccount(balance.subtract(amount));
|
||||
}
|
||||
|
||||
ClosedAccount closedAccount() {
|
||||
return new ClosedAccount();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClosedAccount implements Account {}
|
||||
|
||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
||||
}
|
||||
|
||||
public AccountEntity(ActorContext<AccountCommand> context, String accountNumber) {
|
||||
super(new PersistenceId(accountNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account emptyState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@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, this::deposit)
|
||||
.matchCommand(Withdraw.class, this::withdraw)
|
||||
.matchCommand(GetBalance.class, this::getBalance)
|
||||
.matchCommand(CloseAccount.class, this::closeAccount);
|
||||
|
||||
builder.forStateType(ClosedAccount.class).matchAny(() -> Effect().unhandled());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> createAccount(CreateAccount command) {
|
||||
return Effect()
|
||||
.persist(new AccountCreated())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> deposit(OpenedAccount account, Deposit command) {
|
||||
return Effect()
|
||||
.persist(new Deposited(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> withdraw(OpenedAccount account, Withdraw command) {
|
||||
if (!account.canWithdraw(command.amount)) {
|
||||
return Effect()
|
||||
.reply(command, new Rejected("not enough funds to withdraw " + command.amount));
|
||||
} else {
|
||||
return Effect()
|
||||
.persist(new Withdrawn(command.amount))
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> getBalance(
|
||||
OpenedAccount account, GetBalance command) {
|
||||
return Effect().reply(command, new CurrentBalance(account.balance));
|
||||
}
|
||||
|
||||
private ReplyEffect<AccountEvent, Account> closeAccount(
|
||||
OpenedAccount account, CloseAccount command) {
|
||||
if (account.balance.equals(BigDecimal.ZERO)) {
|
||||
return Effect()
|
||||
.persist(new AccountClosed())
|
||||
.thenReply(command, account2 -> Confirmed.INSTANCE);
|
||||
} else {
|
||||
return Effect().reply(command, new Rejected("balance must be zero for closing account"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<Account, AccountEvent> eventHandler() {
|
||||
EventHandlerBuilder<Account, AccountEvent> builder = newEventHandlerBuilder();
|
||||
|
||||
builder.forNullState().matchEvent(AccountCreated.class, () -> new OpenedAccount());
|
||||
|
||||
builder
|
||||
.forStateType(OpenedAccount.class)
|
||||
.matchEvent(
|
||||
Deposited.class,
|
||||
(account, deposited) -> {
|
||||
account.makeDeposit(deposited.amount);
|
||||
return account;
|
||||
})
|
||||
.matchEvent(
|
||||
Withdrawn.class,
|
||||
(account, withdrawn) -> {
|
||||
account.makeWithdraw(withdrawn.amount);
|
||||
return account;
|
||||
})
|
||||
.matchEvent(AccountClosed.class, (account, closed) -> account.closedAccount());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
// #account-entity
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ package jdocs.akka.persistence.typed;
|
|||
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.SupervisorStrategy;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
|
|
@ -13,78 +14,260 @@ import akka.persistence.typed.javadsl.EventHandler;
|
|||
import akka.persistence.typed.javadsl.EventSourcedBehavior;
|
||||
import java.time.Duration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class BasicPersistentBehaviorTest {
|
||||
|
||||
// #structure
|
||||
public interface Command {}
|
||||
interface Structure {
|
||||
// #structure
|
||||
public class MyPersistentBehavior
|
||||
extends EventSourcedBehavior<
|
||||
MyPersistentBehavior.Command, MyPersistentBehavior.Event, MyPersistentBehavior.State> {
|
||||
|
||||
public interface Event {}
|
||||
static EventSourcedBehavior<Command, Event, State> eventSourcedBehavior =
|
||||
new MyPersistentBehavior(new PersistenceId("pid"));
|
||||
|
||||
public static class State {}
|
||||
interface Command {}
|
||||
|
||||
// #supervision
|
||||
public static class MyPersistentBehavior extends EventSourcedBehavior<Command, Event, State> {
|
||||
public MyPersistentBehavior(PersistenceId persistenceId) {
|
||||
super(
|
||||
persistenceId,
|
||||
SupervisorStrategy.restartWithBackoff(
|
||||
Duration.ofSeconds(10), Duration.ofSeconds(30), 0.2));
|
||||
interface Event {}
|
||||
|
||||
public static class State {}
|
||||
|
||||
public MyPersistentBehavior(PersistenceId persistenceId) {
|
||||
super(persistenceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State emptyState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<Command, Event, State> commandHandler() {
|
||||
return (state, command) -> {
|
||||
throw new RuntimeException("TODO: process the command & return an Effect");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<State, Event> eventHandler() {
|
||||
return (state, event) -> {
|
||||
throw new RuntimeException("TODO: process the event return the next state");
|
||||
};
|
||||
}
|
||||
}
|
||||
// #supervision
|
||||
|
||||
@Override
|
||||
public State emptyState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<Command, Event, State> commandHandler() {
|
||||
return (state, command) -> {
|
||||
throw new RuntimeException("TODO: process the command & return an Effect");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<State, Event> eventHandler() {
|
||||
return (state, event) -> {
|
||||
throw new RuntimeException("TODO: process the event return the next state");
|
||||
};
|
||||
}
|
||||
|
||||
// #recovery
|
||||
@Override
|
||||
public void onRecoveryCompleted(State state) {
|
||||
throw new RuntimeException("TODO: add some end-of-recovery side-effect here");
|
||||
}
|
||||
// #recovery
|
||||
|
||||
// #tagging
|
||||
@Override
|
||||
public Set<String> tagsFor(Event event) {
|
||||
throw new RuntimeException("TODO: inspect the event and return any tags it should have");
|
||||
}
|
||||
// #tagging
|
||||
// #structure
|
||||
}
|
||||
|
||||
static EventSourcedBehavior<Command, Event, State> eventSourcedBehavior =
|
||||
new MyPersistentBehavior(new PersistenceId("pid"));
|
||||
// #structure
|
||||
interface FirstExample {
|
||||
// #behavior
|
||||
public class MyPersistentBehavior
|
||||
extends EventSourcedBehavior<
|
||||
MyPersistentBehavior.Command, MyPersistentBehavior.Event, MyPersistentBehavior.State> {
|
||||
|
||||
// #wrapPersistentBehavior
|
||||
static Behavior<Command> debugAlwaysSnapshot =
|
||||
Behaviors.setup(
|
||||
(context) -> {
|
||||
return new MyPersistentBehavior(new PersistenceId("pid")) {
|
||||
@Override
|
||||
public boolean shouldSnapshot(State state, Event event, long sequenceNr) {
|
||||
context
|
||||
.getLog()
|
||||
.info("Snapshot actor {} => state: {}", context.getSelf().path().name(), state);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
});
|
||||
// #wrapPersistentBehavior
|
||||
// #behavior
|
||||
|
||||
// #command
|
||||
interface Command {}
|
||||
|
||||
public static class Add implements Command {
|
||||
public final String data;
|
||||
|
||||
public Add(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Clear implements Command {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
interface Event {}
|
||||
|
||||
public static class Added implements Event {
|
||||
public final String data;
|
||||
|
||||
public Added(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Cleared implements Event {
|
||||
INSTANCE
|
||||
}
|
||||
// #command
|
||||
|
||||
// #state
|
||||
public static class State {
|
||||
private final List<String> items;
|
||||
|
||||
private State(List<String> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public State() {
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
||||
public State addItem(String data) {
|
||||
List<String> newItems = new ArrayList<>(items);
|
||||
newItems.add(0, data);
|
||||
// keep 5 items
|
||||
List<String> latest = newItems.subList(0, Math.min(4, newItems.size() - 1));
|
||||
return new State(latest);
|
||||
}
|
||||
}
|
||||
// #state
|
||||
|
||||
// #behavior
|
||||
public MyPersistentBehavior(PersistenceId persistenceId) {
|
||||
super(persistenceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State emptyState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
// #command-handler
|
||||
@Override
|
||||
public CommandHandler<Command, Event, State> commandHandler() {
|
||||
return newCommandHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchCommand(Add.class, command -> Effect().persist(new Added(command.data)))
|
||||
.matchCommand(Clear.class, command -> Effect().persist(Cleared.INSTANCE))
|
||||
.build();
|
||||
}
|
||||
// #command-handler
|
||||
|
||||
// #event-handler
|
||||
@Override
|
||||
public EventHandler<State, Event> eventHandler() {
|
||||
return newEventHandlerBuilder()
|
||||
.forAnyState()
|
||||
.matchEvent(Added.class, (state, event) -> state.addItem(event.data))
|
||||
.matchEvent(Cleared.class, () -> new State())
|
||||
.build();
|
||||
}
|
||||
// #event-handler
|
||||
}
|
||||
// #behavior
|
||||
|
||||
}
|
||||
|
||||
interface More {
|
||||
interface Command {}
|
||||
|
||||
interface Event {}
|
||||
|
||||
public static class State {}
|
||||
|
||||
// #supervision
|
||||
public class MyPersistentBehavior extends EventSourcedBehavior<Command, Event, State> {
|
||||
public MyPersistentBehavior(PersistenceId persistenceId) {
|
||||
super(
|
||||
persistenceId,
|
||||
SupervisorStrategy.restartWithBackoff(
|
||||
Duration.ofSeconds(10), Duration.ofSeconds(30), 0.2));
|
||||
}
|
||||
// #supervision
|
||||
|
||||
@Override
|
||||
public State emptyState() {
|
||||
return new State();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<Command, Event, State> commandHandler() {
|
||||
return (state, command) -> {
|
||||
throw new RuntimeException("TODO: process the command & return an Effect");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<State, Event> eventHandler() {
|
||||
return (state, event) -> {
|
||||
throw new RuntimeException("TODO: process the event return the next state");
|
||||
};
|
||||
}
|
||||
|
||||
// #recovery
|
||||
@Override
|
||||
public void onRecoveryCompleted(State state) {
|
||||
throw new RuntimeException("TODO: add some end-of-recovery side-effect here");
|
||||
}
|
||||
// #recovery
|
||||
|
||||
// #tagging
|
||||
@Override
|
||||
public Set<String> tagsFor(Event event) {
|
||||
throw new RuntimeException("TODO: inspect the event and return any tags it should have");
|
||||
}
|
||||
// #tagging
|
||||
}
|
||||
|
||||
EventSourcedBehavior<Command, Event, State> eventSourcedBehavior =
|
||||
new MyPersistentBehavior(new PersistenceId("pid"));
|
||||
|
||||
// #wrapPersistentBehavior
|
||||
Behavior<Command> debugAlwaysSnapshot =
|
||||
Behaviors.setup(
|
||||
(context) -> {
|
||||
return new MyPersistentBehavior(new PersistenceId("pid")) {
|
||||
@Override
|
||||
public boolean shouldSnapshot(State state, Event event, long sequenceNr) {
|
||||
context
|
||||
.getLog()
|
||||
.info(
|
||||
"Snapshot actor {} => state: {}", context.getSelf().path().name(), state);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
});
|
||||
// #wrapPersistentBehavior
|
||||
}
|
||||
|
||||
interface WithActorContext {
|
||||
interface Command {}
|
||||
|
||||
interface Event {}
|
||||
|
||||
public static class State {}
|
||||
|
||||
// #actor-context
|
||||
public class MyPersistentBehavior extends EventSourcedBehavior<Command, Event, State> {
|
||||
|
||||
public static Behavior<Command> behavior(PersistenceId persistenceId) {
|
||||
return Behaviors.setup(ctx -> new MyPersistentBehavior(persistenceId, ctx));
|
||||
}
|
||||
|
||||
// this makes the context available to the command handler etc.
|
||||
private final ActorContext<Command> ctx;
|
||||
|
||||
public MyPersistentBehavior(PersistenceId persistenceId, ActorContext<Command> ctx) {
|
||||
super(persistenceId);
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
// #actor-context
|
||||
@Override
|
||||
public State emptyState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<Command, Event, State> commandHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<State, Event> eventHandler() {
|
||||
return null;
|
||||
}
|
||||
// #actor-context
|
||||
}
|
||||
// #actor-context
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,43 +17,6 @@ object PersistentActorCompileOnlyTest {
|
|||
|
||||
import akka.persistence.typed.scaladsl.EventSourcedBehavior._
|
||||
|
||||
object Simple {
|
||||
//#command
|
||||
sealed trait SimpleCommand
|
||||
case class Cmd(data: String) extends SimpleCommand
|
||||
|
||||
sealed trait SimpleEvent
|
||||
case class Evt(data: String) extends SimpleEvent
|
||||
//#command
|
||||
|
||||
//#state
|
||||
case class ExampleState(events: List[String] = Nil)
|
||||
//#state
|
||||
|
||||
//#command-handler
|
||||
val commandHandler: CommandHandler[SimpleCommand, SimpleEvent, ExampleState] =
|
||||
CommandHandler.command {
|
||||
case Cmd(data) ⇒ Effect.persist(Evt(data))
|
||||
}
|
||||
//#command-handler
|
||||
|
||||
//#event-handler
|
||||
val eventHandler: (ExampleState, SimpleEvent) ⇒ ExampleState = {
|
||||
case (state, Evt(data)) ⇒ state.copy(data :: state.events)
|
||||
}
|
||||
//#event-handler
|
||||
|
||||
//#behavior
|
||||
val simpleBehavior: EventSourcedBehavior[SimpleCommand, SimpleEvent, ExampleState] =
|
||||
EventSourcedBehavior[SimpleCommand, SimpleEvent, ExampleState](
|
||||
persistenceId = PersistenceId("sample-id-1"),
|
||||
emptyState = ExampleState(Nil),
|
||||
commandHandler = commandHandler,
|
||||
eventHandler = eventHandler)
|
||||
//#behavior
|
||||
|
||||
}
|
||||
|
||||
object WithAck {
|
||||
case object Ack
|
||||
|
||||
|
|
@ -434,30 +397,4 @@ object PersistentActorCompileOnlyTest {
|
|||
|
||||
}
|
||||
|
||||
object WithContext {
|
||||
sealed trait Command
|
||||
sealed trait Event
|
||||
class State
|
||||
|
||||
// #actor-context
|
||||
val behavior: Behavior[String] =
|
||||
Behaviors.setup { ctx ⇒
|
||||
EventSourcedBehavior[String, String, State](
|
||||
persistenceId = PersistenceId("myPersistenceId"),
|
||||
emptyState = new State,
|
||||
commandHandler = CommandHandler.command {
|
||||
cmd ⇒
|
||||
ctx.log.info("Got command {}", cmd)
|
||||
Effect.persist(cmd).thenRun { state ⇒
|
||||
ctx.log.info("event persisted, new state {}", state)
|
||||
}
|
||||
},
|
||||
eventHandler = {
|
||||
case (state, _) ⇒ state
|
||||
})
|
||||
}
|
||||
// #actor-context
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,58 @@ import akka.persistence.typed.PersistenceId
|
|||
|
||||
object BasicPersistentBehaviorCompileOnly {
|
||||
|
||||
object FirstExample {
|
||||
//#command
|
||||
sealed trait Command
|
||||
final case class Add(data: String) extends Command
|
||||
case object Clear extends Command
|
||||
|
||||
sealed trait Event
|
||||
final case class Added(data: String) extends Event
|
||||
case object Cleared extends Event
|
||||
//#command
|
||||
|
||||
//#state
|
||||
final case class State(history: List[String] = Nil)
|
||||
//#state
|
||||
|
||||
//#command-handler
|
||||
import akka.persistence.typed.scaladsl.Effect
|
||||
|
||||
val commandHandler: (State, Command) ⇒ Effect[Event, State] = {
|
||||
(state, command) ⇒
|
||||
command match {
|
||||
case Add(data) ⇒ Effect.persist(Added(data))
|
||||
case Clear ⇒ Effect.persist(Cleared)
|
||||
}
|
||||
}
|
||||
//#command-handler
|
||||
|
||||
//#event-handler
|
||||
val eventHandler: (State, Event) ⇒ State = {
|
||||
(state, event) ⇒
|
||||
event match {
|
||||
case Added(data) ⇒ state.copy((data :: state.history).take(5))
|
||||
case Cleared ⇒ State(Nil)
|
||||
}
|
||||
}
|
||||
//#event-handler
|
||||
|
||||
//#behavior
|
||||
def behavior(id: String): EventSourcedBehavior[Command, Event, State] =
|
||||
EventSourcedBehavior[Command, Event, State](
|
||||
persistenceId = PersistenceId(id),
|
||||
emptyState = State(Nil),
|
||||
commandHandler = commandHandler,
|
||||
eventHandler = eventHandler)
|
||||
//#behavior
|
||||
|
||||
}
|
||||
|
||||
//#structure
|
||||
sealed trait Command
|
||||
sealed trait Event
|
||||
case class State()
|
||||
final case class State()
|
||||
|
||||
val behavior: Behavior[Command] =
|
||||
EventSourcedBehavior[Command, Event, State](
|
||||
|
|
@ -32,9 +80,6 @@ object BasicPersistentBehaviorCompileOnly {
|
|||
)
|
||||
//#structure
|
||||
|
||||
case class CommandWithSender(reply: ActorRef[String]) extends Command
|
||||
case class VeryImportantEvent() extends Event
|
||||
|
||||
//#recovery
|
||||
val recoveryBehavior: Behavior[Command] =
|
||||
EventSourcedBehavior[Command, Event, State](
|
||||
|
|
@ -99,4 +144,26 @@ object BasicPersistentBehaviorCompileOnly {
|
|||
))
|
||||
//#supervision
|
||||
|
||||
// #actor-context
|
||||
import akka.persistence.typed.scaladsl.Effect
|
||||
import akka.persistence.typed.scaladsl.EventSourcedBehavior.CommandHandler
|
||||
|
||||
val behaviorWithContext: Behavior[String] =
|
||||
Behaviors.setup { context ⇒
|
||||
EventSourcedBehavior[String, String, State](
|
||||
persistenceId = PersistenceId("myPersistenceId"),
|
||||
emptyState = new State,
|
||||
commandHandler = CommandHandler.command {
|
||||
cmd ⇒
|
||||
context.log.info("Got command {}", cmd)
|
||||
Effect.persist(cmd).thenRun { state ⇒
|
||||
context.log.info("event persisted, new state {}", state)
|
||||
}
|
||||
},
|
||||
eventHandler = {
|
||||
case (state, _) ⇒ state
|
||||
})
|
||||
}
|
||||
// #actor-context
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue