rename akka-docs dir to docs (#62)
This commit is contained in:
parent
13dce0ec69
commit
708da8caec
1029 changed files with 2033 additions and 2039 deletions
103
docs/src/test/java/jdocs/ddata/DataBot.java
Normal file
103
docs/src/test/java/jdocs/ddata/DataBot.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata;
|
||||
|
||||
// #data-bot
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.apache.pekko.actor.AbstractActor;
|
||||
import org.apache.pekko.actor.ActorRef;
|
||||
import org.apache.pekko.actor.Cancellable;
|
||||
import org.apache.pekko.cluster.Cluster;
|
||||
import org.apache.pekko.cluster.ddata.DistributedData;
|
||||
import org.apache.pekko.cluster.ddata.Key;
|
||||
import org.apache.pekko.cluster.ddata.ORSet;
|
||||
import org.apache.pekko.cluster.ddata.ORSetKey;
|
||||
import org.apache.pekko.cluster.ddata.Replicator;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.Changed;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.Subscribe;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.Update;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.UpdateResponse;
|
||||
import org.apache.pekko.cluster.ddata.SelfUniqueAddress;
|
||||
import org.apache.pekko.event.Logging;
|
||||
import org.apache.pekko.event.LoggingAdapter;
|
||||
|
||||
public class DataBot extends AbstractActor {
|
||||
|
||||
private static final String TICK = "tick";
|
||||
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
private final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
private final SelfUniqueAddress node =
|
||||
DistributedData.get(getContext().getSystem()).selfUniqueAddress();
|
||||
|
||||
private final Cancellable tickTask =
|
||||
getContext()
|
||||
.getSystem()
|
||||
.scheduler()
|
||||
.scheduleWithFixedDelay(
|
||||
Duration.ofSeconds(5),
|
||||
Duration.ofSeconds(5),
|
||||
getSelf(),
|
||||
TICK,
|
||||
getContext().getDispatcher(),
|
||||
getSelf());
|
||||
|
||||
private final Key<ORSet<String>> dataKey = ORSetKey.create("key");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, a -> a.equals(TICK), a -> receiveTick())
|
||||
.match(
|
||||
Changed.class,
|
||||
c -> c.key().equals(dataKey),
|
||||
c -> receiveChanged((Changed<ORSet<String>>) c))
|
||||
.match(UpdateResponse.class, r -> receiveUpdateResponse())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void receiveTick() {
|
||||
String s = String.valueOf((char) ThreadLocalRandom.current().nextInt(97, 123));
|
||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
// add
|
||||
log.info("Adding: {}", s);
|
||||
Update<ORSet<String>> update =
|
||||
new Update<>(dataKey, ORSet.create(), Replicator.writeLocal(), curr -> curr.add(node, s));
|
||||
replicator.tell(update, getSelf());
|
||||
} else {
|
||||
// remove
|
||||
log.info("Removing: {}", s);
|
||||
Update<ORSet<String>> update =
|
||||
new Update<>(
|
||||
dataKey, ORSet.create(), Replicator.writeLocal(), curr -> curr.remove(node, s));
|
||||
replicator.tell(update, getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveChanged(Changed<ORSet<String>> c) {
|
||||
ORSet<String> data = c.dataValue();
|
||||
log.info("Current elements: {}", data.getElements());
|
||||
}
|
||||
|
||||
private void receiveUpdateResponse() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
Subscribe<ORSet<String>> subscribe = new Subscribe<>(dataKey, getSelf());
|
||||
replicator.tell(subscribe, ActorRef.noSender());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStop() {
|
||||
tickTask.cancel();
|
||||
}
|
||||
}
|
||||
// #data-bot
|
||||
469
docs/src/test/java/jdocs/ddata/DistributedDataDocTest.java
Normal file
469
docs/src/test/java/jdocs/ddata/DistributedDataDocTest.java
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.pekko.actor.*;
|
||||
import org.apache.pekko.testkit.javadsl.TestKit;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import docs.ddata.DistributedDataDocSpec;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import org.apache.pekko.cluster.ddata.*;
|
||||
import org.apache.pekko.japi.pf.ReceiveBuilder;
|
||||
|
||||
import static org.apache.pekko.cluster.ddata.Replicator.*;
|
||||
|
||||
@SuppressWarnings({"unchecked", "unused"})
|
||||
public class DistributedDataDocTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system =
|
||||
ActorSystem.create(
|
||||
"DistributedDataDocTest", ConfigFactory.parseString(DistributedDataDocSpec.config()));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
TestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
static
|
||||
// #update
|
||||
class DemonstrateUpdate extends AbstractActor {
|
||||
final SelfUniqueAddress node =
|
||||
DistributedData.get(getContext().getSystem()).selfUniqueAddress();
|
||||
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
final Key<GSet<String>> set1Key = GSetKey.create("set1");
|
||||
final Key<ORSet<String>> set2Key = ORSetKey.create("set2");
|
||||
final Key<Flag> activeFlagKey = FlagKey.create("active");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
ReceiveBuilder b = receiveBuilder();
|
||||
|
||||
b.matchEquals(
|
||||
"demonstrate update",
|
||||
msg -> {
|
||||
replicator.tell(
|
||||
new Replicator.Update<PNCounter>(
|
||||
counter1Key,
|
||||
PNCounter.create(),
|
||||
Replicator.writeLocal(),
|
||||
curr -> curr.increment(node, 1)),
|
||||
getSelf());
|
||||
|
||||
final WriteConsistency writeTo3 = new WriteTo(3, Duration.ofSeconds(1));
|
||||
replicator.tell(
|
||||
new Replicator.Update<GSet<String>>(
|
||||
set1Key, GSet.create(), writeTo3, curr -> curr.add("hello")),
|
||||
getSelf());
|
||||
|
||||
final WriteConsistency writeMajority = new WriteMajority(Duration.ofSeconds(5));
|
||||
replicator.tell(
|
||||
new Replicator.Update<ORSet<String>>(
|
||||
set2Key, ORSet.create(), writeMajority, curr -> curr.add(node, "hello")),
|
||||
getSelf());
|
||||
|
||||
final WriteConsistency writeAll = new WriteAll(Duration.ofSeconds(5));
|
||||
replicator.tell(
|
||||
new Replicator.Update<Flag>(
|
||||
activeFlagKey, Flag.create(), writeAll, curr -> curr.switchOn()),
|
||||
getSelf());
|
||||
});
|
||||
// #update
|
||||
|
||||
// #update-response1
|
||||
b.match(
|
||||
UpdateSuccess.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
// ok
|
||||
});
|
||||
// #update-response1
|
||||
|
||||
// #update-response2
|
||||
b.match(
|
||||
UpdateSuccess.class,
|
||||
a -> a.key().equals(set1Key),
|
||||
a -> {
|
||||
// ok
|
||||
})
|
||||
.match(
|
||||
UpdateTimeout.class,
|
||||
a -> a.key().equals(set1Key),
|
||||
a -> {
|
||||
// write to 3 nodes failed within 1.second
|
||||
});
|
||||
// #update-response2
|
||||
|
||||
// #update
|
||||
return b.build();
|
||||
}
|
||||
}
|
||||
// #update
|
||||
|
||||
static
|
||||
// #update-request-context
|
||||
class DemonstrateUpdateWithRequestContext extends AbstractActor {
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final WriteConsistency writeTwo = new WriteTo(2, Duration.ofSeconds(3));
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(
|
||||
String.class,
|
||||
a -> a.equals("increment"),
|
||||
a -> {
|
||||
// incoming command to increase the counter
|
||||
Optional<Object> reqContext = Optional.of(getSender());
|
||||
Replicator.Update<PNCounter> upd =
|
||||
new Replicator.Update<PNCounter>(
|
||||
counter1Key,
|
||||
PNCounter.create(),
|
||||
writeTwo,
|
||||
reqContext,
|
||||
curr -> curr.increment(node, 1));
|
||||
replicator.tell(upd, getSelf());
|
||||
})
|
||||
.match(
|
||||
UpdateSuccess.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell("ack", getSelf());
|
||||
})
|
||||
.match(
|
||||
UpdateTimeout.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell("nack", getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
// #update-request-context
|
||||
|
||||
static
|
||||
// #get
|
||||
class DemonstrateGet extends AbstractActor {
|
||||
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
final Key<GSet<String>> set1Key = GSetKey.create("set1");
|
||||
final Key<ORSet<String>> set2Key = ORSetKey.create("set2");
|
||||
final Key<Flag> activeFlagKey = FlagKey.create("active");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
ReceiveBuilder b = receiveBuilder();
|
||||
|
||||
b.matchEquals(
|
||||
"demonstrate get",
|
||||
msg -> {
|
||||
replicator.tell(
|
||||
new Replicator.Get<PNCounter>(counter1Key, Replicator.readLocal()), getSelf());
|
||||
|
||||
final ReadConsistency readFrom3 = new ReadFrom(3, Duration.ofSeconds(1));
|
||||
replicator.tell(new Replicator.Get<GSet<String>>(set1Key, readFrom3), getSelf());
|
||||
|
||||
final ReadConsistency readMajority = new ReadMajority(Duration.ofSeconds(5));
|
||||
replicator.tell(new Replicator.Get<ORSet<String>>(set2Key, readMajority), getSelf());
|
||||
|
||||
final ReadConsistency readAll = new ReadAll(Duration.ofSeconds(5));
|
||||
replicator.tell(new Replicator.Get<Flag>(activeFlagKey, readAll), getSelf());
|
||||
});
|
||||
// #get
|
||||
|
||||
// #get-response1
|
||||
b.match(
|
||||
GetSuccess.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
GetSuccess<PNCounter> g = a;
|
||||
BigInteger value = g.dataValue().getValue();
|
||||
})
|
||||
.match(
|
||||
NotFound.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
// key counter1 does not exist
|
||||
});
|
||||
// #get-response1
|
||||
|
||||
// #get-response2
|
||||
b.match(
|
||||
GetSuccess.class,
|
||||
a -> a.key().equals(set1Key),
|
||||
a -> {
|
||||
GetSuccess<GSet<String>> g = a;
|
||||
Set<String> value = g.dataValue().getElements();
|
||||
})
|
||||
.match(
|
||||
GetFailure.class,
|
||||
a -> a.key().equals(set1Key),
|
||||
a -> {
|
||||
// read from 3 nodes failed within 1.second
|
||||
})
|
||||
.match(
|
||||
NotFound.class,
|
||||
a -> a.key().equals(set1Key),
|
||||
a -> {
|
||||
// key set1 does not exist
|
||||
});
|
||||
// #get-response2
|
||||
|
||||
// #get
|
||||
return b.build();
|
||||
}
|
||||
}
|
||||
// #get
|
||||
|
||||
static
|
||||
// #get-request-context
|
||||
class DemonstrateGetWithRequestContext extends AbstractActor {
|
||||
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final ReadConsistency readTwo = new ReadFrom(2, Duration.ofSeconds(3));
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(
|
||||
String.class,
|
||||
a -> a.equals("get-count"),
|
||||
a -> {
|
||||
// incoming request to retrieve current value of the counter
|
||||
Optional<Object> reqContext = Optional.of(getSender());
|
||||
replicator.tell(new Replicator.Get<PNCounter>(counter1Key, readTwo), getSelf());
|
||||
})
|
||||
.match(
|
||||
GetSuccess.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
GetSuccess<PNCounter> g = a;
|
||||
long value = g.dataValue().getValue().longValue();
|
||||
replyTo.tell(value, getSelf());
|
||||
})
|
||||
.match(
|
||||
GetFailure.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell(-1L, getSelf());
|
||||
})
|
||||
.match(
|
||||
NotFound.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
ActorRef replyTo = (ActorRef) a.getRequest().get();
|
||||
replyTo.tell(0L, getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
// #get-request-context
|
||||
|
||||
static
|
||||
// #subscribe
|
||||
class DemonstrateSubscribe extends AbstractActor {
|
||||
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
|
||||
BigInteger currentValue = BigInteger.valueOf(0);
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(
|
||||
Changed.class,
|
||||
a -> a.key().equals(counter1Key),
|
||||
a -> {
|
||||
Changed<PNCounter> g = a;
|
||||
currentValue = g.dataValue().getValue();
|
||||
})
|
||||
.match(
|
||||
String.class,
|
||||
a -> a.equals("get-count"),
|
||||
a -> {
|
||||
// incoming request to retrieve current value of the counter
|
||||
getSender().tell(currentValue, getSender());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
// subscribe to changes of the Counter1Key value
|
||||
replicator.tell(new Subscribe<PNCounter>(counter1Key, getSelf()), ActorRef.noSender());
|
||||
}
|
||||
}
|
||||
// #subscribe
|
||||
|
||||
static
|
||||
// #delete
|
||||
class DemonstrateDelete extends AbstractActor {
|
||||
final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
|
||||
final Key<PNCounter> counter1Key = PNCounterKey.create("counter1");
|
||||
final Key<ORSet<String>> set2Key = ORSetKey.create("set2");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals(
|
||||
"demonstrate delete",
|
||||
msg -> {
|
||||
replicator.tell(
|
||||
new Delete<PNCounter>(counter1Key, Replicator.writeLocal()), getSelf());
|
||||
|
||||
final WriteConsistency writeMajority = new WriteMajority(Duration.ofSeconds(5));
|
||||
replicator.tell(new Delete<PNCounter>(counter1Key, writeMajority), getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
// #delete
|
||||
|
||||
public void demonstratePNCounter() {
|
||||
// #pncounter
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final PNCounter c0 = PNCounter.create();
|
||||
final PNCounter c1 = c0.increment(node, 1);
|
||||
final PNCounter c2 = c1.increment(node, 7);
|
||||
final PNCounter c3 = c2.decrement(node, 2);
|
||||
System.out.println(c3.value()); // 6
|
||||
// #pncounter
|
||||
}
|
||||
|
||||
public void demonstratePNCounterMap() {
|
||||
// #pncountermap
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final PNCounterMap<String> m0 = PNCounterMap.create();
|
||||
final PNCounterMap<String> m1 = m0.increment(node, "a", 7);
|
||||
final PNCounterMap<String> m2 = m1.decrement(node, "a", 2);
|
||||
final PNCounterMap<String> m3 = m2.increment(node, "b", 1);
|
||||
System.out.println(m3.get("a")); // 5
|
||||
System.out.println(m3.getEntries());
|
||||
// #pncountermap
|
||||
}
|
||||
|
||||
public void demonstrateGSet() {
|
||||
// #gset
|
||||
final GSet<String> s0 = GSet.create();
|
||||
final GSet<String> s1 = s0.add("a");
|
||||
final GSet<String> s2 = s1.add("b").add("c");
|
||||
if (s2.contains("a")) System.out.println(s2.getElements()); // a, b, c
|
||||
// #gset
|
||||
}
|
||||
|
||||
public void demonstrateORSet() {
|
||||
// #orset
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final ORSet<String> s0 = ORSet.create();
|
||||
final ORSet<String> s1 = s0.add(node, "a");
|
||||
final ORSet<String> s2 = s1.add(node, "b");
|
||||
final ORSet<String> s3 = s2.remove(node, "a");
|
||||
System.out.println(s3.getElements()); // b
|
||||
// #orset
|
||||
}
|
||||
|
||||
public void demonstrateORMultiMap() {
|
||||
// #ormultimap
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final ORMultiMap<String, Integer> m0 = ORMultiMap.create();
|
||||
final ORMultiMap<String, Integer> m1 = m0.put(node, "a", new HashSet<>(Arrays.asList(1, 2, 3)));
|
||||
final ORMultiMap<String, Integer> m2 = m1.addBinding(node, "a", 4);
|
||||
final ORMultiMap<String, Integer> m3 = m2.removeBinding(node, "a", 2);
|
||||
final ORMultiMap<String, Integer> m4 = m3.addBinding(node, "b", 1);
|
||||
System.out.println(m4.getEntries());
|
||||
// #ormultimap
|
||||
}
|
||||
|
||||
public void demonstrateFlag() {
|
||||
// #flag
|
||||
final Flag f0 = Flag.create();
|
||||
final Flag f1 = f0.switchOn();
|
||||
System.out.println(f1.enabled());
|
||||
// #flag
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateLWWRegister() {
|
||||
// #lwwregister
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final LWWRegister<String> r1 = LWWRegister.create(node, "Hello");
|
||||
final LWWRegister<String> r2 = r1.withValue(node, "Hi");
|
||||
System.out.println(r1.value() + " by " + r1.updatedBy() + " at " + r1.timestamp());
|
||||
// #lwwregister
|
||||
assertEquals("Hi", r2.value());
|
||||
}
|
||||
|
||||
static
|
||||
// #lwwregister-custom-clock
|
||||
class Record {
|
||||
public final int version;
|
||||
public final String name;
|
||||
public final String address;
|
||||
|
||||
public Record(int version, String name, String address) {
|
||||
this.version = version;
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
// #lwwregister-custom-clock
|
||||
|
||||
public void demonstrateLWWRegisterWithCustomClock() {
|
||||
// #lwwregister-custom-clock
|
||||
|
||||
final SelfUniqueAddress node = DistributedData.get(system).selfUniqueAddress();
|
||||
final LWWRegister.Clock<Record> recordClock =
|
||||
new LWWRegister.Clock<Record>() {
|
||||
@Override
|
||||
public long apply(long currentTimestamp, Record value) {
|
||||
return value.version;
|
||||
}
|
||||
};
|
||||
|
||||
final Record record1 = new Record(1, "Alice", "Union Square");
|
||||
final LWWRegister<Record> r1 = LWWRegister.create(node, record1);
|
||||
|
||||
final Record record2 = new Record(2, "Alice", "Madison Square");
|
||||
final LWWRegister<Record> r2 = LWWRegister.create(node, record2);
|
||||
|
||||
final LWWRegister<Record> r3 = r1.merge(r2);
|
||||
System.out.println(r3.value());
|
||||
// #lwwregister-custom-clock
|
||||
|
||||
assertEquals("Madison Square", r3.value().address);
|
||||
}
|
||||
}
|
||||
282
docs/src/test/java/jdocs/ddata/ShoppingCart.java
Normal file
282
docs/src/test/java/jdocs/ddata/ShoppingCart.java
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.apache.pekko.actor.AbstractActor;
|
||||
import org.apache.pekko.actor.ActorRef;
|
||||
import org.apache.pekko.actor.Props;
|
||||
import org.apache.pekko.cluster.ddata.*;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.GetFailure;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.GetResponse;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.GetSuccess;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.NotFound;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.ReadConsistency;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.ReadMajority;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.Update;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.UpdateFailure;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.UpdateSuccess;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.UpdateTimeout;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.WriteConsistency;
|
||||
import org.apache.pekko.cluster.ddata.Replicator.WriteMajority;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ShoppingCart extends AbstractActor {
|
||||
|
||||
// #read-write-majority
|
||||
private final WriteConsistency writeMajority = new WriteMajority(Duration.ofSeconds(3));
|
||||
private static final ReadConsistency readMajority = new ReadMajority(Duration.ofSeconds(3));
|
||||
// #read-write-majority
|
||||
|
||||
public static final String GET_CART = "getCart";
|
||||
|
||||
public static class AddItem {
|
||||
public final LineItem item;
|
||||
|
||||
public AddItem(LineItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RemoveItem {
|
||||
public final String productId;
|
||||
|
||||
public RemoveItem(String productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Cart {
|
||||
public final Set<LineItem> items;
|
||||
|
||||
public Cart(Set<LineItem> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LineItem implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final String productId;
|
||||
public final String title;
|
||||
public final int quantity;
|
||||
|
||||
public LineItem(String productId, String title, int quantity) {
|
||||
this.productId = productId;
|
||||
this.title = title;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((productId == null) ? 0 : productId.hashCode());
|
||||
result = prime * result + quantity;
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
LineItem other = (LineItem) obj;
|
||||
if (productId == null) {
|
||||
if (other.productId != null) return false;
|
||||
} else if (!productId.equals(other.productId)) return false;
|
||||
if (quantity != other.quantity) return false;
|
||||
if (title == null) {
|
||||
if (other.title != null) return false;
|
||||
} else if (!title.equals(other.title)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LineItem [productId="
|
||||
+ productId
|
||||
+ ", title="
|
||||
+ title
|
||||
+ ", quantity="
|
||||
+ quantity
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static Props props(String userId) {
|
||||
return Props.create(ShoppingCart.class, userId);
|
||||
}
|
||||
|
||||
private final ActorRef replicator = DistributedData.get(getContext().getSystem()).replicator();
|
||||
private final SelfUniqueAddress node =
|
||||
DistributedData.get(getContext().getSystem()).selfUniqueAddress();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String userId;
|
||||
|
||||
private final Key<LWWMap<String, LineItem>> dataKey;
|
||||
|
||||
public ShoppingCart(String userId) {
|
||||
this.userId = userId;
|
||||
this.dataKey = LWWMapKey.create("cart-" + userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return matchGetCart().orElse(matchAddItem()).orElse(matchRemoveItem()).orElse(matchOther());
|
||||
}
|
||||
|
||||
// #get-cart
|
||||
private Receive matchGetCart() {
|
||||
return receiveBuilder()
|
||||
.matchEquals(GET_CART, s -> receiveGetCart())
|
||||
.match(
|
||||
GetSuccess.class,
|
||||
this::isResponseToGetCart,
|
||||
g -> receiveGetSuccess((GetSuccess<LWWMap<String, LineItem>>) g))
|
||||
.match(
|
||||
NotFound.class,
|
||||
this::isResponseToGetCart,
|
||||
n -> receiveNotFound((NotFound<LWWMap<String, LineItem>>) n))
|
||||
.match(
|
||||
GetFailure.class,
|
||||
this::isResponseToGetCart,
|
||||
f -> receiveGetFailure((GetFailure<LWWMap<String, LineItem>>) f))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void receiveGetCart() {
|
||||
Optional<Object> ctx = Optional.of(getSender());
|
||||
replicator.tell(
|
||||
new Replicator.Get<LWWMap<String, LineItem>>(dataKey, readMajority, ctx), getSelf());
|
||||
}
|
||||
|
||||
private boolean isResponseToGetCart(GetResponse<?> response) {
|
||||
return response.key().equals(dataKey)
|
||||
&& (response.getRequest().orElse(null) instanceof ActorRef);
|
||||
}
|
||||
|
||||
private void receiveGetSuccess(GetSuccess<LWWMap<String, LineItem>> g) {
|
||||
Set<LineItem> items = new HashSet<>(g.dataValue().getEntries().values());
|
||||
ActorRef replyTo = (ActorRef) g.getRequest().get();
|
||||
replyTo.tell(new Cart(items), getSelf());
|
||||
}
|
||||
|
||||
private void receiveNotFound(NotFound<LWWMap<String, LineItem>> n) {
|
||||
ActorRef replyTo = (ActorRef) n.getRequest().get();
|
||||
replyTo.tell(new Cart(new HashSet<>()), getSelf());
|
||||
}
|
||||
|
||||
private void receiveGetFailure(GetFailure<LWWMap<String, LineItem>> f) {
|
||||
// ReadMajority failure, try again with local read
|
||||
Optional<Object> ctx = Optional.of(getSender());
|
||||
replicator.tell(
|
||||
new Replicator.Get<LWWMap<String, LineItem>>(dataKey, Replicator.readLocal(), ctx),
|
||||
getSelf());
|
||||
}
|
||||
// #get-cart
|
||||
|
||||
// #add-item
|
||||
private Receive matchAddItem() {
|
||||
return receiveBuilder().match(AddItem.class, this::receiveAddItem).build();
|
||||
}
|
||||
|
||||
private void receiveAddItem(AddItem add) {
|
||||
Update<LWWMap<String, LineItem>> update =
|
||||
new Update<>(dataKey, LWWMap.create(), writeMajority, cart -> updateCart(cart, add.item));
|
||||
replicator.tell(update, getSelf());
|
||||
}
|
||||
|
||||
// #add-item
|
||||
|
||||
private LWWMap<String, LineItem> updateCart(LWWMap<String, LineItem> data, LineItem item) {
|
||||
if (data.contains(item.productId)) {
|
||||
LineItem existingItem = data.get(item.productId).get();
|
||||
int newQuantity = existingItem.quantity + item.quantity;
|
||||
LineItem newItem = new LineItem(item.productId, item.title, newQuantity);
|
||||
return data.put(node, item.productId, newItem);
|
||||
} else {
|
||||
return data.put(node, item.productId, item);
|
||||
}
|
||||
}
|
||||
|
||||
private Receive matchRemoveItem() {
|
||||
return receiveBuilder()
|
||||
.match(RemoveItem.class, this::receiveRemoveItem)
|
||||
.match(
|
||||
GetSuccess.class,
|
||||
this::isResponseToRemoveItem,
|
||||
g -> receiveRemoveItemGetSuccess((GetSuccess<LWWMap<String, LineItem>>) g))
|
||||
.match(
|
||||
GetFailure.class,
|
||||
this::isResponseToRemoveItem,
|
||||
f -> receiveRemoveItemGetFailure((GetFailure<LWWMap<String, LineItem>>) f))
|
||||
.match(
|
||||
NotFound.class,
|
||||
this::isResponseToRemoveItem,
|
||||
n -> {
|
||||
/* nothing to remove */
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
// #remove-item
|
||||
private void receiveRemoveItem(RemoveItem rm) {
|
||||
// Try to fetch latest from a majority of nodes first, since ORMap
|
||||
// remove must have seen the item to be able to remove it.
|
||||
Optional<Object> ctx = Optional.of(rm);
|
||||
replicator.tell(
|
||||
new Replicator.Get<LWWMap<String, LineItem>>(dataKey, readMajority, ctx), getSelf());
|
||||
}
|
||||
|
||||
private void receiveRemoveItemGetSuccess(GetSuccess<LWWMap<String, LineItem>> g) {
|
||||
RemoveItem rm = (RemoveItem) g.getRequest().get();
|
||||
removeItem(rm.productId);
|
||||
}
|
||||
|
||||
private void receiveRemoveItemGetFailure(GetFailure<LWWMap<String, LineItem>> f) {
|
||||
// ReadMajority failed, fall back to best effort local value
|
||||
RemoveItem rm = (RemoveItem) f.getRequest().get();
|
||||
removeItem(rm.productId);
|
||||
}
|
||||
|
||||
private void removeItem(String productId) {
|
||||
Update<LWWMap<String, LineItem>> update =
|
||||
new Update<>(dataKey, LWWMap.create(), writeMajority, cart -> cart.remove(node, productId));
|
||||
replicator.tell(update, getSelf());
|
||||
}
|
||||
|
||||
private boolean isResponseToRemoveItem(GetResponse<?> response) {
|
||||
return response.key().equals(dataKey)
|
||||
&& (response.getRequest().orElse(null) instanceof RemoveItem);
|
||||
}
|
||||
// #remove-item
|
||||
|
||||
private Receive matchOther() {
|
||||
return receiveBuilder()
|
||||
.match(
|
||||
UpdateSuccess.class,
|
||||
u -> {
|
||||
// ok
|
||||
})
|
||||
.match(
|
||||
UpdateTimeout.class,
|
||||
t -> {
|
||||
// will eventually be replicated
|
||||
})
|
||||
.match(
|
||||
UpdateFailure.class,
|
||||
f -> {
|
||||
throw new IllegalStateException("Unexpected failure: " + f);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
48
docs/src/test/java/jdocs/ddata/TwoPhaseSet.java
Normal file
48
docs/src/test/java/jdocs/ddata/TwoPhaseSet.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.pekko.cluster.ddata.AbstractReplicatedData;
|
||||
import org.apache.pekko.cluster.ddata.GSet;
|
||||
|
||||
// #twophaseset
|
||||
public class TwoPhaseSet extends AbstractReplicatedData<TwoPhaseSet> {
|
||||
|
||||
public final GSet<String> adds;
|
||||
public final GSet<String> removals;
|
||||
|
||||
public TwoPhaseSet(GSet<String> adds, GSet<String> removals) {
|
||||
this.adds = adds;
|
||||
this.removals = removals;
|
||||
}
|
||||
|
||||
public static TwoPhaseSet create() {
|
||||
return new TwoPhaseSet(GSet.create(), GSet.create());
|
||||
}
|
||||
|
||||
public TwoPhaseSet add(String element) {
|
||||
return new TwoPhaseSet(adds.add(element), removals);
|
||||
}
|
||||
|
||||
public TwoPhaseSet remove(String element) {
|
||||
return new TwoPhaseSet(adds, removals.add(element));
|
||||
}
|
||||
|
||||
public Set<String> getElements() {
|
||||
Set<String> result = new HashSet<>(adds.getElements());
|
||||
result.removeAll(removals.getElements());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TwoPhaseSet mergeData(TwoPhaseSet that) {
|
||||
return new TwoPhaseSet(this.adds.merge(that.adds), this.removals.merge(that.removals));
|
||||
}
|
||||
}
|
||||
// #twophaseset
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata.protobuf;
|
||||
|
||||
// #serializer
|
||||
import jdocs.ddata.TwoPhaseSet;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages.TwoPhaseSet.Builder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.pekko.actor.ExtendedActorSystem;
|
||||
import org.apache.pekko.cluster.ddata.GSet;
|
||||
import org.apache.pekko.cluster.ddata.protobuf.AbstractSerializationSupport;
|
||||
|
||||
public class TwoPhaseSetSerializer extends AbstractSerializationSupport {
|
||||
|
||||
private final ExtendedActorSystem system;
|
||||
|
||||
public TwoPhaseSetSerializer(ExtendedActorSystem system) {
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedActorSystem system() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeManifest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int identifier() {
|
||||
return 99998;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBinary(Object obj) {
|
||||
if (obj instanceof TwoPhaseSet) {
|
||||
return twoPhaseSetToProto((TwoPhaseSet) obj).toByteArray();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can't serialize object of type " + obj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinaryJava(byte[] bytes, Class<?> manifest) {
|
||||
return twoPhaseSetFromBinary(bytes);
|
||||
}
|
||||
|
||||
protected TwoPhaseSetMessages.TwoPhaseSet twoPhaseSetToProto(TwoPhaseSet twoPhaseSet) {
|
||||
Builder b = TwoPhaseSetMessages.TwoPhaseSet.newBuilder();
|
||||
ArrayList<String> adds = new ArrayList<>(twoPhaseSet.adds.getElements());
|
||||
if (!adds.isEmpty()) {
|
||||
Collections.sort(adds);
|
||||
b.addAllAdds(adds);
|
||||
}
|
||||
ArrayList<String> removals = new ArrayList<>(twoPhaseSet.removals.getElements());
|
||||
if (!removals.isEmpty()) {
|
||||
Collections.sort(removals);
|
||||
b.addAllRemovals(removals);
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
protected TwoPhaseSet twoPhaseSetFromBinary(byte[] bytes) {
|
||||
try {
|
||||
TwoPhaseSetMessages.TwoPhaseSet msg = TwoPhaseSetMessages.TwoPhaseSet.parseFrom(bytes);
|
||||
GSet<String> adds = GSet.create();
|
||||
for (String elem : msg.getAddsList()) {
|
||||
adds = adds.add(elem);
|
||||
}
|
||||
GSet<String> removals = GSet.create();
|
||||
for (String elem : msg.getRemovalsList()) {
|
||||
removals = removals.add(elem);
|
||||
}
|
||||
// GSet will accumulate deltas when adding elements,
|
||||
// but those are not of interest in the result of the deserialization
|
||||
return new TwoPhaseSet(adds.resetDelta(), removals.resetDelta());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #serializer
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata.protobuf;
|
||||
|
||||
// #serializer
|
||||
import jdocs.ddata.TwoPhaseSet;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages;
|
||||
import docs.ddata.protobuf.msg.TwoPhaseSetMessages.TwoPhaseSet2.Builder;
|
||||
|
||||
import org.apache.pekko.actor.ExtendedActorSystem;
|
||||
import org.apache.pekko.cluster.ddata.GSet;
|
||||
import org.apache.pekko.cluster.ddata.protobuf.AbstractSerializationSupport;
|
||||
import org.apache.pekko.cluster.ddata.protobuf.ReplicatedDataSerializer;
|
||||
|
||||
public class TwoPhaseSetSerializer2 extends AbstractSerializationSupport {
|
||||
|
||||
private final ExtendedActorSystem system;
|
||||
private final ReplicatedDataSerializer replicatedDataSerializer;
|
||||
|
||||
public TwoPhaseSetSerializer2(ExtendedActorSystem system) {
|
||||
this.system = system;
|
||||
this.replicatedDataSerializer = new ReplicatedDataSerializer(system);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedActorSystem system() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeManifest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int identifier() {
|
||||
return 99998;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBinary(Object obj) {
|
||||
if (obj instanceof TwoPhaseSet) {
|
||||
return twoPhaseSetToProto((TwoPhaseSet) obj).toByteArray();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can't serialize object of type " + obj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinaryJava(byte[] bytes, Class<?> manifest) {
|
||||
return twoPhaseSetFromBinary(bytes);
|
||||
}
|
||||
|
||||
protected TwoPhaseSetMessages.TwoPhaseSet2 twoPhaseSetToProto(TwoPhaseSet twoPhaseSet) {
|
||||
Builder b = TwoPhaseSetMessages.TwoPhaseSet2.newBuilder();
|
||||
if (!twoPhaseSet.adds.isEmpty())
|
||||
b.setAdds(otherMessageToProto(twoPhaseSet.adds).toByteString());
|
||||
if (!twoPhaseSet.removals.isEmpty())
|
||||
b.setRemovals(otherMessageToProto(twoPhaseSet.removals).toByteString());
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected TwoPhaseSet twoPhaseSetFromBinary(byte[] bytes) {
|
||||
try {
|
||||
TwoPhaseSetMessages.TwoPhaseSet2 msg = TwoPhaseSetMessages.TwoPhaseSet2.parseFrom(bytes);
|
||||
|
||||
GSet<String> adds = GSet.create();
|
||||
if (msg.hasAdds()) adds = (GSet<String>) otherMessageFromBinary(msg.getAdds().toByteArray());
|
||||
|
||||
GSet<String> removals = GSet.create();
|
||||
if (msg.hasRemovals())
|
||||
adds = (GSet<String>) otherMessageFromBinary(msg.getRemovals().toByteArray());
|
||||
|
||||
return new TwoPhaseSet(adds, removals);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #serializer
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.ddata.protobuf;
|
||||
|
||||
import jdocs.ddata.TwoPhaseSet;
|
||||
|
||||
import org.apache.pekko.actor.ExtendedActorSystem;
|
||||
|
||||
public class TwoPhaseSetSerializerWithCompression extends TwoPhaseSetSerializer {
|
||||
public TwoPhaseSetSerializerWithCompression(ExtendedActorSystem system) {
|
||||
super(system);
|
||||
}
|
||||
|
||||
// #compression
|
||||
@Override
|
||||
public byte[] toBinary(Object obj) {
|
||||
if (obj instanceof TwoPhaseSet) {
|
||||
return compress(twoPhaseSetToProto((TwoPhaseSet) obj));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can't serialize object of type " + obj.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromBinaryJava(byte[] bytes, Class<?> manifest) {
|
||||
return twoPhaseSetFromBinary(decompress(bytes));
|
||||
}
|
||||
// #compression
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue