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> . |
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.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.
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
.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 modified 2yr ago