IEventListener<TEvent>
interface. Event can be any plain object that implements the IEvent interface (which itself is empty in its definition).
DomainEvent
listener will also receive events of type DomainAggregateEvent
and all types derived from it). Single event type can be handled by any amount of event handlers, which are registered using the dependency container (their order of invocation is not guaranteed in any way).
IEventMessage<T>
). Event message is an envelope wrapping the event with additional metadata that may not be primarily important for the domain (i.e. they are not a part of the event’s definition on its own), but still might be needed elsewhere (especially when consuming the events outside of the domain model core). This allows to keep the event type definitions simple and clean of things unrelated to their purpose from the domain perspective.
BasicEventMetadataNames
.
IEventMetadataProvider
in the DI container. These providers are invoked by the EventMessageFactory
when the event messages get constructed (i.e. typically before saving to event store or sending to other connected systems).
EventVersionAttribute
specifying its new version (which makes it possible to preserve its original name). An example:IEventUpgrade
(Revo auto-discovers these upon startup).EventUpgrade<TAggregate>
class as a base class for your upgrades, which also check the aggregate class before trying to apply any upgrades (which is more efficient if the event is used by only one aggregate class).IEnumerable
) of all original aggregate's event messages and then returns an upgraded stream of event messages. You can easily implement this transformation using C# yield return
operator.EventMessageUpgradeExtensions.Upgrade<TSource>
for upgrading the event message, which returns a new event message with the domain event replaced while keeping all original message metadata.EventMessageUpgradeExtensions
class making the transformation even easier, e.g.:IEventListener<TEvent>
) via the event bus is the basic way of processing events that is implemented by the framework. As such, it is completely synchronous – meaning that all the listeners are invoked sequentially, one-by-one and without guaranteed order, in a synchronous manner. Because of their nature, synchronous event listeners posess no delivery guarantees and should rarely be used in actual application code (possible valid use cases include notifications that only have transient effects).async/await
features of C# (the Task Asynchronous Pattern ak TAP; in fact, most of the framework codebase is already async all the way), but rather to the a/synchronicity of the event delivery and processing itself in regard to the event publishing and to other listeners.IAsyncListener<TEvent>
for the specific event (base) type and registered using the dependency container. Events asynchronously delivered via this interface are a bit different in their nature when compared to the events delivered using the regular (synchronous) event pipeline.
IEventSequencer<TEvent>
). When the async processing of an event begins, the event dispatcher calls all of these registered sequencers. An event sequencer primarily defines two properties – which event sequence(s) it wants to use for the event (which in turn implies the event queue it will be pushed to) and what sequence number(s) will it assign to that event. Based on that information, the event dispatcher subsequently creates corresponding event queues (if it does not exist already) and pushes the event to them. Later when an event backlog worker gets to process any of the queues, it iterates through all the events queued in it, ordered by their sequence number, and passes them to corresponding async event listener(s). When the backlog worker is done, it removes the events that were successfully processed from that queue. If processing of any of the events fails, it stays in the queue until the problem gets resolved and event successfully processed.
IEventSourceCatchUp
interface.
IAsyncEventSequencer
signals that it wants to attempt synchronous dispatch by implementingShouldAttemptSynchronousDispatch
, the events added to its queue that would normally get scheduled for later processing in the background get directly passed to the event processor instead - that is, in a blocking (synchronous) manner, possibly on the same thread. If any of the async event listeners fail, they still retain the same properties of other asynchronous events – i.e. their processing gets retried later either upon the restart of the system or at the next processing of the same queue.
EntityEventToPocoProjectors<,>
along with projection row versioning (see later chapter on read models and projections).