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)
}
}
Repository interface belongs to the domain layer since it plays the roles of defining the operations on Entity required for implementing business logic (Service). Implements the methods defined in Repository interface. Implements CRUD operations of the Entity and is dependent on persistence layer.
Domain Services stores centralized directory information and lets users and domains communicate. When a user attempts to connect to a device or resource on a network, this service provides login authentication, verifying the user's login credentials and access permissions.
Domain Services (or just Services in DDD) is used to perform domain operations and business rules. In his DDD book, Eric Evans describes a good Service in three characteristics: The operation relates to a domain concept that is not a natural part of an Entity or Value Object.
An aggregate is a domain-driven design pattern. It's a cluster of domain objects (e.g. entity, value object), treated as one single unit. A car is a good example. It consists of wheels, lights and an engine.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With