Adapters

Using XOOM Symbio adapters to translate between service/application, messaging, and storage state.

Use XOOM Symbio adapters to translate object state within a service or application to serialized states that are suitable for network-based messaging and long-term database storage. To explain the details within this topic we refer to the following components.

Type

Description

StateAdapter<S,RS>

The interface defining the behaviors of an adapter for application and serialized states.

DefaultTextStateAdapter

The default StateAdapter used when no type-specific adapter is registered with the StateAdapterProvider.

StateAdapterProvider

The registry and provider of StateAdapter instances by state type, as well as adapter behaviors using type lookups.

State<T>

The abstract base type that supports binary, object, and text serialization states.

BinaryState

The concrete type that supports State<byte[]>.

ObjectState

The concrete type that supports State<Object>.

TextState

The concrete type that supports State<String>.

EntryAdapter<S,E>

The interface defining the behaviors of an adapter for application sources and serialized entries.

DefaultTextEntryAdapter

The default EntryAdapter used when no type-specific adapter is registered with the EntryAdapterProvider.

EntryAdapterProvider

The registry and provider of EntryAdapter instances by source type, as well as adapter behaviors using type lookups.

Entry<T>

The interface that defines a serialized state persisted to a Journal<T>. A journal entry may be a serialized Source<T> concrete implementation such as those defined in XOOM Lattice: Command, DomainEvent, and ProcessMessage, and others.

BaseEntry<T>

The abstract base type that supports entry binary, object, and text serialization.

BinaryEntry

The concrete type that supports Entry<byte[]>.

ObjectEntry

The concrete type that supports Entry<Object>.

TextEntry

The concrete type that supports Entry<String>.

NullEntry

The concrete type that implements the Null Object pattern for Entry<byte[]>, Entry<Object>, and Entry<String>.

Using Adapters

There are a few ways to use adapters. You may use the default adapters or implement your own. The use of the default adapters may be good enough to get your service or application up and running quickly. As time passes and your DomainEvent and/or Command types change, you will need to replace the defaults with your own implementations.

Default Adapters

The XOOM Symbio tooling provides default StateAdapter and EntryAdapter types for both State and Entry. These default adapters will be used automatically when the service or application has not registered a custom adapter for a given state or source type. This means that you do not need to register these default adapters with the StateAdapterProvider or the EntryAdapterProvider.

The current default adapters are the DefaultTextStateAdapter and the DefaultTextEntryAdapter. Because these are default adapters you will not need to import them into your main code, although the import statements are shown here for clarity.

import io.vlingo.xoom.symbio.DefaultTextEntryAdapter;
import io.vlingo.xoom.symbio.DefaultTextStateAdapter;

Both of these serialize from service and application object state to JSON text where the State and Entry types are TextState and TextEntry, respectively. They also deserialize from TextState and TextEntry back to service and application object state.

If you use a different text format other than JSON, or if you use binary, you must provide your own custom adapters. We may provide other default adapter types in the future depending on demand.

Implementing Custom Type Adapters

The following is an example of the implementation of a StateAdapter.

import io.vlingo.xoom.common.serialization.JsonSerialization;
import io.vlingo.xoom.symbio.Metadata;
import io.vlingo.xoom.symbio.State;
import io.vlingo.xoom.symbio.State.TextState;
import io.vlingo.xoom.symbio.StateAdapter;

public class ProductStateAdapter implements StateAdapter<ProductState,State<String>> {
  @Override public int typeVersion() { return 1; }

  @Override
  public Product fromRawState(final State<String> raw) {
    return JsonSerialization.deserialized(raw.data, ProductState.class);
  }

  @Override
  public <ST> ST fromRawState(final State<String> raw, final Class<ST> stateType) {
    return JsonSerialization.deserialized(raw.data, stateType);
  }

  @Override
  public State<String> toRawState(final ProductState state, final int stateVersion) {
    return toRawState(state.id, state, stateVersion, Metadata.nullMetadata());
  }

  @Override
  public State<String> toRawState(final String id, final ProductState state, final int stateVersion, final Metadata metadata) {
    final String serialization = JsonSerialization.serialized(state);
    return new TextState(id, ProductState.class, typeVersion(), serialization, stateVersion);
  }
}

This ProductStateAdapter is responsible for adapting from the Product service/application object state to what is called the raw state and back again from raw state to the Product service/application state.

The raw state is suitable for traveling across a network and being persisted into database storage. This is handled by the two methods named toRawState().

The service/application state is, as the name indicates, used as operational state. Adapting from raw state back to service/application state is handled by the two methods named fromRawState().

There is another method, typeVersion(), that provides the current version of the operational state type. This specific implementation indicates version 1. This version may also be encoded as a SemanticVersion, as is provided with XOOM Common.

The current type version is automatically encoded into the raw state. When adapting back from the raw state to the operational state, you must check the raw state's type version to determine if it must be upgraded to the current operational state type version. This is available via the variable public final int typeVersion in State<T>. (This is an immutable variable and does not require an accessor method.)

Next is an example of an EntryAdapter for the CartInitialized event.

public class CartInitializedAdapter implements EntryAdapter<CartInitialized, TextEntry> {
  @Override
  public CartInitialized fromEntry(final TextEntry entry) {
    return JsonSerialization.deserialized(entry.entryData(), CartInitialized.class);
  }

  @Override
  public TextEntry toEntry(final CartInitialized source, final Metadata metadata) {
    final String serialization = JsonSerialization.serialized(source);
    return new TextEntry(CartInitialized.class, 1, serialization, metadata);
  }

  @Override
  public TextEntry toEntry(final CartInitialized source, final String id, final Metadata metadata) {
    final String serialization = JsonSerialization.serialized(source);
    return new TextEntry(id, CartInitialized.class, 1, serialization, metadata);
  }

  @Override
  public TextEntry toEntry(final CartInitialized source, final int version, final String id, final Metadata metadata) {
    final String serialization = JsonSerialization.serialized(source);
    return new TextEntry(id, CartInitialized.class, 1, serialization, version, metadata);
  }
}

The same design guidance applies for EntryAdapter implementations as for StateAdapter.

Registering Custom Type Adapters

Custom type adapters are registered with adapter providers. When the adapter provider is created it is registered with an associated unique identity in the World. The registration is a means to avoid the use of static variables to hold the adapter providers. It also makes it convenient for the various XOOM Symbio storage mechanisms to access the adapters when moving data into and out of the underlying databases.

To register a custom type adapter you first must allocate adapter providers, and then register each custom type adapter with a given adapter provider. The first example demonstrates registering state adapters.

World world = World.startWithDefaults("my-service");

StateAdapterProvider stateAdapterProvider =
    StateAdapterProvider.instance(world);

stateAdapterProvider.registerAdapter(
    ProductState.class, new ProductStateAdapter());

stateAdapterProvider.registerAdapter(
    CartState.class, new CartStateAdapter());

The second example demonstrates registering entry adapters.

World world = World.startWithDefaults("my-service");

EntryAdapterProvider entryAdapterProvider =
    EntryAdapterProvider.instance(world);

entryAdapterProvider.registerAdapter(
    CartInitialized.class, new CartInitializedEntryAdapter());

entryAdapterProvider.registerAdapter(
    ProductPlacedInCart.class, new ProductPlacedInCartEntryAdapter());

You should determine which states and sources (e.g. DomainEvent and Command types) must be exchanged across collaborating services and establish their schemas in the XOOM Schemata schema registry.

Last updated