Command, for example—into a CQRS query model.
ProjectToDescriptionis a description of how data and events are filtered and selected. You may configure a
State<T>and also for projecting
Source<T>instances, such as
DomainEventtypes. Both are described below.
ProjectionDispatcherto be registered with the storage mechanism is created by means of an actor that implements both protocols. Two such are the
TextProjectionDispatcherActor, and used when a binary store or text store is used, respectively.
Dispatcherregistered with it.
ProjectToDescriptionmay be defined for state data and for events. The following shows a description defined for a state object.
Metadata, and the
Metadatainstance may have a named
operationthat caused the state to transition to the current value. The above description indicates that if the state data contains an
"User:name", the dispatcher will route the data to the
operationname is associated with the new state. In this case the command is to replace the user's name with a different name. The transitioned state and the descriptive operation is applied. After the state is persisted, the
Dispatcheris given the opportunity to route the new state to a projection. As seen in the description of the previous code snippet, matching the
"User:name"operation will cause the new state to be routed to the
ProjectToDescriptionmay be defined for state data and for events. The following shows a description defined for events.
DomainEventis one of the types
ForumModeratorAssigned, the dispatcher will route the event to the
ForumStartedevent occurs. After the event is persisted, the
Dispatcheris given the opportunity to route it to a projection. As seen in the description of the previous code snippet, matching the
ForumStartedevent will cause it to be routed to the
ProjectToDescriptionup to date with new and changing event types could be error prone by being overlooked. Instead of referencing each event type, you may instead reference the Java package that contains all of the events.
Journalare wired for use.
ProjectToDescriptioninstances created, which are formed into a single
List<ProjectToDescription>. Each of the descriptions indicate how various command model changes will be dispatched into the actor instance whose type is the first parameter. For example, this
DashboardProjectionActorinstance will be created inside the
DomainEventtypes defined as inner classes of the
com.dashdawn.model.dashboard.Eventsclass, will be dispatched to it. The same goes for the
WaveBoardDetailsProjectionActorand the inner
Eventstypes defined in its package.
descriptionsare then used to create the
ProjectionDispatcher, which is implemented by the
TextProjectionDispatcherActorprovided by XOOM LATTICE. Actually this actor implements two protocols, both the
Dispatcheris used by the
Journalto dispatch newly appended events to the
ProjectionDispatcher. (This is implemented by the abstract base
ProjectionDispatcherActor, which is extended by the concrete actors,
BinaryProjectionDispatcherActor). The two protocol references answered by
actorFor()are captured in the
ProjectionDispatcherProviderinstance is created, where both protocol references are held and available to dependents.
Dispatcheris used, where the command model storage will use it to dispatch events into the projections. Note that the command model store knows nothing about projections, only the
Startupclass containing a
Projectiondesigned to handle state mutation operations. It corresponds to the above section
Projectablecontains the data to be projected. The
ProjectionControlis used to confirm that the
Projectablehas been projected so that the
Dispatcherwill not route it again. The code inside each of the cases would create or update the views effected by the state mutations.
Projectiondesigned to handle events. This example corresponds to the above section
Actorabstract base type for this:
StateStoreProjectionActor. This is a much more powerful abstract than directly using the
Projectioninterface. As the name indicates, this projection base is used to project into a
StateStoreProjectionActor, as demonstrated next. This example overrides the
merge(...)that is best used with Event Sourcing.
DashboardProjectionActorconstructor uses the super constructor that takes only the
StateStoreused to write and read
DashboardViewinstances. This single-parameter constructor provides a default
EntryAdapterfor the concrete projection to use. These are part of XOOM SYMBIO and are known as
StateStoreProjectionActorthat takes an additional two parameters, one for the
StateAdapterand one for the
Entrytypes to the model types. When the single argument constructor is used you get the default
Projectionprotocol, but there is a default implementation of method
projectWith()provided by the abstract base class
StateStoreProjectionActor. The implementation of this method is quite simple for most uses. If you need a more involved implementation you may override the default provided by the abstract base class.
Projectable, which may hold a state, or entries, or both.
upsertFor()does the following.
currentDataFor(projectable)for its notion of the current state, as in the state that is now being projected. You may override
currentDataFor(projectable). This may or may not be useful, but if it is, you must return the value that is considered current from the
Projectable. This method is generally used only for full-state projections rather than those based on
prepareForMergeWith(). The default behavior adapts any
Source<?>instances, holding them in an internal
List<Source<?>>. The concrete extender may get the adapted
Source<?>instances using the
sources()method. These are provided by default with the second
merge(...)method that is used for Event Sourcing projections. If you have some alternative or additional preparation to do before the merge this method must be overridden.
Tvalue from the
StateStore(see the last statement in the method) and passes a
BiConsumer<T, Integer> upserter. When the read completes, the
upserteris used to merge the
currentDataand/or apply any
trueor if the newly merged data is not equal to the previous data. This happens inside the
currentDataFor()may or may not be useful in terms of providing a new state. This implementation answers an
Emptyinstance of the
DashboardView, which will be used by the
merge()as an accumulator of modifications. This particular override example is unnecessary but demonstrates an alternative to the default implementation. Assume that we decide not to implement the above override and remove the
Emptyvalue declaration as well as the
merge()similar to the previous section is necessary.
DashboardProjectionActoris given the opportunity to prepare for the merge.
Entryinstances into instances of the
IdentifiedDomainEvent. This gives the concrete event types the means to provide the identity of the event instance. As indicated above, this specific implementation of
prepareForMergeWith()is redundant because it does the same that the default behavior already does. You would choose to override
prepareForMergeWith()only if you required behavior beyond the above implementation.
dataIdis provided by the first (and possibly only) instance of the
eventslist. Recall that this
eventslist was just previously populated by the
prepareForMergeWith(). This example
dataIdFor()method override is likely redundant because the default behavior provides a more thorough implementation, first checking for the
Projectable dataId(), and if that is not available, looks for the identity from one of the first
IdentifiedDomainEvent. If neither of these are available then the
dataIdwill be a blank
String, and an override of this method would be necessary to provide a custom value.
prepareForMergeWith(), the source/event instances are retrieved as follows. Here again, your
DomainEventinstances must be of the extends
IdentifiedDomainEventtype, which can provide the
identity()of the entity that emitted them.
merge()and its helper method
mergeInto(), assuming that the
prepareForMergeWith()was also overridden and a local
eventswas collected by it. This example shows an alternative to using the more appropriate
merge(...)override that provides the
currentDatato be used by
mergeInto()as an accumulator of the new state. Any preexisting state on the
currentDatathat coincides with the interpretation of the
DomainEventwill be overwritten/replaced.
Projectablemay have multiple entries (serialized
mergeInto()iterates over the entire collection of events deserialized by the method
prepareForMergeWith()implementation, you would iterate as follows, using the
sources()method rather than a collection that you gathered on your own.
DashboardViewProjectableTypeto match on each of the
DomainEventtype is matched to the enum by the
match()method. A non-match produces the
Unmatchedtype, which is ignored by
mergeInto(), but logged as a warning.
mergeInto()maps event attributes to
DashboardViewattributes. Any preexisting values that are mapped are overwritten on the
viewaccumulator. The iteration and matching is done for any number of events, which may be included with the current
Projectableor others received in the future.
merge()completes, the modified
DashboardViewis written to the
StateStoreProjectionActor<T>provides a default means to determine the current version of the data to be merged and then updated to the database. It works like this:
Projectableis used as the version for the current data to be merged and updated to the database. This is how all of the above examples obtain the current data version for inserts/updates. It is based on a policy to always update the current merged data. That is, the data is always written no matter whether it is the same as the previous data, or different.
Projectableis redelivered due to a latent or failed delivery confirmation. In such cases the
StateStoreProjectionActor<T>provides a secondary policy, which is to automatically increment the previous version by one and to also test the current merged data with the previous data to qualify the need to update the data to the database.
alwaysWrite()method. By default it returns
true. Your override should return
currentDataVersionFor(), which you need not override if the secondary policy works for your specific projection use case.
currentDataVersionFor()provides both policies in one line of code. If
projectable.dataVersion()is returned as was explained above. Otherwise, when
false, the next sequential version from the one previously stored in the databased is returned. Note that in the case where there was no previous data in the database for the given identity, the
-1. Thus, there is the one special case where
1must be explicitly returned as the initial version.
Productentity will have version
ProductDefinedfrom a different
Productentity will also have version
1. This is unavoidable because
ProductDefinedis always the first event emitted from a
Product. If you attempt to merge the
ProductDefinedthat is delivered after the first initially delivered
ProductDefined, the merged/updated data will have version
1just as the previous data did. Writing this version will cause an optimistic concurrency violation in the
StateStore, because the version will make the new data look as if it is based on stale state. By overriding
false, your current/merged data version will always be one greater than the previous data version.
false, there will be a pre-update check requiring the current data to be different from the previous data.
equals()of the data's type. The updating write is performed only when the current and previous data are not equal. Note that the
equals()implementation must check for value equality (compare type equality as well as equality of all corresponding data attributes/fields) rather than entity equality (compare ids only).