0

I have to solve a domain problem and I have some doubts about what is the better solution. I am going to present the problem:

I have Applications and each Application has many Process. An Application has some ProcessSettings too. I have some business rules when I have to create a Process, for example, based on the process settings of application, I have to apply some rules on some process properties.

I have considered Application as aggregate root and Process as other aggregate root, and ProcessSettings as a value object inside Application aggregate.

I have a use case to create processes, and the logic is to create a valid instance of process and persist it with ProcessRepository. Well, I think I have two options to apply the process settings:

  1. In the use case, get the process settings from Application aggregate by ApplicationId through a domain service in Application aggregate, and pass ProcessSettings to process create method.
  2. In the use case, to create the process and through a domain service in Application aggregate pass a copy of process (a value object) to apply the process settings.

What approach do you believe is most correct to use?, or do you implement it in another way?

Thanks in advance!

  • What happens to existing processes when process settings are changed in a way they are no longer valid or when processes change in a way they are not longer valid after being created? – plalx Mar 20 '19 at 19:51
  • Good question, you are right, but I didn't want introduce all requirements but now I am going to do it. Well, the clients buy an Application and they pay for configuring the process settings in your application. Our product owner told us that if the client paid for some settings in a moment and created a process that settings will be valid for that process if the client does not update it. If the client leave to paid some settings then, when the client want to update that process our system will not allow update it because the actual settings will not be fit to the process data. – user1129875 Mar 21 '19 at 08:30

1 Answers1

0

Our product owner told us that if the client paid for some settings in a moment and created a process that settings will be valid for that process if the client does not update it. If the client leave to paid some settings then, when the client want to update that process our system will not allow update it because the actual settings will not be fit to the process data

That makes the implementation much easier, given that process settings-based validation only has to occur in process creation/update scenarios. Furthermore, I would guess that race conditions would also be irrelevant to the business, such as if settings are changed at the same time a process gets created/updated.

In light of this, we can assume that ProcessSettings and Process can be in distinct consistency boundaries. In other words, both can be part of separate aggregate roots.

Furthermore, it's important to recognize that the settings-based validation are not Process invariants, meaning the Process shouldn't be responsible for enforcing these rules itself. Since these aren't invariants you also shouldn't strive for an always-valid strategy and use a deferred validation strategy instead.

From that point there are many good ways of modeling this use case, which will all boil down to something like:

//Application layer service
void createProcess(processId, applicationId, data) {
    application = applicationRepository.applicationOfId(applicationId);
    process = application.createProcess(processId, data);
    processRepository.add(process);
}

//Application AR
Process createProcess(processId, data) {
    process = new Process(processId, this.id, data);
    this.processSettings.ensureRespectedBy(process);
    return process;
}

If ProcessSettings are part of the Application AR then it could make sense to put a factory method on Application for creating processes given it holds the necessary state to perform the validation, like in the above example. That removes the need from introducing a dedicated domain service for the task, such as a stand-alone factory.

If ProcessSettings can be it's own aggregate root you could always do the same, but introduce a lookup domain service for settings:

//Application AR
Process createProcess(processId, data, settingsLookupService) {
    process = new Process(processId, this.id, data);
    processSettings = settingsLookupService.findByApplicationId(this.id);
    processSettings.ensureRespectedBy(process);
    return process;
}

Some might say your aggregate is not pure anymore however, given it's performing indirect IO through calling the settingsLookupService. If you want to avoid such dependency then you may introduce a domain service such as ProcessDomainService to encapsulate the creation/update logic or you may even consider the lookup logic is not complex enough and put it directly in the application layer.

//Application layer service
void createProcess(processId, applicationId, data) {
    processSettings = processRepository.findByApplicationId(applicationId);
    process = application.createProcess(processId, data, processSettings);
    processRepository.add(process);
}

There's no way for us to tell which approach is better in your specific scenario and sometimes there isin't even a perfect way and many various ways could be equally good. By experience it's a good idea to keep aggregates pure though as it's easier for unit tests (less mocking).

plalx
  • 42,889
  • 6
  • 74
  • 90
  • Thanks for your time and solution. I agree with it, but now I have some questions about it. I like so much your first approach. In this case `Process` would be an entity into `Application` AR isn't it?. If yes, what happen if an application had thousands of processes?, Should `Process` be a new separate AR? – user1129875 Mar 21 '19 at 16:45
  • Another question would be the following. In the second approach, the domain service one, would it be correct find the `ProcessSettings` aggregate inside a `Application` use case?, Would not I be coupling both aggregates?. – user1129875 Mar 21 '19 at 16:53
  • @user1129875 No, in the first approach `Process` would still be it's own aggregate, but you would use a factory method on the `Application` AR to create it, given `Application` contains the state necessary to perform the creation, such as passing it's own ID to the `Process` and validate it with it's settings. As you can see in the example, `Application.createProcess` returns a `Process` instance which is then added to the repository in the application layer. – plalx Mar 21 '19 at 17:26
  • @user1129875 Yes, it would introduce some level of coupling between both ARs, just like `Application` is also coupled to `Process` given it participate in it's creation. It's very hard not to introduce coupling between related ARs while staying pragmatic and expressive. We are following the ubiquitous language closely (e.g. "a process is created for an application" -> `process = application.createProcess(...)`) at the expense of coupling that could be avoided with `process = new Process(...)` for instance. It's hard to tell which is best, but with DDD the focus is usually on the language. – plalx Mar 21 '19 at 17:32
  • Good, then, with the first approach, the `Application` AR does not contain the processes it create, isn't it?, You only would use that AR to create and update processes, isn't it?. Others operations about process would be on `Process` AR directly, isn't it? – user1129875 Mar 21 '19 at 18:46
  • Yes, Process is it's own AR. – plalx Mar 22 '19 at 02:14