Reputation: 1061
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
Upvotes: 4
Views: 1504
Reputation: 14072
Should the service layer contain so much logic? if not where should it go?
I'm assuming you're talking about services in the Application layer here, not the Domain layer. It seems your domain objects are almost anemic, which some consider to be an anti-pattern, but there's a lot of debate over that.
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 behavior 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.
Note that I'm talking about purely Domain business rules here, not application-specific rules. For instance, the fact that some operation has to be performed through a wizard with 4 steps, that the user is asked for confirmation at the end of the last step, and that all modifications are flushed to the persistent store after the last step are application-specific business rules, not Domain rules. Therefore they shouldn't be moved to the the Domain layer.
if the domain, should aggregate roots hold references to repositories?
IMO an Aggregate root shouldn't hold a reference to its own repository and know how to store itself, because it breaks persistence ignorance and introduces an additional responsibility in the domain object that will muddle it. However, an Aggregate Root could occasionally hold a reference to another entity's Repository.
How should transactionality be handled?
I'd say there are 2 types of transactions :
"User" transactions in the Application layer, aka "units of work". These are high-level transactions that span a use case or last for the lifetime of a web page in a web application ("open session in view"). ORM frameworks often provide facilities for managing these transactions.
Transactions in the Domain layer. They can be initiated by a domain object or service. For example : FundsTransferService.Transfer() could use a transaction internally. Here you can use the basic transaction handling of your platform.
Should entities (or aggregate roots) hold references to domain services? If so, how should they obtain the references?
Yes, entities can sometimes call domain services. Domain services contain domain rules and behavior that don't belong in any entity. You can hardcode these dependencies or inject them in the entities, depending on the level of decoupling you want.
In order to get a new id for an entity, we must call a stored procedure, which we have wrapped in a repository. Where would you reference this? Some complex methods on Entities which need to create many child entities would need to reference this?
I wouldn't recommend making ID generation for entities accessible on demand. As David pointed out, it's often a better idea to generate a Guid at the language level when you new up the entity.
If you still want to go the ID generation route, calling the ID generator is typically a job for the Factory of your entity rather than the entity itself.
Upvotes: 5
Reputation: 8295
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?
Upvotes: 12