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:
Patrik Nordwall 2019-01-31 16:27:26 +01:00 committed by GitHub
parent 84bab0fbca
commit dbcc54b0b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1562 additions and 1052 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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