0

I have a problem with my personal project.

I have there Project, which has Stages, and Stages have Tasks. At first, I was trying to make Project and AggregateRoot and Stage and Tasks Entities inside that Aggregate. As there are other Entities such as Costs, Installments, FinancialData and many, many more there as well Project has started to grow into god class, so I have reconsidered in all and made Project, Stage, and Task separate AggregateRoots.

So I have started refactoring it, and all was fine, but I have a problem with one functionality. Status system. Sometimes changes of the status on Task can start a chain of changing status of Stage and then Project as well (for example, adding new Task to finished Stage should put that stage into in progress status, and then if Project was in finished status, should be moved to in progress as well). Here is my question. How to approach that? What I was doing till now, I was loading Project from the repository in one of the first actions in application service that was marked with @Transactional and saving at the end of that method after all actions.

After refactoring sometimes there is a need that I need to change three AggregateRoots in one transaction. If that should be then one Aggregate, then Project is coming back to the state, when it has tons of methods to handle all changes on Stages and Tasks. I'm a bit lost here.

Should load all three at the very beginning of the action, pass them in the chain of actions, and at the end of the method call save on each repository?

Artur Jarosz
  • 81
  • 4
  • 14

2 Answers2

1

The operations which can touch multiple aggregates are often best modeled as sagas (there's an alternative if event-driven, but there's nothing in your question indicating that the rest of the system is event-driven). The saga would operate on the various aggregates and, importantly, be able to handle a failure/rejection of an operation (e.g. depending on requirements: retry (implies a potentially arbitrarily long period of visible inconsistency), undo changes to other aggregates, or tear down the system (sacrifice availability for consistency)).

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
  • The whole status system in my project is event-driven, but events listeners that are being called there expect to have AgreggateRoot as a method argument. Example: I have a method, that creates Tasks in the application layer. It loads the Task from the repository, then goes to domain service and handles creating task and calls another domain service to change status to ToDo. Then listeners are fired and check whether there is a need to change status on Stage. But all that happens in the layer below one, where I use repositories. So what I miss here is a Stage being loaded from repository. – Artur Jarosz Sep 12 '21 at 10:25
  • If the stage is being updated, why do you need to load it from a repository? – Levi Ramsey Sep 12 '21 at 10:27
  • This is the problem, that while Project was my Aggregate root, I was passing Project, and it has Stages and Stages had Tasks, so I could get what I need from the Project. Right now Project, Stage and Task are separate AR, so while creating Task, I don't have access to Stage from it, so while I need to update Stage, I don't have it loaded there. This is the whole problem. – Artur Jarosz Sep 12 '21 at 10:37
  • Then a saga is what is called for, recognizing that each update will (at least from a DDD perspective) be a separate transaction. – Levi Ramsey Sep 12 '21 at 11:02
  • OK, thanks for comments @Leci Ramsey. I will need to refactor services responsible for status changes so that they can be put in the chain of actions in saga, and not being just on big chain of actions somewhere in domain logic. – Artur Jarosz Sep 12 '21 at 16:36
  • As @LeviRamsey refers to a saga you find a [question about the differences between a saga and a process manager](https://stackoverflow.com/questions/15528015/what-is-the-difference-between-a-saga-a-process-manager-and-a-document-based-ap) relevant. I mainly use process managers and I regard a saga as just another process manager. – Eben Roux Sep 15 '21 at 04:41
1

You should understand do you really need transactional consistency between your statuses, maybe eventual consistency will be a solution and you will update statuses by events. In case it requires transactional consistency then it must be one aggregate because this is the main feature of aggregate to protect true invariants. To find an answer to the question you need to ask the business about this in a real project. The important thing is true invariants, in your example, I think, you have entity-oriented aggregates, but you need more policy or process-oriented aggregates with capability is only to protect true invariants rather than being data containers. Maybe this video will be helpful Mauro Servienti - Talk Session: All Our Aggregates Are Wrong

  • This is what I am trying to figure out - whether it should be one aggregate or not. In case it is one - I have a huge root. On the other hand, if it is not, how to make sure that all of my actions are going to be rolled back when the last event fails on validation? (load task, validate the possibility of status change, change status, save task -> run listener of task status change -> load stage, validate, change status, save -> run listener of stage status change -> load project, validate, validation fails -> status of stage and task should be reverted to previous ones). – Artur Jarosz Sep 19 '21 at 10:42