Bojan Vukasovic
Bojan Vukasovic

Reputation: 2268

CQRS read model projection - business logic

So, I trigger command on aggregate root and it has some 10 events happened as a result of the command. This events are internal ones, and since outer systems need aggregation of this events, I decided to make projection (read projection basically). In order to make this projection from 10 events (internal) TO 1 event (external), I have to apply some business rules (business rules concerning merging of events). Where should I put this rules, since it seems like part of domain but I'm creating projections of internal events?

Basically since projection logic is part of domain, should I keep it inside aggregate and call it in code where projection is made?

UPDATE

So, inside one aggregate root, I have e.g. 3 events (internal) as response to one Command (aggregate.createPaintandwashatsametime(id, red)) that is sent to aggregate root and that are spreading through all the aggregate root entities like: CarCreated(Id), CarSeatColored(Red), CarWashed() etc. (all this 3 events are happened because of single command). External system expects to receive one external event as CarMaintainenceDone(Id, repainted=true, washed=true, somevalue=22);

Now, if i have some complex logic to make this CarMaintainenceDone event (like if(color==red then in projection somevalue==22 otherwise 44) - should this go in projection code or be part of domain?

UPDATE 2

Let me try to give you new example. Just ignore how domain is modeled since this is just example:

As you can see we have AggregateRoot that contains Multiplier which is there just to call things with the right name. When we do multiplication we first send integer 1 to ObjectA which has some logic to set internal state and emit ObjectAHasSetParam event. The same thing goes with ObjectB. Finally, ObjectC listens to all of this events, and on paramsHasBeenSet will do actual multiplication.

In event store in this case I would preserve list of events:

[ObjectAHasSetParam , ObjectBHasSetParam , ObjectCHasMultiplied ]

My point here was: if I emit all of this events one by one out of process - the state that somebody else updates will possibly be inconsistent, since this 3 events make sense only together. That is why I wanted to make something like projection, but I think in this case I just need to publish list of this events together instead of event by event.

class AggregateRoot{
    Multiplier ml;

    void handle(MultiplyCommand(1,2)){
        ml.multiply(1,2);
    }
}

class Multiplier{
    ObjectA a;
    ObjectB b;
    ObjectC res;

    void multiply(1,2){
        a.setParam(1);
        b.setParam(2);
        publish(paramsHaveBeenSet());
    }
}

class ObjectA{
    int p;

    void setParam(1){
        p = 1 + 11;
        publish(ObjectAHasSetParam(12));
    }
}

class ObjectB{
    int p;

    void setParam(2){
        p = 2 + 22;
        publish(ObjectBHasSetParam(24));
    }
}

class ObjectC{
    int p1; int p2;
    int res;

    listen(ObjectAHasSetParam e1){
        p1 = e1.par;
    }

    listen(ObjectBHasSetParam e2){
        p2 = e2.par;
    }

    listen(paramsHaveBeenSet e3){
        res = p1 * p2;
        publish(ObjectCHasMultiplied(288));
    }
}

Upvotes: 1

Views: 2028

Answers (3)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57239

External system expects to receive one external event as CarMaintainenceDone(Id, repainted=true, washed=true, somevalue=22);

A ha! The short answer is process manager.

The longer answer is that you (should) have two aggregates right now. One of them is tracking the state of the car. The other is tracking the process of maintaining the car.

The big hint that there is another aggregate hidden somewhere: you've got this CarMaintenanceDone event, with no aggregate responsible for generating it. All events have an "aggregate" somewhere that produces them. The aggregate might be the real world, or a proxy for the real world (HttpRequestReceived), or a digital thing in some other bounded context; but the event is telling you that something, somewhere, changed state.

That is to say, you have some aggregate that knows the rule of when the maintenance is done. It's an information resource, a log of work. When CarWashed is published (by the Car, or the washing machine, or whatever), an event handler subscribed to the CarWashed event sends a command to the Maintenance aggregate to inform it. The Maintenance aggregate updates its own state, runs its logic, and publishes a MaintenanceCompleted event when all of the individual steps have been accounted for.

Most things that are process like can be implemented as Aggregates; the weird bit is that the "commands" tend to look like event handlers. But they have their own history (based on what they have observed), which describes how the state machine changed in response to each event observed.

It might be more than two, depending on the complexity of the processes.

Rinat Abdullin wrote a good introduction to process managers, that I reference frequently.

Isn't there a clear distinction between an aggregate and a process manager though? I thought process managers would only coordinate and live in the application service world, sending appropriate commands to aggregates based on the events received.

From what I've seen -- no, there isn't. The literature doesn't make that very clear.

For example, Udi Dahan wrote

Here’s the strongest indication I can give you to know that you’re doing CQRS correctly: Your aggregate roots are sagas.

Saga, here, being equivalent to a process.

Upvotes: 2

Constantin Galbenu
Constantin Galbenu

Reputation: 17683

There should not be a notion of a external event. Events are generated by the Aggregates and consumed by synchronous read-models, sagas or published to the outside world where other systems and microservices use them whatever they want.

So, in your case, the consumer (implemented as a saga for example) should aggregate those events by its business rules and then do something (a saga can create a new command for example) and not the Aggregate.

UPDATE (in response to question being updated)

If you think that car maintenance is a responsibility of the Car Aggregate, then Car aggregate should raise the event. It depends on how the future behavior of the Car Aggregate is influenced by that CarMaintainenceDone event. In this particular context, I would generate the event from the Car aggregate, to make code simpler.

Upvotes: 0

plalx
plalx

Reputation: 43718

There's often 2 event models, internal events (only visible within a BC) and external events (published to the outside world). You could decide to make everything external but then you have to version everything.

You can read more about internal vs external events in the Patterns, Principles, and Practices of Domain-Driven Design book p.408 (scroll up a bit in the link).

Projections shouldn't be responsible to publish external events. One common practice would be to register an internal event handler from the application service layer which is responsible for publishing external events on a messaging infrastructure. You could leverage that process to aggregate these events together and publish a single external event from them.

How the aggregation is performed would be up to you, but since internal events can be raised synchronously and handlers are usually single-threaded you can just setup a state machine in the handler that kicks-in when it receives the first event of the batch and aggregates them until it receives the last, then publish on the message bus.

If your messaging infrastructure cannot participate in the same transaction as your event store you could just have an additional process that reads the committed events in order and does the same thing as above.

An alternative would be to let the consumer deal with the aggregation. That could be the right approach if the consumer should be able to veto what "CarMaintenanceDone" means.

Finally, you could also publish an extra event from the aggregate itself. The event may not be leveraged by the AR itself, but sometimes it's better to just do what's more practical (just like enriching events with data only consumed by the read model). This approach would also have the advantage of not having to change the logic if more events are added.

Upvotes: 1

Related Questions