Object Storage

Using XOOM Symbio to store objects.

An ObjectStore provides the protocol for reactively persisting and reconstituting objects. Storing objects has the advantage of searching for given instances by any number of their attributes/properties, effectively as if the objects are living in memory with only the need to ask for them by means of partial state characteristics. The objects stored are typically state types of Entity/Aggregate instances, which have unique identities. These state types are extenders of the StateObject abstract base class.

import io.vlingo.xoom.symbio.store.object.StateObject;

public final class ProductState extends StateObject {
  private String name;
  private SKU sku;
  private RFID rfid;
  private Money price;
  ...
  
  public ProductState(long id, String name, SKU sku, RFID rfid, Money price) {
    super(id);
    
    this.name = name;
    this.sku = sku;
    this.rfid = rfid;
    this.price = price;
  }
}

Typically this ProductState would be held by the Aggregate ProductActor, where the actor implements the Product protocol.

public ProductActor extends Actor implements Product {
  private ProductState state;
  
  public ProductActor(long id, String name, SKU sku, RFID rfid, Money price) {
    this.state = new ProductState(id, name, sku, rfid, price);
  }
  
  ...
}

In recent years object storage has been managed by a technique known as object-relational mapping (ORM), where objects are disassembled to fit into the relational table, row, and column structures. In such cases SQL expressions are used to create, read, update, and delete such objects. Using Java means that the SQL operations will be managed using JDBC, but will often employ a mapping layer on top of that. Some well-known ORM tools are TopLink/EclipseLink, Hibernate, various implementations of JPA, and others, such as Cayenne. There are other abstractions, such as Jdbi, that are not classified as an ORM, but provide worthy querying and mapping techniques that are much less invasive than ORM tools. All of these are or can be supported by a XOOM Symbio ObjectStore implementation.

Additionally, there is a XOOM Symbio ObjectStore implementation for Apache Geode. Apache Geode is an in-memory data fabric, compute grid, and distributed cache, which can hold object states.

An important point to consider is, if you use the XOOM Lattice entity type ObjectEntity, there is no need to learn the operations of the ObjectStore. You get all storage persistence for free when you use the ObjectEntity abstract base types. Yet, you must still set up your backing database mechanism, including your chosen ORM configurations. We do not cover ORM details here.

Object Persistence Protocols

The ObjectStore protocol extends ObjectStoreReader and ObjectStoreWriter, defining a single message type in addition: close().

public interface ObjectStore extends ObjectStoreReader, ObjectStoreWriter {
  void close();
}

First consider the ObjectStoreWriter. It is composed of a number of variations of persist() and persistAll() message types, with differences being in the number of parameters supported in each. The richest form of the two message types follow.

<T extends PersistentObject, E> void persist(
  final T persistentObject,
  final List<Source<E>> sources,
  final Metadata metadata,
  final long updateId,
  final PersistResultInterest interest,
  final Object object);

<T extends PersistentObject, E> void persistAll(
  final Collection<T> persistentObjects,
  final List<Source<E>> sources,
  final Metadata metadata,
  final long updateId,
  final PersistResultInterest interest,
  final Object object);

As noted, there are variations of these two message types that have less parameters, providing defaults to the richer message types in their implementations. The full complement of parameters and types are described next.

When using ObjectStoreWriter, the sender must provide an Actor or mocked test object that implements the PersistResultInterestprotocol.

public static interface PersistResultInterest {
  void persistResultedIn(final Outcome<StorageException,Result> outcome, final Object persistentObject, final int possible, final int actual, final Object object);
}

Next consider the ObjectStoreReader protocol. It provides a number of query methods, with variations on required and optional parameters.

void queryAll(
  final QueryExpression expression,
  final QueryResultInterest interest,
  final Object object);
  
void queryObject(
  final QueryExpression expression,
  final QueryResultInterest interest,
  final Object object);

The difference between the two types of methods is the number of objects that should be included in the query results, either one or a one-or-more result.

The QueryExpression is defined as follows. There are two extensions of the basic QueryExpression, which are ListQueryExpression and MapQueryExpression.

public class QueryExpression {
  public final QueryMode mode;
  public final String query;
  public final Class<?> type;
  ...
}

public class ListQueryExpression extends QueryExpression {
  public final List<?> parameters;
  ...
}

public class MapQueryExpression extends QueryExpression {
  public final Map<String,?> parameters;
  ...
}

These class definitions include a number of factory methods for conveniently and expressively creating new instances of each.

The difference between these three query expression types is how parameters are supplied at runtime. The plain QueryExpression is a single parameterless expression. The parameters of a ListQueryExpression are ordered for setting as positional parameters 1, 2, 3, etc., or as a list of comma delimited parameters. The MapQueryExpression parameters are named using the String map keys, with the corresponding values to be set in the query expression, such as expression target :id matched with key "id" and value "8d8acfe97a".

When using ObjectStoreReader, the sender must provide an Actor or mocked test object that implements the QueryResultInterestprotocol.

public static interface QueryResultInterest {
  void queryAllResultedIn(final Outcome<StorageException,Result> outcome, final QueryMultiResults results, final Object object);
  void queryObjectResultedIn(final Outcome<StorageException,Result> outcome, final QuerySingleResult result, final Object object);
}

When the receiver is informed of a query result, it is expressed as either a multiple All result or a single Object result. Note that the Outcome may be a StorageException or a successful Result. The QueryMultiResults holds a collection of resulting objects and QuerySingleResult holds a single object result.

ObjectStore Implementations

There are a number of implementations of the ObjectStore.

Component: xoom-symbio

You may use the in-memory implementation for testing.

import io.vlingo.xoom.symbio.store.object.inmemory.InMemoryObjectStoreActor;

Component: xoom-symbio-geode

This is the implementation of the ObjectStore for Apache Geode.

import io.vlingo.xoom.symbio.store.object.geode.GeodeObjectStoreActor;

Component: xoom-symbio-jdbc

These depend on specific database mechanisms, such as PostgreSQL, MySQL, MariaDB, and HSQLDB.

// for Jdbi
import io.vlingo.xoom.symbio.store.object.jdbc.JDBCObjectStoreActor;
import io.vlingo.xoom.symbio.store.object.jdbc.jdbi.JdbiObjectStoreDelegate;

// for JPA
import io.vlingo.xoom.symbio.store.object.jdbc.jpa.JPAObjectStoreActor;
import io.vlingo.xoom.symbio.store.object.jdbc.jpa.JPAObjectStoreDelegate;

Last updated