efacilitation/eventric

Projections question

Closed this issue · 3 comments

Hello

Here is my thing :
AAggregate hasMany BAggregate
AContext and BContext both have a projection with id: { ... } stores
Into AProjection I'd like to add the belonging BProjection list
Targeted AProjection store :

a1_id:
    foo: 'bar'
    _b : 
        b1_id: { ... }
        b2_id: { ... }
        ...
a2_id: 
    ...

My idea was to load B context in A's projection and listen to all domain events

AProjection:
    initialize: (opt, cb) ->
       BContext.subscribeToAllDomainEvents(@refreshB)
       cb()

Then look for a defined aggregate into the domain event to query the updated B entity

AProjection:
    refreshB: (dE, cb) ->
         if dE.aggregate
              BContext.query('get', id: dE.aggregate.id).then (BState) ->
                    AProjectionStore[BState._A_foreign_key]._b[BState.id] = BState
         cb()

Here is the idea... But my problem is that BProjection::handleBCreated() is called AFTER the query sent the result...

I don't really know how to implement this AProjection to load consistent B values without reload all my AProjection each time BContext emits a BProjection:change and without copy my Bprojection logic into AProjection...

Perhaps propagation logic should be from the aggregate_id subscriptions to AllEvents subscriptions ? Perhaps projections should be able to emit their own events ? Perhaps you though a better way...?

I can try pull requests but I just want us to be on the same page :)

Hi,

so if I understood you correctly you want to build a "global" projection which subscribes to the domainevents from multiple contexts, right?

Something around this lines:

class GlobalProjection
  fromAContext_handleSomethingHappened: (domainEvent, done) ->
    # ..
  fromBContext_handleItHappened: (domainEvent, done) ->
    # ..

Would that work?

This is it, but not so easy...
In fact my GlobalProjection uses context's projection...
I can see four ways to handle my case :

The simple one

The simple solution is to listen projection:change event on each context then update the global projection, using those projections.

+

Easy to implement

-

The Global Projection doesn't know what has changed in each context projection, then has to update the whole global projection
This is not very subtile

The one you proposed

Listen to DomainEvents on each context and build a global projection

+

I think this can be useful, this is a kind of saga projection, I think this is a good idea for the project.

-

In my case this means that I have to duplicate my entity state upgrades logic outside of context, and I would like to keep it only in my context.

Let's take an example

A User context.
A Todos context.
A global projection with both User details and a list of tasks for each User Object

The first solution means if a user completed a task, I have to rebuild the whole global projection because I don't know which task is updated..

The Domain Events solution would mean that I have to listen each Domain Event updating a task and rebuild task state depending on events...

Let's evolve the simple one... with projection events

One simple way is to permit projection events...
Let's imagine an event for my user projection
projection:TodoProjection:changed ( aggregateId )
Then my global projection would be able to query this specific aggregateId to the TodoProjection to update itself... without business logic.

A leaner way to do this woul be to add the DomainEvent Object on the existing projection:Name:changed event ! I will PR this !

!! OK I just saw that I just have to update my dependencies !! You're so awesome guys ! ;)

Let's evolve the other one !

If the global projection listens to all ( or just a few ! ) domain events of both context.
And if an aggregate id is set on domain event then query to the context projection the state of this entity...
About the example
The Todo context emits a TodoCompleted Event...
My global projection catch the concerned DomainEvent.aggregate.id
Then the global projection can query to the Todo context the updated task
TodoContext.query('get', {id: aggregate_id}

I like the last one but the problem is that today, when I query the todo projection, the projection hasn't handled the event yet ! Then after a creation for example, the Entity doesn't exist yet !
This works like a charm if I defer the query a few
(a setTimeout fn, 0 doen't work, a setTimeout fn, 1000 does.... But this is really crappy! This is just for the proof of concept :) !)

Ok, I realised, using the projection DomainEvent emited with the change event, that the last proposition above is useless !
because I can get the domain events via the projection event itself !

So really cool ! But I still think that you should keep your GlobalProjection idea in mind, you may find a case whereit's very useful :)

Regards, and keep going.