I am currently developing some reasonably large applications for a client in the travel industry using .net and nhibernate, and am coming up against a few issues with implementing DDD, and disagreements within the team on the best way to proceed. I'm hoping someone can offer some guidance.
At the moment, we have implemented a service layer outside of the domain, with a service per aggregate root ([EntityName]Service). All other layers use these services get references to an aggregate root via methods like GetByThis() and GetByTheOther(). All our calls into the domain from other layers are via these services.
The services hold injected references to repositories (which are referenced nowhere else) and are also responsible for all save/update behaviour and managing transactionality. The service methods are growing in complexity, and sometimes have behaviour which seems to belong to the domain, like conditional creation logic (if property = this set child objects to something, otherwise something else). Our domain entities have mostly simple methods like GetByThis() and HasAThing(). I feel that we are losing the expressiveness of our domain.
My main issues are:
EDIT
Thanks for the well thought answers @david-masters and @guillaume31.
You have helped me resolve that ‘smelly code’ feeling that I was getting.
Firstly, I should have said that we have a (very) legacy oracle DB to contend with, hence the Id Generation requirement (amongst other issues).
For anyone else who looks at this, both answers gave excellent advice, but for me, this was the best advice:
“From a pragmatic perspective, here's what I would ask myself : if I want to take a part of my Domain layer and reuse it in another application, will it contain all the business rules and behaviour I need to leverage the domain in that new application ? If not, then maybe it means some parts that are currently on the application side need to be moved to the domain layer.”
I reassessed our domain and service layer with this in mind, and now believe I have resolved our design issues
The application service layer should contain no domain logic. The purpose of application services is to 'orchestrate'. It shouldn't make any domain decisions; all business decisions should be in domain objects or domain services. The application service receives a call from a consumer (typically a UI) and invokes methods in the domain and infrastructure services. Application services shouldn't have crud names as you've described. They should have meaningful verbs that describe the use case. Here's an example of what an application service might look like for a banking application:
public class AccountService : IAccountService
{
//These are injected via dependency injection on the constructor
private readonly IAccountRepository _accountRespository;
private readonly IEmailNotificationService _emailNotificationServce;
public void FreezeAccount(Guid accountId)
{
Account account = _accountRespository.GetById(accountId);
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
{
account.Freeze();
_accountRespository.Save(account);
_emailNotificationServce.Send(CreateFreezeNotification(account));
}
}
}
I would recommend that your entities/aggregates do not hold reference to repositories so there is no dependency at all. If an aggregate needs information from a second aggregate to make a decision, the application service should fetch the second aggregate from it's repository and pass it to the first aggregate via a method.
I would apply the same principle to Domain Services. If a domain service is required (normally where a use case needs to involve multiple aggregates in one transaction (although you should try to avoid this to reduce concurrency issues by designing your aggregates better)) then the application service should first fetch the aggregates required, then pass them to the domain service. The domain service can then invoke the domain logic on the aggregates.
Transactions should be handled at this Application Service level. As you can see above, all logic that is invoked and persisted is wrapped in a UnitOfWork. Only when this block completes without error does the transaction complete.
With regards to IDs: I always opt for Guid's rather than database IDs. I just find life is so much easier and avoids the problem you describe. If your database needs be incharge of the IDs (such as an INT IDENTITY column) then perhaps you can make that a secondary ID property, and use a Guid ID for domain purposes to save the overhead?
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