Rich Domain Models – How Behavior Fits In

cdomain-driven-designobject-orientedobject-oriented-design

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.

Best Answer

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.

Related Topic