Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Event dispatcher

Samuel Debruyn edited this page Dec 24, 2015 · 4 revisions

What is an event dispatcher?

The event dispatcher is the bridge between the command processing system and the event processing system. When Cirqus has successfully processed a command and persisted the resulting events in the event store, it dispatches the emitted events to the current IEventDispatcher.

By default, Cirqus is using the NullEventDispatcher, which implies that events go nowhere after they have been saved.

Configuration

You can easily dispatch events somewhere else by using the .EventDispatcher part of the configuration API, e.g. to a "view manager event dispatcher" (an event dispatcher that manages views) like this:

CommandProcessor.With()
    .(...)
    .EventDispatcher(e => {
        e.UseViewManagerEventDispatcher(views);
        e.UseEventDispatcher(myCustomEventDispatcher);
    })
    .Create();

It's possible to configure a profiler for an event dispatcher. Take a look at Logging and profiling.

Types of event dispatchers

You can configure as much event dispatchers as you like.

Custom event dispatcher

It's quite simple to create your own event dispatcher. All you have to do is implement the IEventDispatcher interface. All events will then be dispatched to that custom dispatcher.

View manager event dispatcher

The view manager event dispatcher is a pretty advanced event dispatcher that is capable of managing a pool of views, automatically dispatching the right events to them all the time.

Views are implementations of IViewManager. This means that the view manager event dispatcher can manage many types of views, so it's possible to have both MSSQL and MongoDB views in the same event dispatcher.

Each view manager event dispatcher gets its own dedicated background thread, which will take care of doing the actual event dispatch. It's up to you to decide if you want to have few dispatchers that each dispatch to a lot of view managers or have a lot of dispatchers that each dispatch to few view managers.

Event dispatch

There's two types of event dispatching going on:

  • direct dispatch
  • catch-up
Direct dispatch

This is the most common scenario when the system is running. When all view managers are up-to-date with current events and when a new event batch has been emitted by the command processor, the events are delivered directly to the views and they get to process the events very quickly and with very low latency.

Catch-up

Periodically (each few seconds or so), the view manager event dispatcher will check if any of its views are not up-to-date with current events.

This can happen when the system starts after you have introduced a new view, or if you clear the data of an existing view.

If that is the case, the view manager event dispatcher will begin to let views catch up by loading events from the event store until all views are up-to-date.

Configuration

You configure the view manager event dispatcher with the .EventDispatcher(...) part of the configuration API, e.g. like so:

CommandProcessor.With()
    .(...)
    .EventDispatcher(e => e.UseViewManagerEventDispatcher(views))
    .Create();

where views is a IViewManager[]. Each call to UseViewManagerEventDispatcher will result in a new view manager event dispatcher instance, and thus one more worker thread dedicated that the specified views.

This means that you can parallelize event processing by e.g. doing this:

CommandProcessor.With()
    .(...)
    .EventDispatcher(e => {
        e.UseViewManagerEventDispatcher(firstPoolOfViews);
        e.UseViewManagerEventDispatcher(secondPoolOfViews);
        e.UseViewManagerEventDispatcher(integrationViews);
    })
    .Create();

Waiting for views to catch up

Since event processing works asynchronously in the background, it can sometimes be challenging to model your system to accommodate for that. The most frequent scenario probably being the classic "save and show" scenario where a user performs some action in the system and the system shows the effect of that action immediately.

Of course you could simulate in the UI that the action had already been performed. Another option is to actually wait for the views to catch up, which will often happen so fast that it's negligible compared to e.g. web requests and other stuff going on.

You can wait for views on a view manager event dispatcher basis. You do it by creating a ViewManagerWaitHandle, which you register for the event dispatchers you want to be able to wait for, e.g. like so:

var waitHandle = new ViewManagerWaitHandle();

var processor = CommandProcessor.With()
    .(...)
    .EventDispatcher(e => {
        e.UseViewManagerEventDispatcher(firstPoolOfViews).WithWaitHandle(waitHandle);
        e.UseViewManagerEventDispatcher(secondPoolOfViews).WithWaitHandle(waitHandle);
        e.UseViewManagerEventDispatcher(integrationViews);
    })
    .Create();

and then when the application is running, you should get the result of processing your command:

var result = commandProcessor.ProcessCommand(someCommand);

The command processing result contains the highest global sequence number that was emitted as a consequence of executing that command.

Now, with the result in hand, we can either wait for one specific view to catch up:

await waitHandle.WaitFor<SomeSpecificView>(result, timeout);

wait for several specific views:

await Task.WhenAll(
    waitHandle.WaitFor<SomeSpecificView>(result, timeout),
    waitHandle.WaitFor<AnotherSpecificView>(result, timeout)
);

or simply wait for all views:

await waitHandle.WaitForAll(result, timeout);

In all these cases, timeout is the maximum TimeSpan you want to wait. If the timeout is exceeded, an exception is thrown, so you should set it high enough that this only happens if errors somewhere prevent one or more views from catching up.

Other configuration options

Events are processed in batches. The view is persisted only after it's done processing a batch. Usually a batch consists of all the events emitted in a single command (see Command). However, you can change the maximum amount of events per batch so that the batches of events are smaller.

CommandProcessor.With()
    .(...)
    .EventDispatcher(e => {
        e.UseViewManagerEventDispatcher(firstPoolOfViews).WithMaxDomainEventsPerBatch(123);
    })
    .Create();

Dependent view manager event dispatcher

This dispatcher accepts a list of view managers on which it will depend and a list of view managers to which it will dispatch. It will then capture all the events sent to those dependent view managers and dispatch the same events to the second list of view managers.

CommandProcessor.With()
    .(...)
    .EventDispatcher(e => {
        e.UseDependentViewManagerEventDispatcher(newViewManagers)
            .DependentOn(existingViewManagers);
    })
    .Create();

The configuration options are similar to those available on the view manager event dispatcher.