DDD Relate Aggregates in a long process running

398 views Asked by At

I am working on a project in which we define two aggregates: "Project" and "Task". The Project, in addition to other attributes, has the points attribute. These points are distributed to the tasks as they are defined by users. In a use case, the user assigns points for some task, but the project must have these points available. We currently model this as follows:

  1. “task.RequestPoints(points)“, this method will create an aggregate PointsAssignment with attributes points and taskId, which in its constructor issues a PointsAssignmentRequested domain event.
  2. The handler of the event issued will fetch the project related to the task and the aggregate PointsAssigment and call the method “project.assignPoints(pointsAssigment, service)”, that is, it will pass PointAssignment aggregate as a parameter and a service to calculate the difference between the current points of the task and the desired points. If points are available, the project will modify its points attribute and issue a “ProjectPointsAssigned” domain event that will contain the pointsAssignmentId attribute (in addition to others)
  3. The handler of this last event will fetch the PointsAssingment and confirm “pointsAssigment.Confirm ()”, this aggregate will issue a PointsAssigmentConfirmed domain event
  4. The handler for this last event will bring up the associated task and call “task.AssignPoints (pointsAssignment.points)”

My question is: is it correct to pass in step 2 the aggregate PointsAssignment in the project method? That was the only way I found to be able to relate the aggregates. Note: We have created the PointsAssignment aggregate so that in case of failure I could save the error “pointsAssignment.Reject(reasonText)” and display it to the user, since I am using eventual consistency (1 aggregate per transaction).

We think about use a Process Manager (PointsAssingmentProcess), but the same way we need the third aggregate PointsAssingment to correlate this process.

1

There are 1 answers

1
Gustavo Andrade Ferreira On

I would do it a little bit differently (it doesn´t mean more correct). Your project doesn´t need to know anything about the PointsAssignment.

If your project is the one that has the available points for use, it can have simple methods of removing or adding points.

  • RemovePointsCommand -> project->removePoints(points)
  • AddPointsCommand -> project->addPoints(points)

Then, you would have an eventHandler that would react to the PointsAssignmentRequested (i imagine this guy has the id of the project and the number of points and maybe a status field from what you said)

This eventHandler would only do:

on(PointsAssignmentRequested) -> dispatch command (RemovePointsCommand)

// Note that, in here it would be wise to the client to send an ID for this operation, so it can do it asynchronously.

That command can either success or fail, and both of them can dispatch events:

  • RemovePointsSucceeded
  • RemovePointsFailed // Remember that you have a correlation id from earlier persisted

Then, you would have a final eventHandler that would do:

  1. on(RemovePointsSucceeded) -> PointsAssignment.succeed() // Dispatches PointsAssignmentSuceeded

  2. on(PointsAssignmentSuceeded) -> task.AssignPoints (pointsAssignment.points)

On the fail side

  1. on(RemovePointsFailed) -> PointsAssignment.fail() // Dispatches PointsAssignmentFailed

This way you dont have to mix aggregates together, all they know are each others id´s and they can work without knowing anything about the schema of other aggregates, avoiding undesired coupling.

I see the semantics of the this problem exactly as a bank transfer.

You have the bank account (project) You have money in this bank account(points) You are transferring money through a transfer process (pointsAssignment) You are transferring money to an account (task)

The bank account only should have minimal operations, of withdrawing and depositing, it does not need to know anything about the transfer process.

The transfer process need to know from which bank it is withdrawing from and to which account it is depositing to.

I imagine your PointsAssignment being like

{
  "projectId":"X",
  "taskId":"Y",
  "points" : 10,
  "status" : ["issued", "succeeded", "failed"]
}