mgryszko
mgryszko

Reputation: 576

Can Domain Services access Repositories?

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)
    }
}

Upvotes: 24

Views: 8187

Answers (2)

Gehooa Liou
Gehooa Liou

Reputation: 33

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.

Upvotes: 1

plalx
plalx

Reputation: 43718

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.

Upvotes: 14

Related Questions