Projection

So far, we’ve covered developing a Write Model for our Roast Planning Bounded Context for First Pop Coffee Company. We’ve also talked about how the Bus acts as a “spinal cord” for messages being passed throughout this Bounded Context. The Bus transports Commands and Events to Handlers that know what to do with them.

We spent the last post talking about how we publish Events through the Bus to one or more Event Handlers. Events are our primary mechanism for achieving consistency outside of consistency boundaries. In a scenario where we’ve chosen to apply CQRS, they are how we make our Read Model consistent with our Write Model… eventually.

So how do we make our Read Model consistent with all of the work we’re doing in our Write Model? Let’s build an in-memory Read Model. We’ll also start our UI for the Roast Planning Bounded Context.

Our Read Model is a separate persistence store that is used and optimized for reading data. How do we get data into this persistence store? By using Event Handlers. These Event Handlers are responsible for handling Events emitted from the Write Model after it changes Aggregate state.

We are using an Event Store (fake at the moment) in our Write Model for the Roast Scheduling use case. Notice that our Event Store (copied from Greg Young’s SimpleCQRS project contains an instance field of type IEventPublisher. The EventStore class uses _publisher to publish events after it persists them:

public class EventStore : IEventStore {
    private readonly IEventPublisher _publisher;
   	...

When the SaveEvents of the EventStore class is finished saving Events to whatever storage mechanism we’ve chosen, it calls _publisher.Publish(@event):

public void SaveEvents(Guid aggregateId, IEnumerable<Event> events, int expectedVersion) {
    List<EventDescriptor> eventDescriptors;

    // try to get event descriptors list for given aggregate id
    // otherwise -> create empty dictionary
    if (!_current.TryGetValue(aggregateId, out eventDescriptors)) {
        eventDescriptors = new List<EventDescriptor>();
        _current.Add(aggregateId, eventDescriptors);
    }
    // check whether latest event version matches current aggregate version
    // otherwise -> throw exception
    else if (eventDescriptors[eventDescriptors.Count - 1].Version != expectedVersion && expectedVersion != -1) {
        throw new ConcurrencyException();
    }
    var i = expectedVersion;

    // iterate through current aggregate events increasing version with each processed event
    foreach (var @event in events) {
        i++;
        @event.Version = i;

        // push event to the event descriptors list for current aggregate
        eventDescriptors.Add(new EventDescriptor(aggregateId, @event, i));

        // publish current event to the bus for further processing by subscribers
        _publisher.Publish(@event);
    }
}

The Bus that we just spent a few posts talking about, implements IEventPublisher. It is what we’re using in the Roast Planning Context for making sure Messages get to the right handlers. Read Model Projections are registered in our Bus as Event Handlers of Events. In this case, we’ve named the Read Model Projection RoastScheduleView:

// ... when bootstrapping the application
bus.RegisterHandler<RoastScheduleCreatedEvent>(
    new RoastScheduleView().Handle);

In the next post, let’s see what the RoastScheduleView.Handle method looks like. And as always, make sure you sign up to get more detailed content about related topics. Click the big red button below!


continue reading…

previous…


I will be releasing most of this content only to subscribers. Make sure you sign up by clicking the big red button!


Related Posts:

Tweet
comments powered by Disqus