Sharing of event source stream between aggregates

aggregatecqrsdomain-driven-designevent-sourcing

I have a question related to cqrs + event sourcing. I have two aggregate roots (AR1 and AR2). When AR1 received a command/event it will be recreated from previous event sourcing stream from repository (in this case it will have list of some values x,y,z) and after updating internal state to (a,b,c,x,y,z) it will emit new events (a,b,c). This events will be fetched by AR2 and then processed. At the point when I need to recreate AR2 from event source stream, dashed layer inside AR2 should be excatly same as leyer inside AR1 – so there I should have (a,b,c,x,y,z).

One more notice – in AR2 this layer would be read-only = needed to do some business logic inside AR2.

The question is – could I use same events generated in AR1 inner layer to recreate AR2 dashed layer?

There is always option to duplicate data (have the same stream of events stored both in AR1 layer and in AR2 dashed layer) but this will just waste memory. Another option is always to send all the data from AR1 layer to AR2 whenever some event happens – in this case I would send also x,y,z together with a,b,c.

[UPDATE]
It just came to my mind that I can model this as AR2 having reference to AR1 and asking AR1 each time for data. Just have to see how to handle this case since in CQRS aggregate cannot return data?

Aggregate Roots Event

What about this implementation with saga style below?

Saga aggregates communication

Update: This is what I have so far – saga can be used for sending events transformed to commands from AR1 to AR2 (basically 2nd picture), but in case of instantiating new AR2 (green color in pic2 – that should not exist there actually), I do it from service layer like this:

namespace serviceLayer{

    public void createAr1AndAr2(Ar1Id idar1, Ar2Id idar2){
            var ar1 = getAr1FromRepo(iadr1);
            var ar2 = getAr2FromRepo(idar2);        //returns regularAr2 or nullObjectAr2
            ar1.doSmth(ar2);
    }

}

namespace domain{

    class Ar1 {
        List<Smht> partOfInnerState;

        void doSmth(Ar2 ar2){
            ar2.doSmth2(partOfInnerState);
        }
    }

    class regularAr2 extends Ar2{
        void doSmth(List<Smht> partOfInnerState){
                //we need only part of partOfInnerState
        }
    }

    class nullObjectAr2 extends Ar2{
        void doSmth(List<Smht> partOfInnerState){
                //we need full partOfInnerState in order to initialize this object for the first time
        }
    }
}

Best Answer

This events will be fetched by AR2 and then processed.

Ohh, that sounds like a bad idea.

One more notice - in AR2 this layer would be read-only = needed to do some business logic inside AR2.

So looking at your picture, AR2 is writing out events (d,e,f), which says that AR2 is not read only -- which is good; read only aggregate roots don't make sense.

The usual pattern for what you seem to be trying to do here is to use a process manager to coordinate the activities of the two aggregate roots. The role of the process manager is to listen for events, and respond by dispatching commands.

In that picture, you would have something like the following:

Command(A) arrives at AR(1)
    AR(1) loads its history [x,y,z]
    AR(1) executes Command(A), producing events [a,b,c]
    AR(1) writes its new history [x,y,z,a,b,c]

Events(a,b,c) are published

ProcessManager receives the events (a,b,c)
    ProcessManager dispatches Command(B) to AR(2)

Command(B) arrives at AR(2)
    AR(2) loads its own history [d,e,f]
    AR(2) executes Command(B), producing events [g,h]
    AR(2) writes its new history [d,e,f,g,h]

Events(g,h) are published

Trying to have two different aggregate roots share a common event history is really weird; it strongly suggests that your model needs rethinking (why are there two different authorities for the same fact? what happens when AR1 writes an event that violates the invariant enforced by AR2?).

But taking some of the state from one event, and making that an argument in a command sent to another aggregate; that pattern is pretty common. The process manager itself is just a substitute for a human being reading the events and deciding what commands to fire.

I always need current state of AR1 layer, even if AR2 is created from the scratch it needs to have content of layer from AR1. The layer would be read only.

There's no such thing as getting the "current" state of another aggregate; AR1 could be changing while AR2 is doing its work, and there's no way to know that. If that's not acceptable, then your aggregate boundaries are in the wrong place.

If stale data is acceptable, you can have the AR2 command handler query the state of AR1, and use that information in processing the command. If you are going to do that, I normally prefer to wrap the query in a Domain Service, which gives you an extra layer of indirection to work with (the domain model doesn't need to know how the service is implemented). In this design, AR2 doesn't see the AR1 events at all; AR2 passes some state to the domain service, and the domain service looks at the events to figure out the answer, and passes that answer back as a value that AR2 will understand.

Whittaker's solution isn't bad; once you recognize that the data is stale anyway, you have the option of deciding whether the state available at the time of creating the command is good enough. I'm of mixed minds on this -- putting everything into the command is nice, and really easy to understand. On the other hand, there is a larger window for a change to happen, and to some degree discovering the right data to use requires accessing state internal to the aggregate that can change while the command is in flight.

I much prefer designs where the aggregates aren't coupled, though.

But it seems that this is again sharing of data between the AR-s, since fat command will use data from layer from AR1 to supply to AR2

You might look into what Udi Dahan has to say about services as technical authorities. In that case, the data that gets shared is mostly limited to opaque identifiers.

Related Topic