Merge pull request #26813 from akka/wip-26709-ask-ExpectingReply-patriknw
Type inference for EntityRef.ask, #26709
This commit is contained in:
commit
beaa80b773
17 changed files with 435 additions and 139 deletions
|
|
@ -18,9 +18,6 @@ import org.junit.AfterClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.scalatest.junit.JUnitSuite;
|
import org.scalatest.junit.JUnitSuite;
|
||||||
|
|
||||||
import scala.util.Success;
|
|
||||||
import scala.util.Try;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.CompletionStage;
|
import java.util.concurrent.CompletionStage;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
@ -82,9 +79,9 @@ public class AsyncTestingExampleTest
|
||||||
|
|
||||||
static class Message {
|
static class Message {
|
||||||
int i;
|
int i;
|
||||||
ActorRef<Try<Integer>> replyTo;
|
ActorRef<Integer> replyTo;
|
||||||
|
|
||||||
Message(int i, ActorRef<Try<Integer>> replyTo) {
|
Message(int i, ActorRef<Integer> replyTo) {
|
||||||
this.i = i;
|
this.i = i;
|
||||||
this.replyTo = replyTo;
|
this.replyTo = replyTo;
|
||||||
}
|
}
|
||||||
|
|
@ -104,10 +101,10 @@ public class AsyncTestingExampleTest
|
||||||
IntStream.range(0, messages).forEach(this::publish);
|
IntStream.range(0, messages).forEach(this::publish);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletionStage<Try<Integer>> publish(int i) {
|
private CompletionStage<Integer> publish(int i) {
|
||||||
return AskPattern.ask(
|
return AskPattern.ask(
|
||||||
publisher,
|
publisher,
|
||||||
(ActorRef<Try<Integer>> ref) -> new Message(i, ref),
|
(ActorRef<Integer> ref) -> new Message(i, ref),
|
||||||
Duration.ofSeconds(3),
|
Duration.ofSeconds(3),
|
||||||
scheduler);
|
scheduler);
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +162,7 @@ public class AsyncTestingExampleTest
|
||||||
Behavior<Message> mockedBehavior =
|
Behavior<Message> mockedBehavior =
|
||||||
Behaviors.receiveMessage(
|
Behaviors.receiveMessage(
|
||||||
message -> {
|
message -> {
|
||||||
message.replyTo.tell(new Success<>(message.i));
|
message.replyTo.tell(message.i);
|
||||||
return Behaviors.same();
|
return Behaviors.same();
|
||||||
});
|
});
|
||||||
TestProbe<Message> probe = testKit.createTestProbe();
|
TestProbe<Message> probe = testKit.createTestProbe();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import akka.actor.typed.*;
|
||||||
import akka.actor.typed.TypedActorContext;
|
import akka.actor.typed.TypedActorContext;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
|
||||||
import static akka.actor.typed.javadsl.Behaviors.*;
|
import static akka.actor.typed.javadsl.Behaviors.*;
|
||||||
|
|
||||||
|
|
@ -67,6 +68,21 @@ public class ActorCompile {
|
||||||
|
|
||||||
ActorSystem<MyMsg> system = ActorSystem.create(actor1, "Sys");
|
ActorSystem<MyMsg> system = ActorSystem.create(actor1, "Sys");
|
||||||
|
|
||||||
|
{
|
||||||
|
ActorRef<MyMsg> recipient = null;
|
||||||
|
|
||||||
|
CompletionStage<String> reply =
|
||||||
|
AskPattern.ask(
|
||||||
|
recipient, replyTo -> new MyMsgA(replyTo), Duration.ofSeconds(3), system.scheduler());
|
||||||
|
|
||||||
|
AskPattern.ask(
|
||||||
|
recipient,
|
||||||
|
(ActorRef<String> replyTo) -> new MyMsgA(replyTo),
|
||||||
|
Duration.ofSeconds(3),
|
||||||
|
system.scheduler())
|
||||||
|
.thenApply(rsp -> rsp.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Behaviors.<MyMsg>receive(
|
Behaviors.<MyMsg>receive(
|
||||||
(context, message) -> {
|
(context, message) -> {
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,15 @@ import scala.compat.java8.FutureConverters._
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
object AskPattern {
|
object AskPattern {
|
||||||
def ask[T, U](
|
|
||||||
actor: RecipientRef[T],
|
/**
|
||||||
message: JFunction[ActorRef[U], T],
|
* @tparam Req The request protocol, what the other actor accepts
|
||||||
|
* @tparam Res The response protocol, what the other actor sends back
|
||||||
|
*/
|
||||||
|
def ask[Req, Res](
|
||||||
|
actor: RecipientRef[Req],
|
||||||
|
message: JFunction[ActorRef[Res], Req],
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
scheduler: Scheduler): CompletionStage[U] =
|
scheduler: Scheduler): CompletionStage[Res] =
|
||||||
(actor.ask(message.apply)(timeout.asScala, scheduler)).toJava
|
(actor.ask(message.apply)(timeout.asScala, scheduler)).toJava
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ object AskPattern {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [[ask]]
|
* See [[ask]]
|
||||||
|
*
|
||||||
|
* @tparam Req The request protocol, what the other actor accepts
|
||||||
*/
|
*/
|
||||||
implicit final class Askable[T](val ref: RecipientRef[T]) extends AnyVal {
|
implicit final class Askable[Req](val ref: RecipientRef[Req]) extends AnyVal {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ask-pattern implements the initiator side of a request–reply protocol.
|
* The ask-pattern implements the initiator side of a request–reply protocol.
|
||||||
|
|
@ -54,13 +56,15 @@ object AskPattern {
|
||||||
* implicit val scheduler = system.scheduler
|
* implicit val scheduler = system.scheduler
|
||||||
* implicit val timeout = Timeout(3.seconds)
|
* implicit val timeout = Timeout(3.seconds)
|
||||||
* val target: ActorRef[Request] = ...
|
* val target: ActorRef[Request] = ...
|
||||||
* val f: Future[Reply] = target ? (replyTo => (Request("hello", replyTo)))
|
* val f: Future[Reply] = target ? (replyTo => Request("hello", replyTo))
|
||||||
* }}}
|
* }}}
|
||||||
*
|
*
|
||||||
* Note: it is preferrable to use the non-symbolic ask method as it easier allows for wildcards for
|
* Note: it is preferrable to use the non-symbolic ask method as it easier allows for wildcards for
|
||||||
* the `ActorRef`.
|
* the `replyTo: ActorRef`.
|
||||||
|
*
|
||||||
|
* @tparam Res The response protocol, what the other actor sends back
|
||||||
*/
|
*/
|
||||||
def ?[U](replyTo: ActorRef[U] => T)(implicit timeout: Timeout, scheduler: Scheduler): Future[U] = {
|
def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
|
||||||
ask(replyTo)(timeout, scheduler)
|
ask(replyTo)(timeout, scheduler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,15 +89,15 @@ object AskPattern {
|
||||||
* implicit val scheduler = system.scheduler
|
* implicit val scheduler = system.scheduler
|
||||||
* implicit val timeout = Timeout(3.seconds)
|
* implicit val timeout = Timeout(3.seconds)
|
||||||
* val target: ActorRef[Request] = ...
|
* val target: ActorRef[Request] = ...
|
||||||
* val f: Future[Reply] = target.ask(replyTo => (Request("hello", replyTo)))
|
* val f: Future[Reply] = target.ask(replyTo => Request("hello", replyTo))
|
||||||
* // alternatively
|
* // alternatively
|
||||||
* val f2: Future[Reply] = target.ask(Request("hello", _))
|
* val f2: Future[Reply] = target.ask(Request("hello", _))
|
||||||
* // note that the explicit type on f2 is important for the compiler
|
|
||||||
* // to understand the type of the wildcard
|
|
||||||
* }}}
|
* }}}
|
||||||
|
*
|
||||||
|
* @tparam Res The response protocol, what the other actor sends back
|
||||||
*/
|
*/
|
||||||
@silent
|
@silent
|
||||||
def ask[U](replyTo: ActorRef[U] => T)(implicit timeout: Timeout, scheduler: Scheduler): Future[U] = {
|
def ask[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
|
||||||
// We do not currently use the implicit sched, but want to require it
|
// We do not currently use the implicit sched, but want to require it
|
||||||
// because it might be needed when we move to a 'native' typed runtime, see #24219
|
// because it might be needed when we move to a 'native' typed runtime, see #24219
|
||||||
ref match {
|
ref match {
|
||||||
|
|
|
||||||
|
|
@ -468,8 +468,10 @@ object EntityTypeKey {
|
||||||
*
|
*
|
||||||
* Note that if you are inside of an actor you should prefer [[akka.actor.typed.javadsl.ActorContext.ask]]
|
* Note that if you are inside of an actor you should prefer [[akka.actor.typed.javadsl.ActorContext.ask]]
|
||||||
* as that provides better safety.
|
* as that provides better safety.
|
||||||
|
*
|
||||||
|
* @tparam Res The response protocol, what the other actor sends back
|
||||||
*/
|
*/
|
||||||
def ask[U](message: JFunction[ActorRef[U], M], timeout: Duration): CompletionStage[U]
|
def ask[Res](message: JFunction[ActorRef[Res], M], timeout: Duration): CompletionStage[Res]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
|
|
|
||||||
|
|
@ -406,8 +406,10 @@ object EntityTypeKey {
|
||||||
* }}}
|
* }}}
|
||||||
*
|
*
|
||||||
* Please note that an implicit [[akka.util.Timeout]] must be available to use this pattern.
|
* Please note that an implicit [[akka.util.Timeout]] must be available to use this pattern.
|
||||||
|
*
|
||||||
|
* @tparam Res The response protocol, what the other actor sends back
|
||||||
*/
|
*/
|
||||||
def ask[U](f: ActorRef[U] => M)(implicit timeout: Timeout): Future[U]
|
def ask[Res](f: ActorRef[Res] => M)(implicit timeout: Timeout): Future[Res]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to "ask" the [[EntityRef]] for a reply.
|
* Allows to "ask" the [[EntityRef]] for a reply.
|
||||||
|
|
@ -423,12 +425,17 @@ object EntityTypeKey {
|
||||||
*
|
*
|
||||||
* implicit val timeout = Timeout(3.seconds)
|
* implicit val timeout = Timeout(3.seconds)
|
||||||
* val target: EntityRef[Request] = ...
|
* val target: EntityRef[Request] = ...
|
||||||
* val f: Future[Reply] = target ? (Request("hello", _))
|
* val f: Future[Reply] = target ? (replyTo => Request("hello", replyTo))
|
||||||
* }}}
|
* }}}
|
||||||
*
|
*
|
||||||
* Please note that an implicit [[akka.util.Timeout]] must be available to use this pattern.
|
* Please note that an implicit [[akka.util.Timeout]] must be available to use this pattern.
|
||||||
|
*
|
||||||
|
* Note: it is preferrable to use the non-symbolic ask method as it easier allows for wildcards for
|
||||||
|
* the `replyTo: ActorRef`.
|
||||||
|
*
|
||||||
|
* @tparam Res The response protocol, what the other actor sends back
|
||||||
*/
|
*/
|
||||||
def ?[U](message: ActorRef[U] => M)(implicit timeout: Timeout): Future[U] =
|
def ?[Res](message: ActorRef[Res] => M)(implicit timeout: Timeout): Future[Res] =
|
||||||
this.ask(message)(timeout)
|
this.ask(message)(timeout)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdocs.akka.cluster.sharding.typed;
|
||||||
|
|
||||||
|
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||||
|
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||||
|
import akka.actor.typed.ActorRef;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.ClusterSharding;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.Entity;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EntityRef;
|
||||||
|
import akka.cluster.typed.Cluster;
|
||||||
|
import akka.cluster.typed.Join;
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.scalatest.junit.JUnitSuite;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static jdocs.akka.cluster.sharding.typed.AccountExampleWithEventHandlersInState.AccountEntity;
|
||||||
|
import static jdocs.akka.cluster.sharding.typed.AccountExampleWithEventHandlersInState.AccountEntity.*;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class AccountExampleTest extends JUnitSuite {
|
||||||
|
|
||||||
|
public static final Config config =
|
||||||
|
ConfigFactory.parseString(
|
||||||
|
"akka.actor.provider = cluster \n"
|
||||||
|
+ "akka.remote.netty.tcp.port = 0 \n"
|
||||||
|
+ "akka.remote.artery.canonical.port = 0 \n"
|
||||||
|
+ "akka.remote.artery.canonical.hostname = 127.0.0.1 \n"
|
||||||
|
+ "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n");
|
||||||
|
|
||||||
|
@ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config);
|
||||||
|
|
||||||
|
private ClusterSharding _sharding = null;
|
||||||
|
|
||||||
|
private ClusterSharding sharding() {
|
||||||
|
if (_sharding == null) {
|
||||||
|
// initialize first time only
|
||||||
|
Cluster cluster = Cluster.get(testKit.system());
|
||||||
|
cluster.manager().tell(new Join(cluster.selfMember().address()));
|
||||||
|
|
||||||
|
ClusterSharding sharding = ClusterSharding.get(testKit.system());
|
||||||
|
sharding.init(
|
||||||
|
Entity.ofEventSourcedEntityWithEnforcedReplies(
|
||||||
|
AccountEntity.ENTITY_TYPE_KEY, ctx -> AccountEntity.create(ctx.getEntityId())));
|
||||||
|
_sharding = sharding;
|
||||||
|
}
|
||||||
|
return _sharding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleDeposit() {
|
||||||
|
EntityRef<AccountCommand> ref = sharding().entityRefFor(AccountEntity.ENTITY_TYPE_KEY, "1");
|
||||||
|
TestProbe<OperationResult> probe = testKit.createTestProbe(OperationResult.class);
|
||||||
|
ref.tell(new CreateAccount(probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Deposit(BigDecimal.valueOf(100), probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Deposit(BigDecimal.valueOf(10), probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWithdraw() {
|
||||||
|
EntityRef<AccountCommand> ref = sharding().entityRefFor(AccountEntity.ENTITY_TYPE_KEY, "2");
|
||||||
|
TestProbe<OperationResult> probe = testKit.createTestProbe(OperationResult.class);
|
||||||
|
ref.tell(new CreateAccount(probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Deposit(BigDecimal.valueOf(100), probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Withdraw(BigDecimal.valueOf(10), probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectWithdrawOverdraft() {
|
||||||
|
EntityRef<AccountCommand> ref = sharding().entityRefFor(AccountEntity.ENTITY_TYPE_KEY, "3");
|
||||||
|
TestProbe<OperationResult> probe = testKit.createTestProbe(OperationResult.class);
|
||||||
|
ref.tell(new CreateAccount(probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Deposit(BigDecimal.valueOf(100), probe.getRef()));
|
||||||
|
probe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Withdraw(BigDecimal.valueOf(110), probe.getRef()));
|
||||||
|
probe.expectMessageClass(Rejected.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleGetBalance() {
|
||||||
|
EntityRef<AccountCommand> ref = sharding().entityRefFor(AccountEntity.ENTITY_TYPE_KEY, "4");
|
||||||
|
TestProbe<OperationResult> opProbe = testKit.createTestProbe(OperationResult.class);
|
||||||
|
ref.tell(new CreateAccount(opProbe.getRef()));
|
||||||
|
opProbe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
ref.tell(new Deposit(BigDecimal.valueOf(100), opProbe.getRef()));
|
||||||
|
opProbe.expectMessage(Confirmed.INSTANCE);
|
||||||
|
|
||||||
|
TestProbe<CurrentBalance> getProbe = testKit.createTestProbe(CurrentBalance.class);
|
||||||
|
ref.tell(new GetBalance(getProbe.getRef()));
|
||||||
|
assertEquals(
|
||||||
|
BigDecimal.valueOf(100), getProbe.expectMessageClass(CurrentBalance.class).balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void beUsableWithAsk() throws Exception {
|
||||||
|
Duration timeout = Duration.ofSeconds(3);
|
||||||
|
EntityRef<AccountCommand> ref = sharding().entityRefFor(AccountEntity.ENTITY_TYPE_KEY, "5");
|
||||||
|
CompletionStage<OperationResult> createResult = ref.ask(CreateAccount::new, timeout);
|
||||||
|
assertEquals(Confirmed.INSTANCE, createResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// above works because then the response type is inferred by the lhs type
|
||||||
|
// below requires (ActorRef<OperationResult> replyTo)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Confirmed.INSTANCE,
|
||||||
|
ref.ask(
|
||||||
|
(ActorRef<OperationResult> replyTo) ->
|
||||||
|
new Deposit(BigDecimal.valueOf(100), replyTo),
|
||||||
|
timeout)
|
||||||
|
.toCompletableFuture()
|
||||||
|
.get(3, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Confirmed.INSTANCE,
|
||||||
|
ref.ask(
|
||||||
|
(ActorRef<OperationResult> replyTo) ->
|
||||||
|
new Withdraw(BigDecimal.valueOf(10), replyTo),
|
||||||
|
timeout)
|
||||||
|
.toCompletableFuture()
|
||||||
|
.get(3, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
BigDecimal balance =
|
||||||
|
ref.ask(GetBalance::new, timeout)
|
||||||
|
.thenApply(currentBalance -> currentBalance.balance)
|
||||||
|
.toCompletableFuture()
|
||||||
|
.get(3, TimeUnit.SECONDS);
|
||||||
|
assertEquals(BigDecimal.valueOf(90), balance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,19 +2,19 @@
|
||||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package jdocs.akka.persistence.typed;
|
package jdocs.akka.cluster.sharding.typed;
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef;
|
import akka.actor.typed.ActorRef;
|
||||||
import akka.actor.typed.Behavior;
|
import akka.actor.typed.Behavior;
|
||||||
import akka.actor.typed.javadsl.ActorContext;
|
|
||||||
import akka.actor.typed.javadsl.Behaviors;
|
import akka.actor.typed.javadsl.Behaviors;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EntityTypeKey;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EventSourcedEntityWithEnforcedReplies;
|
||||||
import akka.persistence.typed.ExpectingReply;
|
import akka.persistence.typed.ExpectingReply;
|
||||||
import akka.persistence.typed.PersistenceId;
|
import akka.persistence.typed.PersistenceId;
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder;
|
||||||
import akka.persistence.typed.javadsl.EventHandler;
|
import akka.persistence.typed.javadsl.EventHandler;
|
||||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
|
||||||
import akka.persistence.typed.javadsl.ReplyEffect;
|
import akka.persistence.typed.javadsl.ReplyEffect;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
@ -29,9 +29,12 @@ public interface AccountExampleWithCommandHandlersInState {
|
||||||
|
|
||||||
// #account-entity
|
// #account-entity
|
||||||
public class AccountEntity
|
public class AccountEntity
|
||||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
extends EventSourcedEntityWithEnforcedReplies<
|
||||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||||
|
|
||||||
|
public static final EntityTypeKey<AccountCommand> ENTITY_TYPE_KEY =
|
||||||
|
EntityTypeKey.create(AccountCommand.class, "Account");
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||||
|
|
||||||
|
|
@ -226,12 +229,12 @@ public interface AccountExampleWithCommandHandlersInState {
|
||||||
|
|
||||||
public static class ClosedAccount implements Account {}
|
public static class ClosedAccount implements Account {}
|
||||||
|
|
||||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
public static AccountEntity create(String accountNumber) {
|
||||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
return new AccountEntity(accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountEntity(ActorContext<AccountCommand> context, String accountNumber) {
|
public AccountEntity(String accountNumber) {
|
||||||
super(new PersistenceId(accountNumber));
|
super(ENTITY_TYPE_KEY, accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -2,38 +2,38 @@
|
||||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package jdocs.akka.persistence.typed;
|
package jdocs.akka.cluster.sharding.typed;
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef;
|
import akka.actor.typed.ActorRef;
|
||||||
import akka.actor.typed.Behavior;
|
import akka.cluster.sharding.typed.javadsl.EntityTypeKey;
|
||||||
import akka.actor.typed.javadsl.ActorContext;
|
import akka.cluster.sharding.typed.javadsl.EventSourcedEntityWithEnforcedReplies;
|
||||||
import akka.actor.typed.javadsl.Behaviors;
|
|
||||||
import akka.persistence.typed.ExpectingReply;
|
import akka.persistence.typed.ExpectingReply;
|
||||||
import akka.persistence.typed.PersistenceId;
|
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder;
|
||||||
import akka.persistence.typed.javadsl.ReplyEffect;
|
|
||||||
import akka.persistence.typed.javadsl.EventHandler;
|
import akka.persistence.typed.javadsl.EventHandler;
|
||||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
import akka.persistence.typed.javadsl.ReplyEffect;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
* 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
|
* 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
|
* delegate to methods in the class - replies of various types, using ExpectingReply and
|
||||||
* ExpectingReply and EventSourcedBehaviorWithEnforcedReplies
|
* EventSourcedEntityWithEnforcedReplies
|
||||||
*/
|
*/
|
||||||
public interface AccountExampleWithEventHandlersInState {
|
public interface AccountExampleWithEventHandlersInState {
|
||||||
|
|
||||||
// #account-entity
|
// #account-entity
|
||||||
// #withEnforcedReplies
|
// #withEnforcedReplies
|
||||||
public class AccountEntity
|
public class AccountEntity
|
||||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
extends EventSourcedEntityWithEnforcedReplies<
|
||||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||||
// #withEnforcedReplies
|
// #withEnforcedReplies
|
||||||
|
|
||||||
|
public static final EntityTypeKey<AccountCommand> ENTITY_TYPE_KEY =
|
||||||
|
EntityTypeKey.create(AccountCommand.class, "Account");
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
// #reply-command
|
// #reply-command
|
||||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||||
|
|
@ -195,12 +195,12 @@ public interface AccountExampleWithEventHandlersInState {
|
||||||
|
|
||||||
public static class ClosedAccount implements Account {}
|
public static class ClosedAccount implements Account {}
|
||||||
|
|
||||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
public static AccountEntity create(String accountNumber) {
|
||||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
return new AccountEntity(accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountEntity(ActorContext<AccountCommand> context, String accountNumber) {
|
public AccountEntity(String accountNumber) {
|
||||||
super(new PersistenceId(accountNumber));
|
super(ENTITY_TYPE_KEY, accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -2,19 +2,19 @@
|
||||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package jdocs.akka.persistence.typed;
|
package jdocs.akka.cluster.sharding.typed;
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef;
|
import akka.actor.typed.ActorRef;
|
||||||
import akka.actor.typed.Behavior;
|
import akka.actor.typed.Behavior;
|
||||||
import akka.actor.typed.javadsl.ActorContext;
|
|
||||||
import akka.actor.typed.javadsl.Behaviors;
|
import akka.actor.typed.javadsl.Behaviors;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EntityTypeKey;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EventSourcedEntityWithEnforcedReplies;
|
||||||
import akka.persistence.typed.ExpectingReply;
|
import akka.persistence.typed.ExpectingReply;
|
||||||
import akka.persistence.typed.PersistenceId;
|
import akka.persistence.typed.PersistenceId;
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder;
|
||||||
import akka.persistence.typed.javadsl.EventHandler;
|
import akka.persistence.typed.javadsl.EventHandler;
|
||||||
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
import akka.persistence.typed.javadsl.EventHandlerBuilder;
|
||||||
import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies;
|
|
||||||
import akka.persistence.typed.javadsl.ReplyEffect;
|
import akka.persistence.typed.javadsl.ReplyEffect;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
@ -23,15 +23,18 @@ import java.math.BigDecimal;
|
||||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
* 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
|
* 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,
|
* handlers that delegate to methods in the EventSourcedBehavior class - replies of various types,
|
||||||
* using ExpectingReply and EventSourcedBehaviorWithEnforcedReplies
|
* using ExpectingReply and EventSourcedEntityWithEnforcedReplies
|
||||||
*/
|
*/
|
||||||
public interface AccountExampleWithMutableState {
|
public interface AccountExampleWithMutableState {
|
||||||
|
|
||||||
// #account-entity
|
// #account-entity
|
||||||
public class AccountEntity
|
public class AccountEntity
|
||||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
extends EventSourcedEntityWithEnforcedReplies<
|
||||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||||
|
|
||||||
|
public static final EntityTypeKey<AccountCommand> ENTITY_TYPE_KEY =
|
||||||
|
EntityTypeKey.create(AccountCommand.class, "Account");
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||||
|
|
||||||
|
|
@ -189,12 +192,12 @@ public interface AccountExampleWithMutableState {
|
||||||
|
|
||||||
public static class ClosedAccount implements Account {}
|
public static class ClosedAccount implements Account {}
|
||||||
|
|
||||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
public static AccountEntity create(String accountNumber) {
|
||||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
return new AccountEntity(accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountEntity(ActorContext<AccountCommand> context, String accountNumber) {
|
public AccountEntity(String accountNumber) {
|
||||||
super(new PersistenceId(accountNumber));
|
super(ENTITY_TYPE_KEY, accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2018-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package jdocs.akka.persistence.typed;
|
package jdocs.akka.cluster.sharding.typed;
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef;
|
import akka.actor.typed.ActorRef;
|
||||||
import akka.actor.typed.Behavior;
|
import akka.actor.typed.Behavior;
|
||||||
import akka.actor.typed.javadsl.ActorContext;
|
import akka.actor.typed.javadsl.ActorContext;
|
||||||
import akka.actor.typed.javadsl.Behaviors;
|
import akka.actor.typed.javadsl.Behaviors;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EntityTypeKey;
|
||||||
|
import akka.cluster.sharding.typed.javadsl.EventSourcedEntityWithEnforcedReplies;
|
||||||
import akka.persistence.typed.ExpectingReply;
|
import akka.persistence.typed.ExpectingReply;
|
||||||
import akka.persistence.typed.PersistenceId;
|
import akka.persistence.typed.PersistenceId;
|
||||||
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
import akka.persistence.typed.javadsl.CommandHandlerWithReply;
|
||||||
|
|
@ -23,15 +25,18 @@ import java.math.BigDecimal;
|
||||||
* Bank account example illustrating: - different state classes representing the lifecycle of the
|
* 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 -
|
* 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
|
* command handlers that delegate to methods in the EventSourcedBehavior class - replies of various
|
||||||
* types, using ExpectingReply and EventSourcedBehaviorWithEnforcedReplies
|
* types, using ExpectingReply and EventSourcedEntityWithEnforcedReplies
|
||||||
*/
|
*/
|
||||||
public interface AccountExampleWithNullState {
|
public interface AccountExampleWithNullState {
|
||||||
|
|
||||||
// #account-entity
|
// #account-entity
|
||||||
public class AccountEntity
|
public class AccountEntity
|
||||||
extends EventSourcedBehaviorWithEnforcedReplies<
|
extends EventSourcedEntityWithEnforcedReplies<
|
||||||
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
AccountEntity.AccountCommand, AccountEntity.AccountEvent, AccountEntity.Account> {
|
||||||
|
|
||||||
|
public static final EntityTypeKey<AccountCommand> ENTITY_TYPE_KEY =
|
||||||
|
EntityTypeKey.create(AccountCommand.class, "Account");
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
interface AccountCommand<Reply> extends ExpectingReply<Reply> {}
|
||||||
|
|
||||||
|
|
@ -187,12 +192,12 @@ public interface AccountExampleWithNullState {
|
||||||
|
|
||||||
public static class ClosedAccount implements Account {}
|
public static class ClosedAccount implements Account {}
|
||||||
|
|
||||||
public static Behavior<AccountCommand> behavior(String accountNumber) {
|
public static AccountEntity create(String accountNumber) {
|
||||||
return Behaviors.setup(context -> new AccountEntity(context, accountNumber));
|
return new AccountEntity(accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountEntity(ActorContext<AccountCommand> context, String accountNumber) {
|
public AccountEntity(String accountNumber) {
|
||||||
super(new PersistenceId(accountNumber));
|
super(ENTITY_TYPE_KEY, accountNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docs.akka.cluster.sharding.typed
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.ClusterSharding
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.Entity
|
||||||
|
import akka.cluster.typed.Cluster
|
||||||
|
import akka.cluster.typed.Join
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import org.scalatest.WordSpecLike
|
||||||
|
|
||||||
|
object AccountExampleSpec {
|
||||||
|
val config = ConfigFactory.parseString("""
|
||||||
|
akka.actor.provider = cluster
|
||||||
|
|
||||||
|
akka.remote.netty.tcp.port = 0
|
||||||
|
akka.remote.artery.canonical.port = 0
|
||||||
|
akka.remote.artery.canonical.hostname = 127.0.0.1
|
||||||
|
|
||||||
|
akka.persistence.journal.plugin = "akka.persistence.journal.inmem"
|
||||||
|
""")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountExampleSpec extends ScalaTestWithActorTestKit(AccountExampleSpec.config) with WordSpecLike {
|
||||||
|
import AccountExampleWithEventHandlersInState.AccountEntity
|
||||||
|
import AccountExampleWithEventHandlersInState.AccountEntity._
|
||||||
|
|
||||||
|
private val sharding = ClusterSharding(system)
|
||||||
|
|
||||||
|
override def beforeAll(): Unit = {
|
||||||
|
super.beforeAll()
|
||||||
|
Cluster(system).manager ! Join(Cluster(system).selfMember.address)
|
||||||
|
|
||||||
|
sharding.init(Entity(AccountEntity.TypeKey, ctx => AccountEntity(ctx.entityId)))
|
||||||
|
}
|
||||||
|
|
||||||
|
"Account example" must {
|
||||||
|
|
||||||
|
"handle Deposit" in {
|
||||||
|
val probe = createTestProbe[OperationResult]()
|
||||||
|
val ref = ClusterSharding(system).entityRefFor(AccountEntity.TypeKey, "1")
|
||||||
|
ref ! CreateAccount(probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
ref ! Deposit(100, probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
ref ! Deposit(10, probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
"handle Withdraw" in {
|
||||||
|
// OperationResult is the expected reply type for these commands, but it should also be
|
||||||
|
// possible to use the super type AccountCommandReply
|
||||||
|
val probe = createTestProbe[AccountCommandReply]()
|
||||||
|
val ref = ClusterSharding(system).entityRefFor(AccountEntity.TypeKey, "2")
|
||||||
|
ref ! CreateAccount(probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
ref ! Deposit(100, probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
ref ! Withdraw(10, probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
"reject Withdraw overdraft" in {
|
||||||
|
val probe = createTestProbe[OperationResult]()
|
||||||
|
val ref = ClusterSharding(system).entityRefFor(AccountEntity.TypeKey, "3")
|
||||||
|
ref ! CreateAccount(probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
ref ! Deposit(100, probe.ref)
|
||||||
|
probe.expectMessage(Confirmed)
|
||||||
|
ref ! Withdraw(110, probe.ref)
|
||||||
|
probe.expectMessageType[Rejected]
|
||||||
|
}
|
||||||
|
|
||||||
|
"handle GetBalance" in {
|
||||||
|
val opProbe = createTestProbe[OperationResult]()
|
||||||
|
val ref = ClusterSharding(system).entityRefFor(AccountEntity.TypeKey, "4")
|
||||||
|
ref ! CreateAccount(opProbe.ref)
|
||||||
|
opProbe.expectMessage(Confirmed)
|
||||||
|
ref ! Deposit(100, opProbe.ref)
|
||||||
|
opProbe.expectMessage(Confirmed)
|
||||||
|
|
||||||
|
val getProbe = createTestProbe[CurrentBalance]()
|
||||||
|
ref ! GetBalance(getProbe.ref)
|
||||||
|
getProbe.expectMessage(CurrentBalance(100))
|
||||||
|
}
|
||||||
|
|
||||||
|
"be usable with ask" in {
|
||||||
|
val ref = ClusterSharding(system).entityRefFor(AccountEntity.TypeKey, "5")
|
||||||
|
val createResult: Future[OperationResult] = ref.ask(CreateAccount(_))
|
||||||
|
createResult.futureValue should ===(Confirmed)
|
||||||
|
implicit val ec: ExecutionContext = testKit.system.executionContext
|
||||||
|
|
||||||
|
// Errors are shown in IntelliJ Scala plugin 2019.1.6, but compiles with Scala 2.12.8.
|
||||||
|
// Ok in IntelliJ if using ref.ask[OperationResult].
|
||||||
|
ref.ask(Deposit(100, _)).futureValue should ===(Confirmed)
|
||||||
|
ref.ask(Withdraw(10, _)).futureValue should ===(Confirmed)
|
||||||
|
ref.ask(GetBalance(_)).map(_.balance).futureValue should ===(90)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2017-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package docs.akka.persistence.typed
|
package docs.akka.cluster.sharding.typed
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.Behavior
|
import akka.actor.typed.Behavior
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.EntityTypeKey
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.EventSourcedEntity
|
||||||
import akka.persistence.typed.ExpectingReply
|
import akka.persistence.typed.ExpectingReply
|
||||||
import akka.persistence.typed.PersistenceId
|
|
||||||
import akka.persistence.typed.scaladsl.Effect
|
import akka.persistence.typed.scaladsl.Effect
|
||||||
import akka.persistence.typed.scaladsl.EventSourcedBehavior
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bank account example illustrating:
|
* Bank account example illustrating:
|
||||||
|
|
@ -20,18 +20,18 @@ import akka.persistence.typed.scaladsl.EventSourcedBehavior
|
||||||
*/
|
*/
|
||||||
object AccountExampleWithCommandHandlersInState {
|
object AccountExampleWithCommandHandlersInState {
|
||||||
|
|
||||||
//##account-entity
|
//#account-entity
|
||||||
object AccountEntity {
|
object AccountEntity {
|
||||||
// Command
|
// Command
|
||||||
sealed trait AccountCommand[Reply] extends ExpectingReply[Reply]
|
sealed trait AccountCommand[Reply] extends ExpectingReply[Reply]
|
||||||
final case class CreateAccount()(override val replyTo: ActorRef[OperationResult])
|
final case class CreateAccount(override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class Deposit(amount: BigDecimal)(override val replyTo: ActorRef[OperationResult])
|
final case class Deposit(amount: BigDecimal, override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class Withdraw(amount: BigDecimal)(override val replyTo: ActorRef[OperationResult])
|
final case class Withdraw(amount: BigDecimal, override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class GetBalance()(override val replyTo: ActorRef[CurrentBalance]) extends AccountCommand[CurrentBalance]
|
final case class GetBalance(override val replyTo: ActorRef[CurrentBalance]) extends AccountCommand[CurrentBalance]
|
||||||
final case class CloseAccount()(override val replyTo: ActorRef[OperationResult])
|
final case class CloseAccount(override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
|
|
@ -79,16 +79,14 @@ object AccountExampleWithCommandHandlersInState {
|
||||||
|
|
||||||
override def applyCommand(cmd: AccountCommand[_]): ReplyEffect =
|
override def applyCommand(cmd: AccountCommand[_]): ReplyEffect =
|
||||||
cmd match {
|
cmd match {
|
||||||
case c @ Deposit(amount) =>
|
case c: Deposit =>
|
||||||
Effect.persist(Deposited(amount)).thenReply(c)(_ => Confirmed)
|
Effect.persist(Deposited(c.amount)).thenReply(c)(_ => Confirmed)
|
||||||
|
|
||||||
case c @ Withdraw(amount) =>
|
case c: Withdraw =>
|
||||||
if (canWithdraw(amount)) {
|
if (canWithdraw(c.amount))
|
||||||
Effect.persist(Withdrawn(amount)).thenReply(c)(_ => Confirmed)
|
Effect.persist(Withdrawn(c.amount)).thenReply(c)(_ => Confirmed)
|
||||||
|
else
|
||||||
} else {
|
Effect.reply(c)(Rejected(s"Insufficient balance $balance to be able to withdraw ${c.amount}"))
|
||||||
Effect.reply(c)(Rejected(s"Insufficient balance $balance to be able to withdraw $amount"))
|
|
||||||
}
|
|
||||||
|
|
||||||
case c: GetBalance =>
|
case c: GetBalance =>
|
||||||
Effect.reply(c)(CurrentBalance(balance))
|
Effect.reply(c)(CurrentBalance(balance))
|
||||||
|
|
@ -134,15 +132,19 @@ object AccountExampleWithCommandHandlersInState {
|
||||||
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
|
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
|
||||||
}
|
}
|
||||||
|
|
||||||
def behavior(accountNumber: String): Behavior[AccountCommand[AccountCommandReply]] = {
|
val TypeKey: EntityTypeKey[AccountCommand[_]] =
|
||||||
EventSourcedBehavior.withEnforcedReplies[AccountCommand[AccountCommandReply], AccountEvent, Account](
|
EntityTypeKey[AccountCommand[_]]("Account")
|
||||||
PersistenceId(s"Account|$accountNumber"),
|
|
||||||
|
def apply(accountNumber: String): Behavior[AccountCommand[_]] = {
|
||||||
|
EventSourcedEntity.withEnforcedReplies[AccountCommand[_], AccountEvent, Account](
|
||||||
|
TypeKey,
|
||||||
|
accountNumber,
|
||||||
EmptyAccount,
|
EmptyAccount,
|
||||||
(state, cmd) => state.applyCommand(cmd),
|
(state, cmd) => state.applyCommand(cmd),
|
||||||
(state, event) => state.applyEvent(event))
|
(state, event) => state.applyEvent(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//##account-entity
|
//#account-entity
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2017-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package docs.akka.persistence.typed
|
package docs.akka.cluster.sharding.typed
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.Behavior
|
import akka.actor.typed.Behavior
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.EntityTypeKey
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.EventSourcedEntity
|
||||||
import akka.persistence.typed.ExpectingReply
|
import akka.persistence.typed.ExpectingReply
|
||||||
import akka.persistence.typed.PersistenceId
|
|
||||||
import akka.persistence.typed.scaladsl.Effect
|
import akka.persistence.typed.scaladsl.Effect
|
||||||
import akka.persistence.typed.scaladsl.EventSourcedBehavior
|
|
||||||
import akka.persistence.typed.scaladsl.ReplyEffect
|
import akka.persistence.typed.scaladsl.ReplyEffect
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,18 +26,18 @@ object AccountExampleWithEventHandlersInState {
|
||||||
object AccountEntity {
|
object AccountEntity {
|
||||||
// Command
|
// Command
|
||||||
//#reply-command
|
//#reply-command
|
||||||
sealed trait AccountCommand[Reply] extends ExpectingReply[Reply]
|
sealed trait AccountCommand[Reply <: AccountCommandReply] extends ExpectingReply[Reply]
|
||||||
//#reply-command
|
//#reply-command
|
||||||
final case class CreateAccount()(override val replyTo: ActorRef[OperationResult])
|
final case class CreateAccount(override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class Deposit(amount: BigDecimal)(override val replyTo: ActorRef[OperationResult])
|
final case class Deposit(amount: BigDecimal, override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
//#reply-command
|
//#reply-command
|
||||||
final case class Withdraw(amount: BigDecimal)(override val replyTo: ActorRef[OperationResult])
|
final case class Withdraw(amount: BigDecimal, override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
//#reply-command
|
//#reply-command
|
||||||
final case class GetBalance()(override val replyTo: ActorRef[CurrentBalance]) extends AccountCommand[CurrentBalance]
|
final case class GetBalance(override val replyTo: ActorRef[CurrentBalance]) extends AccountCommand[CurrentBalance]
|
||||||
final case class CloseAccount()(override val replyTo: ActorRef[OperationResult])
|
final case class CloseAccount(override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
|
|
@ -89,17 +89,16 @@ object AccountExampleWithEventHandlersInState {
|
||||||
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
|
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val TypeKey: EntityTypeKey[AccountCommand[_]] =
|
||||||
|
EntityTypeKey[AccountCommand[_]]("Account")
|
||||||
|
|
||||||
// Note that after defining command, event and state classes you would probably start here when writing this.
|
// Note that after defining command, event and state classes you would probably start here when writing this.
|
||||||
// When filling in the parameters of EventSourcedBehavior.apply you can use IntelliJ alt+Enter > createValue
|
// When filling in the parameters of EventSourcedBehavior.apply you can use IntelliJ alt+Enter > createValue
|
||||||
// to generate the stub with types for the command and event handlers.
|
// to generate the stub with types for the command and event handlers.
|
||||||
|
|
||||||
//#withEnforcedReplies
|
//#withEnforcedReplies
|
||||||
def behavior(accountNumber: String): Behavior[AccountCommand[AccountCommandReply]] = {
|
def apply(accountNumber: String): Behavior[AccountCommand[_]] = {
|
||||||
EventSourcedBehavior.withEnforcedReplies(
|
EventSourcedEntity.withEnforcedReplies(TypeKey, accountNumber, EmptyAccount, commandHandler, eventHandler)
|
||||||
PersistenceId(s"Account|$accountNumber"),
|
|
||||||
EmptyAccount,
|
|
||||||
commandHandler,
|
|
||||||
eventHandler)
|
|
||||||
}
|
}
|
||||||
//#withEnforcedReplies
|
//#withEnforcedReplies
|
||||||
|
|
||||||
|
|
@ -148,12 +147,10 @@ object AccountExampleWithEventHandlersInState {
|
||||||
|
|
||||||
//#reply
|
//#reply
|
||||||
private def withdraw(acc: OpenedAccount, cmd: Withdraw): ReplyEffect[AccountEvent, Account] = {
|
private def withdraw(acc: OpenedAccount, cmd: Withdraw): ReplyEffect[AccountEvent, Account] = {
|
||||||
if (acc.canWithdraw(cmd.amount)) {
|
if (acc.canWithdraw(cmd.amount))
|
||||||
Effect.persist(Withdrawn(cmd.amount)).thenReply(cmd)(_ => Confirmed)
|
Effect.persist(Withdrawn(cmd.amount)).thenReply(cmd)(_ => Confirmed)
|
||||||
|
else
|
||||||
} else {
|
|
||||||
Effect.reply(cmd)(Rejected(s"Insufficient balance ${acc.balance} to be able to withdraw ${cmd.amount}"))
|
Effect.reply(cmd)(Rejected(s"Insufficient balance ${acc.balance} to be able to withdraw ${cmd.amount}"))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//#reply
|
//#reply
|
||||||
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2017-2019 Lightbend Inc. <https://www.lightbend.com>
|
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package docs.akka.persistence.typed
|
package docs.akka.cluster.sharding.typed
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.Behavior
|
import akka.actor.typed.Behavior
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.EntityTypeKey
|
||||||
|
import akka.cluster.sharding.typed.scaladsl.EventSourcedEntity
|
||||||
import akka.persistence.typed.ExpectingReply
|
import akka.persistence.typed.ExpectingReply
|
||||||
import akka.persistence.typed.PersistenceId
|
|
||||||
import akka.persistence.typed.scaladsl.Effect
|
import akka.persistence.typed.scaladsl.Effect
|
||||||
import akka.persistence.typed.scaladsl.EventSourcedBehavior
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bank account example illustrating:
|
* Bank account example illustrating:
|
||||||
|
|
@ -24,14 +24,14 @@ object AccountExampleWithOptionState {
|
||||||
object AccountEntity {
|
object AccountEntity {
|
||||||
// Command
|
// Command
|
||||||
sealed trait AccountCommand[Reply] extends ExpectingReply[Reply]
|
sealed trait AccountCommand[Reply] extends ExpectingReply[Reply]
|
||||||
final case class CreateAccount()(override val replyTo: ActorRef[OperationResult])
|
final case class CreateAccount(override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class Deposit(amount: BigDecimal)(override val replyTo: ActorRef[OperationResult])
|
final case class Deposit(amount: BigDecimal, override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class Withdraw(amount: BigDecimal)(override val replyTo: ActorRef[OperationResult])
|
final case class Withdraw(amount: BigDecimal, override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
final case class GetBalance()(override val replyTo: ActorRef[CurrentBalance]) extends AccountCommand[CurrentBalance]
|
final case class GetBalance(override val replyTo: ActorRef[CurrentBalance]) extends AccountCommand[CurrentBalance]
|
||||||
final case class CloseAccount()(override val replyTo: ActorRef[OperationResult])
|
final case class CloseAccount(override val replyTo: ActorRef[OperationResult])
|
||||||
extends AccountCommand[OperationResult]
|
extends AccountCommand[OperationResult]
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
|
|
@ -63,16 +63,14 @@ object AccountExampleWithOptionState {
|
||||||
|
|
||||||
override def applyCommand(cmd: AccountCommand[_]): ReplyEffect =
|
override def applyCommand(cmd: AccountCommand[_]): ReplyEffect =
|
||||||
cmd match {
|
cmd match {
|
||||||
case c @ Deposit(amount) =>
|
case c: Deposit =>
|
||||||
Effect.persist(Deposited(amount)).thenReply(c)(_ => Confirmed)
|
Effect.persist(Deposited(c.amount)).thenReply(c)(_ => Confirmed)
|
||||||
|
|
||||||
case c @ Withdraw(amount) =>
|
case c: Withdraw =>
|
||||||
if (canWithdraw(amount)) {
|
if (canWithdraw(c.amount))
|
||||||
Effect.persist(Withdrawn(amount)).thenReply(c)(_ => Confirmed)
|
Effect.persist(Withdrawn(c.amount)).thenReply(c)(_ => Confirmed)
|
||||||
|
else
|
||||||
} else {
|
Effect.reply(c)(Rejected(s"Insufficient balance $balance to be able to withdraw ${c.amount}"))
|
||||||
Effect.reply(c)(Rejected(s"Insufficient balance $balance to be able to withdraw $amount"))
|
|
||||||
}
|
|
||||||
|
|
||||||
case c: GetBalance =>
|
case c: GetBalance =>
|
||||||
Effect.reply(c)(CurrentBalance(balance))
|
Effect.reply(c)(CurrentBalance(balance))
|
||||||
|
|
@ -118,9 +116,13 @@ object AccountExampleWithOptionState {
|
||||||
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
|
throw new IllegalStateException(s"unexpected event [$event] in state [ClosedAccount]")
|
||||||
}
|
}
|
||||||
|
|
||||||
def behavior(accountNumber: String): Behavior[AccountCommand[AccountCommandReply]] = {
|
val TypeKey: EntityTypeKey[AccountCommand[_]] =
|
||||||
EventSourcedBehavior.withEnforcedReplies[AccountCommand[AccountCommandReply], AccountEvent, Option[Account]](
|
EntityTypeKey[AccountCommand[_]]("Account")
|
||||||
PersistenceId(s"Account|$accountNumber"),
|
|
||||||
|
def apply(accountNumber: String): Behavior[AccountCommand[_]] = {
|
||||||
|
EventSourcedEntity.withEnforcedReplies[AccountCommand[_], AccountEvent, Option[Account]](
|
||||||
|
TypeKey,
|
||||||
|
accountNumber,
|
||||||
None,
|
None,
|
||||||
(state, cmd) =>
|
(state, cmd) =>
|
||||||
state match {
|
state match {
|
||||||
|
|
@ -14,10 +14,10 @@ Here we are using a bank account as the example domain. It has 3 state classes t
|
||||||
of the account; `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.
|
of the account; `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #account-entity }
|
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.scala) { #account-entity }
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithEventHandlersInState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #account-entity }
|
: @@snip [AccountExampleWithEventHandlersInState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.java) { #account-entity }
|
||||||
|
|
||||||
@scala[Notice how the `eventHandler` delegates to the `applyEvent` in the `Account` (state), which is implemented
|
@scala[Notice how the `eventHandler` delegates to the `applyEvent` in the `Account` (state), which is implemented
|
||||||
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||||
|
|
@ -29,10 +29,10 @@ in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||||
We can take the previous bank account example one step further by handling the commands in the state too.
|
We can take the previous bank account example one step further by handling the commands in the state too.
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AccountExampleWithCommandHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithCommandHandlersInState.scala) { #account-entity }
|
: @@snip [AccountExampleWithCommandHandlersInState.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleWithCommandHandlersInState.scala) { #account-entity }
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithCommandHandlersInState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithCommandHandlersInState.java) { #account-entity }
|
: @@snip [AccountExampleWithCommandHandlersInState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithCommandHandlersInState.java) { #account-entity }
|
||||||
|
|
||||||
@scala[Notice how the command handler is delegating to `applyCommand` in the `Account` (state), which is implemented
|
@scala[Notice how the command handler is delegating to `applyCommand` in the `Account` (state), which is implemented
|
||||||
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
in the concrete `EmptyAccount`, `OpenedAccount`, and `ClosedAccount`.]
|
||||||
|
|
@ -52,10 +52,10 @@ illustrates using `null` as the `emptyState`.]
|
||||||
is then used in command and event handlers at the outer layer before delegating to the state or other methods.]
|
is then used in command and event handlers at the outer layer before delegating to the state or other methods.]
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AccountExampleWithOptionState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithOptionState.scala) { #account-entity }
|
: @@snip [AccountExampleWithOptionState.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleWithOptionState.scala) { #account-entity }
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithNullState.java) { #account-entity }
|
: @@snip [AccountExampleWithNullState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithNullState.java) { #account-entity }
|
||||||
|
|
||||||
@@@ div { .group-java }
|
@@@ div { .group-java }
|
||||||
## Mutable state
|
## Mutable state
|
||||||
|
|
@ -69,6 +69,6 @@ e.g. as a reply to a command. Messages must be immutable to avoid concurrency pr
|
||||||
The above examples are using immutable state classes and below is corresponding example with mutable state.
|
The above examples are using immutable state classes and below is corresponding example with mutable state.
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithMutableState.java) { #account-entity }
|
: @@snip [AccountExampleWithNullState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithMutableState.java) { #account-entity }
|
||||||
|
|
||||||
@@@
|
@@@
|
||||||
|
|
|
||||||
|
|
@ -285,19 +285,19 @@ created with @scala[`Effect.reply`]@java[`Effects().reply`], @scala[`Effect.noRe
|
||||||
@scala[`Effect.thenReply`]@java[`Effects().thenReply`], or @scala[`Effect.thenNoReply`]@java[`Effects().thenNoReply`].
|
@scala[`Effect.thenReply`]@java[`Effects().thenReply`], or @scala[`Effect.thenNoReply`]@java[`Effects().thenNoReply`].
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #withEnforcedReplies }
|
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.scala) { #withEnforcedReplies }
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #withEnforcedReplies }
|
: @@snip [AccountExampleWithNullState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.java) { #withEnforcedReplies }
|
||||||
|
|
||||||
The commands must implement `ExpectingReply` to include the @scala[`ActorRef[ReplyMessageType]`]@java[`ActorRef<ReplyMessageType>`]
|
The commands must implement `ExpectingReply` to include the @scala[`ActorRef[ReplyMessageType]`]@java[`ActorRef<ReplyMessageType>`]
|
||||||
in a standardized way.
|
in a standardized way.
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #reply-command }
|
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.scala) { #reply-command }
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #reply-command }
|
: @@snip [AccountExampleWithNullState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.java) { #reply-command }
|
||||||
|
|
||||||
The `ReplyEffect` is created with @scala[`Effect.reply`]@java[`Effects().reply`], @scala[`Effect.noReply`]@java[`Effects().noReply`],
|
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[`Effect.thenReply`]@java[`Effects().thenReply`], or @scala[`Effect.thenNoReply`]@java[`Effects().thenNoReply`].
|
||||||
|
|
@ -306,10 +306,10 @@ The `ReplyEffect` is created with @scala[`Effect.reply`]@java[`Effects().reply`]
|
||||||
`EventSourcedBehaviorWithEnforcedReplies`], as opposed to newCommandHandlerBuilder when using `EventSourcedBehavior`.]
|
`EventSourcedBehaviorWithEnforcedReplies`], as opposed to newCommandHandlerBuilder when using `EventSourcedBehavior`.]
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/AccountExampleWithEventHandlersInState.scala) { #reply }
|
: @@snip [AccountExampleWithEventHandlersInState.scala](/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.scala) { #reply }
|
||||||
|
|
||||||
Java
|
Java
|
||||||
: @@snip [AccountExampleWithNullState.java](/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/AccountExampleWithEventHandlersInState.java) { #reply }
|
: @@snip [AccountExampleWithNullState.java](/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleWithEventHandlersInState.java) { #reply }
|
||||||
|
|
||||||
These effects will send the reply message even when @scala[`EventSourcedBehavior.withEnforcedReplies`]@java[`EventSourcedBehaviorWithEnforcedReplies`]
|
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.
|
is not used, but then there will be no compilation errors if the reply decision is left out.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue