Package eu.antidotedb.client

This is the main package of the Antidote Java Client.

Usage

First add the necessary imports if you are not using an IDE that adds them automatically:


     import eu.antidotedb.client.*;
     import java.net.InetSocketAddress;
 

To connect to Antidote, create a new AntidoteClient instance and pass it one or more InetSocketAddress entries:


     AntidoteClient antidote = new AntidoteClient(new InetSocketAddress("localhost", 8087));
 

You can also pass a PoolManager to get more control over the configuration.

Antidote-Keys

Objects in the database are addressed using a Key. A key contains the CRDT type and a name of type ByteString. To create a key the static methods on Key can be used:

For example a key for a set datatype stored under key "users" can be retrieved as follows:


     SetKey<String> userSet = Key.set("users");
 

It is also possible to statically import the methods for creating keys:


     import static eu.antidotedb.client.Key.*;
     ...
     SetKey<String> userSet = set("users");
 

The type parameter of SetKey denotes the type of elements stored in the set. The default is String, but this can easily be configured by passing a ValueCoder to the set method. For example a key for a set of ByteStrings can be created like this:

     SetKey<ByteString> userSetB = set("users", ValueCoder.bytestringEncoder);
 

More information is available in the documentation of the ValueCoder interface.

Buckets

In Antidote each object is stored in a Bucket. To create a bucket use the static bucket method:


     Bucket bucket = Bucket.bucket("mybucket");
 

Transactions

A unit of operation in Antidote is a transaction. A client should first start a transaction, then read and/or update several objects, and finally commit the transaction. There are two types of transactions: interactive transactions and static transactions.

Interactive transactions

With an interactive transaction, a client can execute several updates and reads before committing the transactions. An interactive transaction can be started with the startTransaction method on the AntidoteClient object. It is good practice to use the resulting InteractiveTransaction with a try-with-resource statement to avoid leaving transactions open accidentially.


     ValueCoder<Integer> intCoder = ValueCoder.stringCoder(Object::toString, Integer::valueOf);
     CounterKey c = Key.counter("my_example_counter");
     SetKey<Integer> numberSet = Key.set("set_of_numbers", intCoder);
     try (InteractiveTransaction tx = antidoteClient.startTransaction()) {
         int val = bucket.read(tx, c);
         bucket.update(tx, numberSet.add(val));
     }
 

Static transactions

Static transactions consist of a single bulk operation. A static transaction can update multiple objects atomically or read multiple objects atomically, but it is not possible to read and update in the same static transaction.

Alternatively, use the AntidoteClient.noTransaction() method to execute the request without any transactional context:

List<String> value = bucket.read(antidote.noTransaction(), userSet);
 

Reading objects

A Bucket has a read method, which retrieves the current value of the object from the database. The read method takes a transaction object.

For reading multiple objects simultaneously, a BatchRead can be used. Start a batch read with the AntidoteClient.newBatchRead() method. Then use the BatchRead as the transaction context for several read-requests. Finally, after committing the BatchRead via the commit method, the results can be obtained via the BatchReadResult.get() methods:


     CounterKey c1 = Key.counter("c1");
     CounterKey c2 = Key.counter("c2");
     CounterKey c3 = Key.counter("c3");
     BatchRead batchRead = antidoteClient.newBatchRead();
     BatchReadResult<Integer> c1val = bucket.read(batchRead, c1);
     BatchReadResult<Integer> c2val = bucket.read(batchRead, c2);
     BatchReadResult<Integer> c3val = bucket.read(batchRead, c3);
     batchRead.commit(antidoteClient.noTransaction());
     int sum = c1val.get() + c2val.get() + c3val.get();
 

If all read values have the same time the Bucket.readAll method provides a shortcut for performing several reads simultaneously:


     List<Integer> values = bucket.readAll(antidoteClient.noTransaction(), Arrays.asList(c1, c2, c3));
 
The return value datatype of the read method corresponds to the datatype of the CRDT object being read:

Reading Map CRDTs

Reading a map CRDT object consists of two steps: First, reading the map object using the read method. This returns a MapReadResult object which presents the result of a read request on a map CRDT.

The second step is to get the values of nested CRDTs from the MapReadResult. This can be done using the get method:


     MapKey m = Key.map_rr("test_map");
     MapKey.MapReadResult mapReadResult = bucket.read(client.noTransaction(), m);
     String mapReadResult.get(Key.register("map_register_entry"));
 

Updating objects

Each Key has one or more methods to create update operations on the key. To execute the update operation on a bucket it has to be passed to the Bucket.update method. Just like with reads, the update method take a transactional context as its first argument.

For example a SetKey has an add method to add elements to the set:


     bucket.update(antidote.noTransaction(), set("users").add("Hans Wurst"));
 

For performing several updates simultaneously the Bucket.updates methods can be used.

When constructing map updates, updates for nested CRDTs can be created in the same way as other updates, which makes it possible to buid complex map-updates updating several components of the map at once:


     MapKey testmap = map_aw("testmap2");

     AntidoteStaticTransaction tx = antidoteClient.createStaticTransaction();
     bucket.update(tx,
         testmap.update(
             counter("a").increment(5),
             register("b").assign("Hello")
     ));
 

Session guarantees

To ensure session guarantees like "read your writes" Antidote uses vector clocks. Each operation returns a vector clock indicating the time after the operation. At each request to Antidote a vector clock can be given to force a minimum time for the snapshot used in the request.

TODO this is not yet implemented on the client