Search code examples
domain-driven-designcqrsevent-sourcingresolvejs

ReSolve Framework: How to properly derive an aggregate's state from the state of other aggregates?


I am doing my first project with reSolve and have limited experience in DDD, ES and CQRS. So, maybe there is a very easy solution to this and I simply haven't found it, yet.

My Problem: In my project, the state of one aggregate (status of an order) actually derives from the state of other aggregates (status of each work step belonging to this order; there can be two or more work steps going on at the same time, each having it's own status, e.g. error or finished).

After some research, my idea was to invoke an additional command (something called like "updateOrderStatus") in each command handler of the work step aggregate, e.g.:

    pauseWorkStep: (state, { payload: { orderId } }) => {
        async ({ resolve }) => await resolve.executeCommand({
            aggregateName: 'Order',
            aggregateId: orderId,
            type: 'updateOrderStatus',
            payload: {}
        })
        return {
            type: WORKSTEP_PAUSED,
            payload: {}
        }
    }

This "updateOrderStatus"-command would be located in the order aggregate. The corresponding projection function would have to query the work steps' current state in their read model - more or less like this (it's just to show the main idea):

    [ORDER_STATUS_UPDATED]: (state, { timestamp }) => ({
        ...state,
        updatedAt: timestamp,
        status: () => {
            const workStepStatus = async ({ resolve }) => await resolve.executeQuery({
                modelName: 'WorkSteps',
                resolverName: 'workStepByOrderId',
                resolverArgs: { orderId }
            })
            switch (workStepStatus) {
                case "paused":
                    // define status
                    break;
                case "finished":
                    // define another status

                ...
            }
        }
    })

Based on that, the status of the order can be determined. The problem is, this could lead to inconsistency because the read model could be outdated.

How do I solve this in the reSolve framework? Or, is there is another way doing this and not violating the principles of DDD, CQRS and ES?

Thank you for helping me out.


Solution

  • It seems your aggregate definition is too granular. Aggregate root is not an entity, it can be a collection of entities with a single root.

    Aggregate defines transactional and consistency boundaries. So if you see that command should affect several aggregates as a transaction, this is a good indication that thay are actually a single aggregate.

    In your case I guess you can make Order an aggregate, so pauseWorkStep command is send to the Order, not to the WorkStep.

    The very deep reason for this is a consistency: Aggregate is guaranteed to be in a consistent state. In your approach there is no single transaction, what if one command succeds and another fail? What if some other user sends another command between yours? You don't have control over this in command handler.

    If you really need to orchestrate several aggregates, you need to create a saga/processManager - which is a long running business transaction.

    See also this recent discussion of the similar topic: Should a single command address multiple aggregates?