Java auction example (#29443)
This commit is contained in:
parent
742352caba
commit
c945fbd7a1
4 changed files with 425 additions and 4 deletions
|
|
@ -18,11 +18,17 @@ We model those operations as commands to be sent to the auction actor:
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #commands }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #commands }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/java/jdocs/akka/persistence/typed/ReplicatedAuctionExampleTest.java) { #commands }
|
||||||
|
|
||||||
The events:
|
The events:
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #events }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #events }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/java/jdocs/akka/persistence/typed/ReplicatedAuctionExampleTest.java) { #events }
|
||||||
|
|
||||||
The winner does not have to pay the highest bid but only enough to beat the second highest so the `highestCounterOffer` is in the `AuctionFinished` event.
|
The winner does not have to pay the highest bid but only enough to beat the second highest so the `highestCounterOffer` is in the `AuctionFinished` event.
|
||||||
|
|
||||||
Let's have a look at the auction entity that will handle incoming commands:
|
Let's have a look at the auction entity that will handle incoming commands:
|
||||||
|
|
@ -30,6 +36,9 @@ Let's have a look at the auction entity that will handle incoming commands:
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #command-handler }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #command-handler }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/java/jdocs/akka/persistence/typed/ReplicatedAuctionExampleTest.java) { #command-handler }
|
||||||
|
|
||||||
There is nothing specific to Replicated Event Sourcing about the command handler. It is the same as a command handler for a standard `EventSourcedBehavior`.
|
There is nothing specific to Replicated Event Sourcing about the command handler. It is the same as a command handler for a standard `EventSourcedBehavior`.
|
||||||
For `OfferBid` and `AuctionFinished` we do nothing more than to emit
|
For `OfferBid` and `AuctionFinished` we do nothing more than to emit
|
||||||
events corresponding to the command. For `GetHighestBid` we respond with details from the state. Note, that we overwrite the actual
|
events corresponding to the command. For `GetHighestBid` we respond with details from the state. Note, that we overwrite the actual
|
||||||
|
|
@ -43,21 +52,29 @@ an `initialBid`.
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #setup }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #setup }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/java/jdocs/akka/persistence/typed/ReplicatedAuctionExampleTest.java) { #setup }
|
||||||
|
|
||||||
|
@@@ div { .group-scala }
|
||||||
|
|
||||||
The auction moves through the following phases:
|
The auction moves through the following phases:
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #phase }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #phase }
|
||||||
|
|
||||||
|
@@@
|
||||||
|
|
||||||
The closing and closed states are to model waiting for all replicas to see the result of the auction before
|
The closing and closed states are to model waiting for all replicas to see the result of the auction before
|
||||||
actually closing the action.
|
actually closing the action.
|
||||||
|
|
||||||
Let's have a look at our state class, `AuctionState` which also represents the CRDT in our example.
|
Let's have a look at our state class, `AuctionState` which also represents the CRDT in our example.
|
||||||
|
|
||||||
|
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #state }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #state }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/java/jdocs/akka/persistence/typed/ReplicatedAuctionExampleTest.java) { #state }
|
||||||
|
|
||||||
The state consists of a flag that keeps track of whether the auction is still active, the currently highest bid,
|
The state consists of a flag that keeps track of whether the auction is still active, the currently highest bid,
|
||||||
and the highest counter offer so far.
|
and the highest counter offer so far.
|
||||||
|
|
||||||
|
|
@ -97,6 +114,9 @@ In the event handler above, when recovery is not running, it calls `eventTrigger
|
||||||
Scala
|
Scala
|
||||||
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #event-triggers }
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/scala/docs/akka/persistence/typed/ReplicatedAuctionExampleSpec.scala) { #event-triggers }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [AuctionExample](/akka-persistence-typed-tests/src/test/java/jdocs/akka/persistence/typed/ReplicatedAuctionExampleTest.java) { #event-triggers }
|
||||||
|
|
||||||
The event trigger uses the `ReplicationContext` to decide when to trigger the Finish of the action.
|
The event trigger uses the `ReplicationContext` to decide when to trigger the Finish of the action.
|
||||||
When a replica saves the `AuctionFinished` event it checks whether it should close the auction.
|
When a replica saves the `AuctionFinished` event it checks whether it should close the auction.
|
||||||
For the close to happen the replica must be the one designated to close and all replicas must have
|
For the close to happen the replica must be the one designated to close and all replicas must have
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,400 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdocs.akka.persistence.typed;
|
||||||
|
|
||||||
|
import akka.actor.testkit.typed.javadsl.LogCapturing;
|
||||||
|
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||||
|
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||||
|
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.testkit.PersistenceTestKitPlugin;
|
||||||
|
import akka.persistence.testkit.query.scaladsl.PersistenceTestKitReadJournal;
|
||||||
|
import akka.persistence.typed.ReplicaId;
|
||||||
|
import akka.persistence.typed.javadsl.*;
|
||||||
|
import akka.serialization.jackson.CborSerializable;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.scalatestplus.junit.JUnitSuite;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static jdocs.akka.persistence.typed.ReplicatedAuctionExample.*;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class ReplicatedAuctionExampleTest extends JUnitSuite {
|
||||||
|
@ClassRule
|
||||||
|
public static final TestKitJunitResource testKit =
|
||||||
|
new TestKitJunitResource(PersistenceTestKitPlugin.getInstance().config());
|
||||||
|
|
||||||
|
@Rule public final LogCapturing logCapturing = new LogCapturing();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void auctionExample() {
|
||||||
|
AuctionSetup setupA =
|
||||||
|
new AuctionSetup(
|
||||||
|
"old-skis",
|
||||||
|
new Bid("chbatey", 12, Instant.now(), R1),
|
||||||
|
Instant.now().plusSeconds(10),
|
||||||
|
true);
|
||||||
|
|
||||||
|
AuctionSetup setupB =
|
||||||
|
new AuctionSetup(
|
||||||
|
"old-skis",
|
||||||
|
new Bid("chbatey", 12, Instant.now(), R1),
|
||||||
|
Instant.now().plusSeconds(10),
|
||||||
|
false);
|
||||||
|
|
||||||
|
ActorRef<Command> replicaA = testKit.spawn(create(setupA, R1));
|
||||||
|
ActorRef<Command> replicaB = testKit.spawn(create(setupA, R2));
|
||||||
|
|
||||||
|
replicaA.tell(new OfferBid("me", 100));
|
||||||
|
replicaA.tell(new OfferBid("me", 99));
|
||||||
|
replicaA.tell(new OfferBid("me", 202));
|
||||||
|
|
||||||
|
TestProbe<Bid> replyProbe = testKit.createTestProbe();
|
||||||
|
replyProbe.awaitAssert(
|
||||||
|
() -> {
|
||||||
|
replicaA.tell(new GetHighestBid(replyProbe.ref()));
|
||||||
|
Bid bid = replyProbe.expectMessageClass(Bid.class);
|
||||||
|
assertEquals(bid.offer, 202);
|
||||||
|
return bid;
|
||||||
|
});
|
||||||
|
|
||||||
|
replicaA.tell(Finish.INSTANCE);
|
||||||
|
|
||||||
|
TestProbe<Boolean> finishProbe = testKit.createTestProbe();
|
||||||
|
finishProbe.awaitAssert(
|
||||||
|
() -> {
|
||||||
|
replicaA.tell(new IsClosed(finishProbe.ref()));
|
||||||
|
return finishProbe.expectMessage(true);
|
||||||
|
});
|
||||||
|
finishProbe.awaitAssert(
|
||||||
|
() -> {
|
||||||
|
replicaB.tell(new IsClosed(finishProbe.ref()));
|
||||||
|
return finishProbe.expectMessage(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplicatedAuctionExample
|
||||||
|
extends ReplicatedEventSourcedBehavior<Command, Event, AuctionState> {
|
||||||
|
|
||||||
|
public static ReplicaId R1 = new ReplicaId("R1");
|
||||||
|
public static ReplicaId R2 = new ReplicaId("R2");
|
||||||
|
|
||||||
|
public static Set<ReplicaId> ALL_REPLICAS = new HashSet<>(Arrays.asList(R1, R2));
|
||||||
|
private final ActorContext<Command> context;
|
||||||
|
private final AuctionSetup setup;
|
||||||
|
|
||||||
|
public static Behavior<Command> create(AuctionSetup setup, ReplicaId replica) {
|
||||||
|
return Behaviors.setup(
|
||||||
|
ctx ->
|
||||||
|
ReplicatedEventSourcing.withSharedJournal(
|
||||||
|
setup.name,
|
||||||
|
replica,
|
||||||
|
ALL_REPLICAS,
|
||||||
|
PersistenceTestKitReadJournal.Identifier(),
|
||||||
|
replicationCtx -> new ReplicatedAuctionExample(replicationCtx, ctx, setup)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplicatedAuctionExample(
|
||||||
|
ReplicationContext replicationContext, ActorContext<Command> context, AuctionSetup setup) {
|
||||||
|
super(replicationContext);
|
||||||
|
this.context = context;
|
||||||
|
this.setup = setup;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#setup
|
||||||
|
static class AuctionSetup {
|
||||||
|
final String name;
|
||||||
|
final Bid initialBid; // the initial bid is the minimum price bidden at start time by the owner
|
||||||
|
final Instant closingAt;
|
||||||
|
final boolean responsibleForClosing;
|
||||||
|
|
||||||
|
public AuctionSetup(
|
||||||
|
String name, Bid initialBid, Instant closingAt, boolean responsibleForClosing) {
|
||||||
|
this.name = name;
|
||||||
|
this.initialBid = initialBid;
|
||||||
|
this.closingAt = closingAt;
|
||||||
|
this.responsibleForClosing = responsibleForClosing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#setup
|
||||||
|
|
||||||
|
public static final class Bid implements CborSerializable {
|
||||||
|
public final String bidder;
|
||||||
|
public final int offer;
|
||||||
|
public final Instant timestamp;
|
||||||
|
public final ReplicaId originReplica;
|
||||||
|
|
||||||
|
public Bid(String bidder, int offer, Instant timestamp, ReplicaId originReplica) {
|
||||||
|
this.bidder = bidder;
|
||||||
|
this.offer = offer;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.originReplica = originReplica;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#commands
|
||||||
|
interface Command extends CborSerializable {}
|
||||||
|
public enum Finish implements Command {
|
||||||
|
INSTANCE
|
||||||
|
}
|
||||||
|
public static final class OfferBid implements Command {
|
||||||
|
public final String bidder;
|
||||||
|
public final int offer;
|
||||||
|
|
||||||
|
public OfferBid(String bidder, int offer) {
|
||||||
|
this.bidder = bidder;
|
||||||
|
this.offer = offer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static final class GetHighestBid implements Command {
|
||||||
|
public final ActorRef<Bid> replyTo;
|
||||||
|
|
||||||
|
public GetHighestBid(ActorRef<Bid> replyTo) {
|
||||||
|
this.replyTo = replyTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static final class IsClosed implements Command {
|
||||||
|
public final ActorRef<Boolean> replyTo;
|
||||||
|
public IsClosed(ActorRef<Boolean> replyTo) {
|
||||||
|
this.replyTo = replyTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private enum Close implements Command {
|
||||||
|
INSTANCE
|
||||||
|
}
|
||||||
|
//#commands
|
||||||
|
|
||||||
|
//#events
|
||||||
|
interface Event extends CborSerializable {}
|
||||||
|
|
||||||
|
public static final class BidRegistered implements Event {
|
||||||
|
public final Bid bid;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BidRegistered(Bid bid) {
|
||||||
|
this.bid = bid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class AuctionFinished implements Event {
|
||||||
|
public final ReplicaId atReplica;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public AuctionFinished(ReplicaId atReplica) {
|
||||||
|
this.atReplica = atReplica;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class WinnerDecided implements Event {
|
||||||
|
public final ReplicaId atReplica;
|
||||||
|
public final Bid winningBid;
|
||||||
|
public final int amount;
|
||||||
|
|
||||||
|
public WinnerDecided(ReplicaId atReplica, Bid winningBid, int amount) {
|
||||||
|
this.atReplica = atReplica;
|
||||||
|
this.winningBid = winningBid;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#events
|
||||||
|
|
||||||
|
//#state
|
||||||
|
static class AuctionState implements CborSerializable {
|
||||||
|
|
||||||
|
final boolean stillRunning;
|
||||||
|
final Bid highestBid;
|
||||||
|
final int highestCounterOffer;
|
||||||
|
final Set<String> finishedAtDc;
|
||||||
|
|
||||||
|
AuctionState(
|
||||||
|
boolean stillRunning, Bid highestBid, int highestCounterOffer, Set<String> finishedAtDc) {
|
||||||
|
this.stillRunning = stillRunning;
|
||||||
|
this.highestBid = highestBid;
|
||||||
|
this.highestCounterOffer = highestCounterOffer;
|
||||||
|
this.finishedAtDc = finishedAtDc;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuctionState withNewHighestBid(Bid bid) {
|
||||||
|
assert (stillRunning);
|
||||||
|
assert (isHigherBid(bid, highestBid));
|
||||||
|
return new AuctionState(
|
||||||
|
stillRunning, bid, highestBid.offer, finishedAtDc); // keep last highest bid around
|
||||||
|
}
|
||||||
|
|
||||||
|
AuctionState withTooLowBid(Bid bid) {
|
||||||
|
assert (stillRunning);
|
||||||
|
assert (isHigherBid(highestBid, bid));
|
||||||
|
return new AuctionState(
|
||||||
|
stillRunning, highestBid, Math.max(highestCounterOffer, bid.offer), finishedAtDc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Boolean isHigherBid(Bid first, Bid second) {
|
||||||
|
return first.offer > second.offer
|
||||||
|
|| (first.offer == second.offer && first.timestamp.isBefore(second.timestamp))
|
||||||
|
|| // if equal, first one wins
|
||||||
|
// If timestamps are equal, choose by dc where the offer was submitted
|
||||||
|
// In real auctions, this last comparison should be deterministic but unpredictable, so
|
||||||
|
// that submitting to a
|
||||||
|
// particular DC would not be an advantage.
|
||||||
|
(first.offer == second.offer
|
||||||
|
&& first.timestamp.equals(second.timestamp)
|
||||||
|
&& first.originReplica.id().compareTo(second.originReplica.id()) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuctionState addFinishedAtReplica(String replica) {
|
||||||
|
Set<String> s = new HashSet<>(finishedAtDc);
|
||||||
|
s.add(replica);
|
||||||
|
return new AuctionState(
|
||||||
|
false, highestBid, highestCounterOffer, Collections.unmodifiableSet(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuctionState close() {
|
||||||
|
return new AuctionState(false, highestBid, highestCounterOffer, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return !stillRunning && finishedAtDc.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#state
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuctionState emptyState() {
|
||||||
|
return new AuctionState(true, setup.initialBid, setup.initialBid.offer, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
//#command-handler
|
||||||
|
@Override
|
||||||
|
public CommandHandler<Command, Event, AuctionState> commandHandler() {
|
||||||
|
|
||||||
|
CommandHandlerBuilder<Command, Event, AuctionState> builder = newCommandHandlerBuilder();
|
||||||
|
|
||||||
|
// running
|
||||||
|
builder
|
||||||
|
.forState(state -> state.stillRunning)
|
||||||
|
.onCommand(
|
||||||
|
OfferBid.class,
|
||||||
|
(state, bid) ->
|
||||||
|
Effect()
|
||||||
|
.persist(
|
||||||
|
new BidRegistered(
|
||||||
|
new Bid(
|
||||||
|
bid.bidder,
|
||||||
|
bid.offer,
|
||||||
|
Instant.ofEpochMilli(
|
||||||
|
this.getReplicationContext().currentTimeMillis()),
|
||||||
|
this.getReplicationContext().replicaId()))))
|
||||||
|
.onCommand(
|
||||||
|
GetHighestBid.class,
|
||||||
|
(state, get) -> {
|
||||||
|
get.replyTo.tell(state.highestBid);
|
||||||
|
return Effect().none();
|
||||||
|
})
|
||||||
|
.onCommand(
|
||||||
|
Finish.class,
|
||||||
|
(state, finish) ->
|
||||||
|
Effect().persist(new AuctionFinished(getReplicationContext().replicaId())))
|
||||||
|
.onCommand(Close.class, (state, close) -> Effect().unhandled())
|
||||||
|
.onCommand(
|
||||||
|
IsClosed.class,
|
||||||
|
(state, get) -> {
|
||||||
|
get.replyTo.tell(false);
|
||||||
|
return Effect().none();
|
||||||
|
});
|
||||||
|
|
||||||
|
// finished
|
||||||
|
builder
|
||||||
|
.forAnyState()
|
||||||
|
.onCommand(OfferBid.class, (state, bid) -> Effect().unhandled())
|
||||||
|
.onCommand(
|
||||||
|
GetHighestBid.class,
|
||||||
|
(state, get) -> {
|
||||||
|
get.replyTo.tell(state.highestBid);
|
||||||
|
return Effect().none();
|
||||||
|
})
|
||||||
|
.onCommand(
|
||||||
|
Finish.class,
|
||||||
|
(state, finish) ->
|
||||||
|
Effect().persist(new AuctionFinished(getReplicationContext().replicaId())))
|
||||||
|
.onCommand(
|
||||||
|
Close.class,
|
||||||
|
(state, close) ->
|
||||||
|
Effect()
|
||||||
|
.persist(
|
||||||
|
new WinnerDecided(
|
||||||
|
getReplicationContext().replicaId(),
|
||||||
|
state.highestBid,
|
||||||
|
state.highestCounterOffer)))
|
||||||
|
.onCommand(
|
||||||
|
IsClosed.class,
|
||||||
|
(state, get) -> {
|
||||||
|
get.replyTo.tell(state.isClosed());
|
||||||
|
return Effect().none();
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
//#command-handler
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventHandler<AuctionState, Event> eventHandler() {
|
||||||
|
return newEventHandlerBuilder()
|
||||||
|
.forAnyState()
|
||||||
|
.onEvent(
|
||||||
|
BidRegistered.class,
|
||||||
|
(state, event) -> {
|
||||||
|
if (AuctionState.isHigherBid(event.bid, state.highestBid)) {
|
||||||
|
return state.withNewHighestBid(event.bid);
|
||||||
|
} else {
|
||||||
|
return state.withTooLowBid(event.bid);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onEvent(
|
||||||
|
AuctionFinished.class,
|
||||||
|
(state, event) -> {
|
||||||
|
AuctionState newState = state.addFinishedAtReplica(event.atReplica.id());
|
||||||
|
if (state.isClosed()) return state; // already closed
|
||||||
|
else if (!getReplicationContext().recoveryRunning()) {
|
||||||
|
eventTriggers(event, newState);
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
})
|
||||||
|
.onEvent(WinnerDecided.class, (state, event) -> state.close())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#event-triggers
|
||||||
|
private void eventTriggers(AuctionFinished event, AuctionState newState) {
|
||||||
|
if (newState.finishedAtDc.contains(getReplicationContext().replicaId().id())) {
|
||||||
|
if (shouldClose(newState)) {
|
||||||
|
context.getSelf().tell(Close.INSTANCE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.getSelf().tell(Finish.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#event-triggers
|
||||||
|
|
||||||
|
private boolean shouldClose(AuctionState state) {
|
||||||
|
return setup.responsibleForClosing
|
||||||
|
&& !state.isClosed()
|
||||||
|
&& getReplicationContext().getAllReplicas().stream()
|
||||||
|
.map(ReplicaId::id)
|
||||||
|
.collect(Collectors.toSet())
|
||||||
|
.equals(state.finishedAtDc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ object ReplicatedAuctionExampleSpec {
|
||||||
|
|
||||||
type MoneyAmount = Int
|
type MoneyAmount = Int
|
||||||
|
|
||||||
case class Bid(bidder: String, offer: MoneyAmount, timestamp: Instant, originDc: ReplicaId)
|
case class Bid(bidder: String, offer: MoneyAmount, timestamp: Instant, originReplica: ReplicaId)
|
||||||
|
|
||||||
//#commands
|
//#commands
|
||||||
sealed trait AuctionCommand
|
sealed trait AuctionCommand
|
||||||
|
|
@ -89,8 +89,8 @@ object ReplicatedAuctionExampleSpec {
|
||||||
// If timestamps are equal, choose by dc where the offer was submitted
|
// If timestamps are equal, choose by dc where the offer was submitted
|
||||||
// In real auctions, this last comparison should be deterministic but unpredictable, so that submitting to a
|
// In real auctions, this last comparison should be deterministic but unpredictable, so that submitting to a
|
||||||
// particular DC would not be an advantage.
|
// particular DC would not be an advantage.
|
||||||
(first.offer == second.offer && first.timestamp.equals(second.timestamp) && first.originDc.id
|
(first.offer == second.offer && first.timestamp.equals(second.timestamp) && first.originReplica.id
|
||||||
.compareTo(second.originDc.id) < 0)
|
.compareTo(second.originReplica.id) < 0)
|
||||||
}
|
}
|
||||||
//#state
|
//#state
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,7 @@ lazy val persistenceTypedTests = akkaModule("akka-persistence-typed-tests")
|
||||||
.dependsOn(persistenceTyped, persistenceTestkit % "test", actorTestkitTyped % "test", jackson % "test->test")
|
.dependsOn(persistenceTyped, persistenceTestkit % "test", actorTestkitTyped % "test", jackson % "test->test")
|
||||||
.settings(AkkaBuild.mayChangeSettings)
|
.settings(AkkaBuild.mayChangeSettings)
|
||||||
.settings(Dependencies.persistenceTypedTests)
|
.settings(Dependencies.persistenceTypedTests)
|
||||||
|
.settings(javacOptions += "-parameters") // for Jackson
|
||||||
.disablePlugins(MimaPlugin)
|
.disablePlugins(MimaPlugin)
|
||||||
.enablePlugins(NoPublish)
|
.enablePlugins(NoPublish)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue