The other day I received some feedback on the way the Domain Model in the Autumn of Agile series is shaping up. The relevant parts of the e-mailed comments are as follows…
I am a little confused about where to put the business rules. Into model objects or in a higher level layer (such as a service layer) ? For example take AddSkill method on Employee class in Skill Project. Logically, user cannot assign same SkillType to an Employee. Business rules are changeable, and for that they should handled on a higher layer as principle.
First, as I told the commenter, thanks very much for the feedback; I really appreciate the input, especially when it goes to such a great point! Because this tension between assigning responsibilities to either services or domain entities in your domain model is a frequent point of confusion among software engineers everywhere, I asked for and received approval to turn my response to him into a blog post so that others could understand my thought process on this kind of decision, so here goes…
Patterns for Business Logic in Domains
I think that the reason there is disagreement (or at least different opinions) on the Internet about the direction to follow in regards to the ‘proper’ location for business rules is that the simple answer is (like most software architecture choices): "it depends" .
The issue of ‘services’ in a Domain Model is a tricky one; its (IMHO) one of the areas in which DDD references (like Evans, Fowler, etc.) tend to offer the poorest guidance about ‘when something becomes a service’. At the extremes, you have one of the following two approaches…
- The ‘everything-is-in-a-service‘ pattern: every business rule is represented in one or more services. The services are responsible for ‘choreographing’ the interaction between your domain entities and enforcing business rules between them.
- The ‘everything-is-in-the-entity‘ pattern: all business rules are encapsulated in the domain entities and the services are relegated to the role of controlling interaction between your domain entities and the rest of the world outside your specific domain itself (like the canonical TaxCalculationService example, etc.)
Anemic Domain Model *IS* an Anti-Pattern
I am NOT a fan of the anemic domain model and DO believe it to be an anti-pattern in most cases. The approach in #1 above most often leads directly to an anemic domain model unless (as some will suggest) you take the approach of injecting one or more services into your domain entities as they are constructed. While this can seem like it mitigates the anemic domain model anti-pattern, its really just about ‘where you type the code’ that enforces the business rules and (IMHO) doesn’t use ‘domain services’ the way they were really intended.
If you are coding domain services just to inject them into one or more domain entities, this may indeed seems to be a good way to encapsulate the business logic into services (for re-use, testing, etc.). But if you then inject them into your entities, then (again) the business logic is ‘in’ your entities since the entities have to know when to invoke the services they have been handed.
Coining a New Term: ‘Service-Ignorance’
To a degree, this is the same debate about whether entities should be persistent-ignorant: should entities also be ‘service-ignorant’‘? The same reasons people (tend to) argue against injecting repositories into entities is the same reason I would usually argue against injecting services into them — in a sense a repository is a just a ‘persistence-service’ for your domain. Services and Repositories can (and often need to be) aware of the entities they manipulate, but entities (usually) shouldn’t be aware of the services or repositories that manipulate THEM.
In pattern #2 above, by placing the business rules into the actual entities, it becomes much simpler (IMO) to develop, test, and for the developer to consume (in the app) the business logic. Business rules in most systems tend to be what we mean when we say ‘behavior’ of our objects and to avoid an anemic domain model, our entities need to have both data AND behaviors. This is why I *tend* to place the business logic into the domain entities themselves: its cleaner from testing and consuming standpoint.
For example, as a service, to do AddSkill(…) I would need to do something like….
Employee emp = new Employee(); Skill skill = new Skill(); SkillService skillService = new SkillService(); skillService.AddSkillToEmployee(emp, skill);
In this case, skill and emp are just ‘dumb data containers’ (really just the old-style DTO-anti-pattern) and I do consider this to be an instance of the anemic domain model anti-pattern. I feel (in general) that business rules that are about enforcing relationships between domain entities (as AddSkill(…) does) are perfectly fine in the entities themselves since it reduces the above code to just…
Employee emp = new Employee(); Skill skill = new Skill(); emp.AddSkill(skill);
…which is (IMHO) a lot clearer, easier to understand, and an acceptable encapsulation of a business rule about enforcing the relationship between an employee and a skill.
So When do I need a Domain Service?
As a contrasting situation, here is an example of a business rule that I wouldn’t really (probably) want to encapsulate in an employee…
Employee emp = new Employee(); Skill skill = new Skill(); PromotionService promotionService = new PromotionService(); promotionService.PromoteEmployee(emp, PromotionService.EmployeeLevels.SeniorDeveloper);
In this case, the service is being invoked to make a ‘fundamental change’ to an employee by promoting them to some new ‘rank’ or ‘position’ in the company (the enum represented by ‘SeniorDeveloper’ in this contrived example). Since its entirely likely that promoting an employee to another rank would mean that more than just the emp instance would need to change (e.g., let’s say that this would also mean that their salary, their vacation time, etc. would also need to change — and in ways it would probably make NO sense for the employee object to be aware of), it seems way too much for the Employee class to ‘know’ about all of those business rules (which, BTW, might –as suggested– change over time significantly and so should be well-encapsulated into a service).
That’s Just my Opinion; I Could be Wrong!
I think where I’m headed here (again, this is just how *I* approach this stuff; there are certainly no shortage of other opinions out there!) is that when the business rule is purely about the entity at the root of the aggregate enforcing rules on its consistent components within it, its (generally) OK for that business rule to be expressed within the entity. But when the business rule begins to affect multiple aggregate roots and require the ‘coordination’ of work between aggregates, that’s when I tend to opt for expressing that logic in a domain service instead of inside the domain entities themselves.
I hope this helps clarify my thoughts on this issue. Great question and keep ‘em coming, everyone~!