98

In the debate of Rich vs. Anemic domain models, the internet is full of philosophical advice but short on authoritative examples. The objective of this question is to find definitive guidelines and concrete examples of proper Domain-Driven Design models. (Ideally in C#.)

For a real-world example, this implementation of DDD seems to be wrong:

The WorkItem domain models below are nothing but property bags, used by Entity Framework for a code-first database. Per Fowler, it is anemic.

The WorkItemService layer is apparently a common misperception of Domain Services; it contains all of the behavior / business logic for the WorkItem. Per Yemelyanov and others, it is procedural. (pg. 6)

So if the below is wrong, how can I make it right?
The behavior, i.e. AddStatusUpdate or Checkout, should belong in the WorkItem class correct?
What dependencies should the WorkItem model have?

enter image description here

public class WorkItemService : IWorkItemService {
    private IUnitOfWorkFactory _unitOfWorkFactory;

    //using Unity for dependency injection
    public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public void AddStatusUpdate(int workItemId, int statusId) {

        using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
            var workItemRepo = unitOfWork.WorkItemRepository;
            var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;

            var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
            if (workItem == null)
                throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(This example was simplified to be more readable. The code is definitely still clunky, because it's a confused attempt, but the domain behavior was: update status by adding the new status to the archive history. Ultimately I agree with the other answers, this could just be handled by CRUD.)

Update

@AlexeyZimarev gave the best answer, a perfect video on the subject in C# by Jimmy Bogard, but it was apparently moved into a comment below because it didn't give enough information beyond the link. I have a rough draft of my notes summarizing the video in my answer below. Please feel free to comment on the answer with any corrections. The video is an hour long but very worth watching.

Update - 2 Years Later

I think it's a sign of DDD's nascent maturity that even after studying it for 2 years, I still can't promise that I know the "right way" of doing it. Ubiquitous language, aggregate roots, and its approach to behavior-driven design are DDD's valuable contributions to the industry. Persistence ignorance and event sourcing causes confusion, and I think philosophy like that holds it back from wider adoption. But if I had to do this code over again, with what I've learned, I think it would look something like this:

enter image description here

I still welcome any answers to this (very active) post that provide any best-practices code for a valid domain model.

9
  • 9
    All the philosophical theories fall right to the ground when you tell them "I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll". "Entities" in the Entity Framework jargon are not the same as "Entities" as in "Domain Model"
    – Fede
    Commented Oct 6, 2013 at 19:02
  • I'm ok with duplicating my domain entities into a DTO, using an automated tool like Automapper, if that's what it takes. I'm just not sure how that is supposed to look like at the end of the day.
    – RJB
    Commented Oct 6, 2013 at 23:05
  • 22
    I would recommend you to watch Jimmy Bogard's NDC 2012 session "Crafting Wicked Domain Models" on Vimeo. He explains what rich domain should be and how to implement them in real life by having behaviour in your entities. Examples are very practical and all in C#. Commented Oct 7, 2013 at 22:48
  • 1
    thanks so much for linking, and for others reading this even if you think you aren't interested in that specific topic, suggest to check out the other videos - NDC is very high quality vimeo.com/ndcoslo/videos
    – cartalot
    Commented Oct 14, 2013 at 0:57
  • 3
    I demand love for Java too :/
    – uylmz
    Commented Feb 12, 2016 at 15:07

4 Answers 4

72

The most helpful answer was given by Alexey Zimarev and got at least 7 upvotes before a moderator moved it into a comment below my original question....

His answer:

I would recommend you to watch Jimmy Bogard's NDC 2012 session "Crafting Wicked Domain Models" on Vimeo. He explains what rich domain should be and how to implement them in real life by having behaviour in your entities. Examples are very practical and all in C#.

http://vimeo.com/43598193

I took some notes to summarize the video for both my team's benefit and to provide a little more immediate detail in this post. (The video is an hour long, but really worth every minute if you have time. Jimmy Bogard deserves a lot of credit for his explanation.)

  • "For most applications... we don't know that they're going to be complex when we start. They just become that way."
    • Complexity grows naturally as code and requirements are added. Applications can start out very simple, as CRUD, but behavior/rules can become baked in.
    • "The nice thing is we don't have to start out complex. We can start with the anemic domain model, that's just property bags, and with just standard refactoring techniques we can move towards a true domain model."
  • Domain models = business objects. Domain behavior = business rules.
  • Behavior is often hidden in an application -- it can be in PageLoad, Button1_Click, or often in helper classes like 'FooManager' or 'FooService'.
  • Business rules that are separate from domain objects "require us to remember" those rules.
    • In my personal example above, one business rule is WorkItem.StatusHistory.Add(). We're not just changing the status, we're archiving it for auditing.
  • Domain behaviors "eliminate bugs in an application a lot more easily than just writing a bunch of tests." Tests require you to know to write those tests. The domain behaviors offer you the right paths to test.
  • Domain services are "helper classes to coordinate activities between different domain model entities."
    • Domain services != domain behavior. Entities have behavior, domain services are just intermediaries between the entities.
  • Domain objects shouldn't have possession of the infrastructure they need (i.e. IOfferCalculatorService). The infrastructure service should be passed in to the domain model that uses it.
  • Domain models should offer to tell you what they can do, and they should only be able to do those things.
  • The properties of domain models should be guarded with private setters, so that only the model can set its own properties, through its own behaviors. Otherwise it's "promiscuous."
  • Anemic domain model objects, that are just property bags for an ORM, are only "a thin veneer -- a strongly typed version over the database."
    • "However easy it is to get a database row into an object, that's what we've got."
    • 'Most persistant object models are just that. What differentiates an anemic domain model versus an application that doesn't really have behavior, is if an object has business rules, but those rules are not found in a domain model.'
  • "For a lot of applications, there's no real need to build any kind of real business application logic layer, it's just something that can talk to the database and perhaps some easy way to represent the data that's in there."
    • So in other words, if all you're doing is CRUD with no special business objects or behavior rules, you don't need DDD.

Please feel free to comment with any other points that you feel should be included, or if you think any of these notes are off the mark. Tried to quote directly or paraphrase as much as possible.

3
  • Great video especially to see how the refactoring works in a tool. Much is about proper encapsulation of domain objects (to make sure they're consistent). He does a great job telling the business rules on offers, members, etc. He mentions the word invariant a couple of times (which is contract-based domain modeling). I wish the .net code would communicate better what is a formal business rule, since those change and you need to maintain them. Commented Dec 30, 2016 at 18:00
  • Late to the party, as usual. My take on 'if all you're doing is CRUD', is that too many people make that argument and forces everything into it, even when it's no longer true. Commented Feb 10, 2020 at 15:37
  • "The infrastructure service should be passed in to the domain model that uses it." i am not sure it is correct, if so then your model is not pure @rjb Commented Dec 4, 2020 at 16:29
8

Your question cannot be answered, because your example is wrong. Specifically, because there is no behavior. At least not in area of your domain. The example of AddStatusUpdate method is not a domain logic, but logic that uses that domain. That kind of logic does make sense to be inside some kind of service, that handles outside requests.

For example, if there was requirement that a specific work item can have only specific statuses, or that it can only have N statuses, then that is domain logic and should be part of either WorkItem or StatusHistory as a method.

The reason for your confusion is because you are trying to apply a guideline to code that doesn't need it. Domain models are only relevant if you have lots of complex domain logic. Eg. logic that works on entities themselves and stems from requirements. If the code is about manipulating entities from outside data, then that is not, most probably, a domain logic. But the moment you get lots of ifs based on what data and entities you are working with, then that is domain logic.

One of the problems of true domain modeling is that it is about managing complex requirements. And as such its true power and benefits cannot be showcased on simple code. You need dozens of entities with tons of requirements around them to truly see the benefits. Again, your example is too simple for domain model to truly shine.

Finally, some OT thing I would mention is that a true domain model with real OOP design would be really hard to persist using the Entity Framework. While ORMs were designed with mapping true OOP structure to relational ones, there are still many problems, and the relational model will often leak into the OOP model. Even with nHibernate, which I consider much more powerful than EF, this can be a problem.

2
  • Good points. Where would the AddStatusUpdate method belong then, in Data or another project in the Infrastrcture? What's an example of any behavior that might theoretically belong in WorkItem? Any psuedo-code or mock-up would be greatly appreciated. My example was actually simplified to be more readable. There are other entities, and for instance the AddStatusUpdate has some extra behavior -- it actually takes a status category name, and if that category doesn't exist, the category is created.
    – RJB
    Commented Oct 7, 2013 at 17:20
  • @RJB Like I said, AddStatusUpdate is code that is using the domain. So either some kind of webservice or application that uses the domain classes. And like I said, you cannot expect any kind of mockup or pseudocode, because you would need to make whole project of big enough complexity to showcase real advantage of OOP domain model.
    – Euphoric
    Commented Oct 7, 2013 at 20:36
8

I realize this question is quite old so this answer is for posterity. I want to answer with a concrete example instead of one based on theory.

Encapsulate the "changing of work item status" on the WorkItem class like so:

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

Now your WorkItem class is responsible for maintaining itself in a legal state. The implementation is pretty weak, however. The product owner wants a history of all status updates made to the WorkItem.

We change it to something like this:

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

The implementation has changed drastically but the caller of the ChangeStatus method is unaware of the underlying implementation details and has no reason to change itself.

This is an example of a rich domain model entity, IMHO.

5

Your assumption that encapsulating your business logic associated with WorkItem into a "fat service" is an inherent anti-pattern which I would argue is not necessarily.

Regardless of your thoughts on the anemic domain model, the standard patterns and practices typical of a Line of Business .NET application encourage a transactional layered approach comprised of various components. They encourage the separation of business logic from the domain model specifically to facilitate communication of a common domain model across other .NET components as well as components on different technology stacks or across physical tiers.

One example of this would be a .NET based SOAP web service that communicates with a Silverlight client application that happens to have a DLL containing simple data types. This domain entity project could be built into a .NET assembly or a Silverlight assembly, where interested Silverlight components that have this DLL will not be exposed to object behaviours that may be dependent on components only available to the service.

Regardless of your stance on this debate, this is the adopted and accepted pattern put forth by Microsoft and in my professional opinion it is not a wrong approach but then an object model that defines its own behaviour is not necessarily an anti-pattern either. If you go forward with this design it is just best to realize and understand some of the limitations and pain points that you might run into if you need to integrate with other components that need to see your domain model. In that particular case perhaps you may want to have a Translator convert your object oriented style domain model into simple data objects that do not expose certain behaviour methods.

5
  • 1
    1)How can you separate business logic from the domain model? It is the domain in which this business logic lives; the entities in that domain are executing the behaviour associated with that business logic. The real world has no services, nor do they exist in the heads of domain experts. 2)Any component that wishes to integrate with you needs to build its own domain model, because its needs will differ and it will have a different view on your domain model. It is a longstanding phallacy that you can create one domain model that can be shared around. Commented Nov 27, 2013 at 13:04
  • 1
    @StefanBilliet Those are good points about the fallacy of a universal domain model, but it is possible in simpler components and component interaction as I have done this before. My opinion is that translation logic between domain models can make for a lot of tedious and boilerplate code and if it can be avoided safely then that can be a good design choice.
    – maple_shaft
    Commented Nov 27, 2013 at 14:41
  • 1
    To be frank, I think the only good design choice is a model that a business expert can reason about. You're building a model of a domain, for a business to use to solve certain problems within that domain. Splitting behaviour from domain entities off into services makes it harder for everyone involved, because you constantly have to map what a domain experts says to service code that bears hardly any resemblance to the current conversation. In my experience, you lose a lot more time with that, than typing boilerplate. That's not to say there are no ways around boilerplace code ofcourse. Commented Nov 27, 2013 at 15:17
  • @StefanBilliet In a perfect world I agree with you where a business expert has the time to sit down with developers. The reality of the software industry is that the business expert has no time or interest in being involved at this level or worse yet the developers are expected to just figure it out with only vague guidance.
    – maple_shaft
    Commented Nov 27, 2013 at 16:18
  • True, but that's not a reason to accept that reality. To continue in such a pursuit is to waste the time (and possibly reputation) of the developers and money of the customer. The proces I described is a relationship that needs to be built over time; it takes a lot of effort, but it yields much better results. There's a reason that "Ubiquitous Language" is often considered to be the most important aspect of DDD. Commented Nov 27, 2013 at 17:00

Not the answer you're looking for? Browse other questions tagged or ask your own question.