Can Domain Services access Repositories?
Asked Answered
H

2

24

Can Domain Services access Repositories? Or they should work on Aggregates/Entities passed to them by Application Services?

Consider two code samples of the same business operation - money transfer. As first step, I alter account balances. Then I obtain the notification email and send the notification. I know, I should probably abstract the way notifications are sent (email, SMS, carrier pigeon), but for simplicity's sake let's assume that we support only emails by now.

Variant 1 uses repositories inside the domain service. Variant 2 resolves dependencies in the application service and passes them to the TransferDomainService.

In this example the operation is simple (subtract money from one account and add it to another). But if there would be more business rules involved (possible requiring access to more aggregates)? If variant 2 is applied, then the application service must have the knowledge what exactly the domain service requires. If variant 1 is chosen, then the domain service asks repositories for what it requires to perform its task.

(Notes about snippets: Groovy code to strip verbosity of Java. DDD building blocks included in names)

Variant 1

class TransferApplicationService {
    def transferDomainService
    def customerDomainService
    def emailNotifierInfrastructureService

    def transfer(fromAccount, toAccount, amount) {
        transferDomainService.transfer(fromAccount, toAccount, amount)
        def email = customerDomainService.accountNotificationEmail(toAccount)
        emailNotifierInfrastructureService.notifyAboutTransfer(email, amount)
    }
}

class TransferDomainService {
    def accountRepository
    def transfer(fromAccount, toAccount, amount) {
        def from = accountRepository.findByNumber(fromAccount)
        def to = accountRepository.findByNumber(toAccount)
        to.decreaseBalance(amount)
        from.increaseBalance(amount)
    }
}

Variant 2

class TransferApplicationService {
    def accountRepository
    def transferDomainService
    def customerDomainService
    def notifierInfrastructureService

    def transfer(fromAccount, toAccount, amount) {
        def from = accountRepository.findByNumber(fromAccount)
        def to = accountRepository.findByNumber(toAccount)
        transferDomainService.transfer(from, to, amount)
        def email = customerDomainService.accountNotificationEmail(toAccount)
        notifierInfrastructureService.notifyAboutTransfer(email, amount)
    }
}

class TransferDomainService {
    def transfer(fromAccount, toAccount, amount) {
        to.decreaseBalance(amount)
        from.increaseBalance(amount)
    }
}
Hatteras answered 14/11, 2014 at 12:33 Comment(5)
Yes. A Domain Service is still an orchestrator only its area of expertise is more restrictedSarsaparilla
@Sarsaparilla do you mean by "Yes" "Domain Services access Repositories" or "they work on Aggregates/Entities passed to them by Application Services"?Hatteras
Yes to the title question.Sarsaparilla
I this vimeo.com/130256611 video the guy advices to not use repositories in your ApplicationsService. Your ApplicationService orchestrates DomainDervices, where a DomainService uses repositories.Spoliate
We are using DDD with CQRS. We use Handlers that decide what to do with UI data coming as Commands. Most of the time a Handler has a repository of an aggregate root injected. It calls a method on the AR and saves it back to the repository. A "Handler" IS an Application Service. While for instance an OrderNumberGenerator is a Domain Service. It can also require and receive a repository. It's just inside the Domain Layer, not the Application.Consequence
F
14

Well, I would say that if choosing which entities to load comes down to a good deal of domain logic, then I might delegate that task to the domain service. However, I would usually strive to resolve aggregate root references in application services.

However, I think you might have a few other issues in here or at least you could use some other DDD tactical patterns like Domain Events to improve your design.

In my opinion, you shouldn't have any notification sending code in the application service at all. Instead, a MoneyTransferred domain event could be raised by the domain service. You would then have a subscriber to this event which would be in charge for sending the email.

In addition to decoupling your components, you are enriching the ubiquitous language of your domain. Sending a notification now occurs in response to a money transfer being made rather than as part of the same process and many other interested parties could react as well.

Finally, your domain service is currently violating the rule of modifying only one aggregate root per transaction. I'm not saying you can never break the rule, but usually that's a good indicator that you should be using eventual consistency, or perhaps that your aggregate boundaries are wrong.

If you think about it, money transfers between accounts rarely occurs in an atomic way (if they ever do). I guess that could be the case if the two accounts are in the same bank, but eventual consistency has to be used when the transfer spans multiple banks.

Forehead answered 15/11, 2014 at 3:18 Comment(17)
Don't take my sample as code as something taken from the production code :). I just wanted to set a context for the Domain Service - Repository variants.Hatteras
@Hatteras Well, in DDD you cannot have answers on fictionnal domains and then apply that to your own domain. All answers always comes down to "it depends". You have a set of rules and guidelines, but nothing that is ever set in stone. What I am saying is that application services should resolve aggregates to the domain service, unless that leaks domain logic in the application service (e.g. finding those dependencies is based on domain rules). Also, what I am saying is that, if you were not modifying both aggregate in the same transaction, perhaps you would not even need a domain service.Forehead
There is no such 'rule' that only one AR should be modified by one one service.Sarsaparilla
@Sarsaparilla Only one aggregate should be modified per transaction since aggregates are transactionnal boundaries... you can find that rule pretty much everywhere (e.g. "And a properly designed bounded context modifies only one aggregate instance per transaction in all cases" taken from this article by no else but Vaughn Vernon himself).Forehead
I can't think of an example right now, but it shouldn't be an artificial rule. It all depends on the Domain and a business process might involve more than 1 AR. An aggregate defines consistency boundaries but a Domain service (use case) might need to work with multiple aggregates i.e multiple different transactions. I don't know if I make much sense, DDD is full of very subtle and tricky nunancesSarsaparilla
@Sarsaparilla They key word is different transactions. If you modify 2 AR in the same transaction you are asking for trouble and your design is probably wrong. I am not saying that no business process invovles more than one AR, that's quite common actucally, but eventual consistency is also generally used in these cases.Forehead
@Sarsaparilla Je viens de voir que tu viens de MLT. Étais-tu au workshop IDDD de Vaughn Vernon à Montréal?Forehead
@Forehead I barely understand french :) No, I wasn't.Sarsaparilla
Consider an appointment app. Each appointment is an aggregate. We want to swap two appointments. Eventual consistency is a read concern, not a domain concern. We only want to allow our domain to modify one appointment if it successfully modifies the other. That's a transaction involving 2 AR's, but the design doesn't appear wrong to me.Hairtail
@Hairtail "Eventual consistency is a read concern, not a domain concern" -- That's not right. EC should be used most of the time to bring 2 independent ARs in agreement. You can modify 2 ARs in the same transaction in some scenarios, like the one you mentioned, but EC should usually be favored.Forehead
OK, I disagree. For me, eventual consistency refers to the consistency between the read model and the source of truth (event store, database etc). Until a command is processed, no state has changed. Once it has, the write model is immediately consistent, the read models are eventually consistent with that change. Processing commands in a queue isn't eventual consistency for me, it's just queuing. In my example, if the business requirement is that our source of truth can't be in a state of having overlapping appointments, do you agree transactions could be used?Hairtail
@Hairtail You seem to have a few misconceptions. Please read Effective Aggregate Design. "can't be in a state of having overlapping appointments, do you agree transactions could be used?" Ideally you'd have an AR boundary designed to protect that rule in a strongly consistent way (if necessary) and therefore wouldn't need to have a transaction that spans multiple ARs. For instance, if an Appointment cannot span over many days, then you may have a DaySchedule AR which allows to protect against overlapping appointments on that day.Forehead
If the Appointment was responsible for it's own schedule then how would you prevent multiple appointments being booked on the same date concurrently in a strongly consistent way? Sometimes, having an AR boundary that is large enough to protect invariants is impractical because the boundary would be too large. In that case you can use eventual consistency. For instance, rather than preventing overlapping appointments you could rather detect conflicts after the fact and mark them for resolution in an eventual consistent way (e.g. you listen to AppointmentScheduled events to do so).Forehead
We're discussing two separate issues here. Eventual consistency refers to consistency between two things. In this case, that's the write model, and the read model. Not consistency with reality, or business logic (every system would be eventually consistent). Before a command is executed, the state of the aggregates hasn't changed, so what are we going to be eventually consistent with? In my example, of swapping two appointments, DaySchedules doesn't solve the issue as they don't have to be on the same day. I deliberately picked an example I don't feel can be designed away.Hairtail
'how would you prevent multiple appointments being booked on the same date concurrently' - this is exactly my question. It's not practical in some cases to implement some form of optimistic update, followed by event-triggered commands to resolve - as that costs you scalability (and could mislead the user due to the interim state, and what to change?). In this case, I am personally of the view that I would maintain an internal state in a more traditional relational DB and insert the events within the same transaction as a conditional update. In the case of swap, that includes both changesHairtail
Let us continue this discussion in chat.Forehead
That seems to be quite a common question in DDD. One similar question is here: softwareengineering.stackexchange.com/questions/387919/…Scottie
J
1

I would like do both ways:

  1. application to load account objects with repo and pass them to domain service object to finish transfer between accounts. application persistent domain objects.
  2. domain service object load from repo and finish transfer between accounts and returns 2 accounts to application, application persistent domain objects.

I prefer the first one.

Joliejoliet answered 22/10, 2021 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.