DEV Community

Avoid anemic domain models by empowering your objects

Ka Wai Cheung on January 16, 2017

One of the common anti-patterns I see in a lot of object-oriented code today (including my own) is what Martin Fowler refers to as anemic domain mo...
Collapse
 
dubyabrian profile image
W. Brian Gourlie

Your article illustrates a problem that is hard to model nicely in classic OO, requiring a bunch of nuanced code to overcome these shortcomings. I'd argue that inter-mixing data and "process" is a consequence of these shortcomings.

Since you're using C#, I'll illustrate how this problem would be more appropriately modeled in F#. We'll start by modeling a type that encodes the activity date:

type ActivityDate = 
    | ArbitraryDate of DateTime
    | Today
Enter fullscreen mode Exit fullscreen mode

Here we've declared a sum type (aka discriminated union) that encodes all the information necessary to determine if the activity date is EITHER an arbitrary date, OR a value that indicates that the user is filtering on today's activity. Now we'll define the actual domain model:

type ActivityFilter = { activityDate : ActivityDate; /* Hiding the other properties */ }
Enter fullscreen mode Exit fullscreen mode

As you can see, we require one fewer properties than the C# example in order to convey the same amount of information.

Take a moment to consider how much code was required in the C# examples to determine the actual date we need to filter on. Here's how that would look in F#:

let getFilterDate activityDate = 
    match activityDate with
    | ArbitraryDate date -> date
    | Today -> DateTime.Now.Date
Enter fullscreen mode Exit fullscreen mode

In a grand total of 8 lines of code, we've modeled the same problem in a much cleaner, more straightforward way. Debating where the methods that operate on our domain models should live is largely an exercise in bike shedding. Instead, we should be asking if the languages and paradigms we use are anemic.

Collapse
 
ghost profile image
Ghost

You realize you can add methods to the domain models right? What he means by "anemic" is that you shouldn't have methods on the domain model that's affecting other state in the application or reaching out to a database or some other service. IsActivityDateSetToToday can easily be a method on the model.

Collapse
 
developerscode profile image
Ka Wai Cheung

Hi Nate -

Yes, I know you can add methods to domain models.

However, I disagree with how you interpret what Fowler means by "anemic". Yes, if your methods are affecting other application states, that's poor encapsulation; or, if your domain model is holding onto database connections, that's probably not the right place for it. But that's not what he's talking about.

A domain model is anemic when it isn't owning the business logic that it rightfully should own. (i.e...."Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming.")

In my example, the ActivityFilter ought to own the business logic around what the correct activity date is.

I don't make IsActivityDateSetToToday an exposed method because that's exactly the opposite of what I want to achieve. It's an internalized parameter that only the ActivityFilter knows about so that it can deduce what ActivityDate to hand back to whomever asks for it.

Collapse
 
ghost profile image
Ghost

"In my example, the ActivityFilter ought to own the business logic around what the correct activity date is."

Perhaps I've misread the article, but I don't think this is the case. The ActivityFilter knows what date the user chose, but if that needs to be changed dependent on some outside conditions it shouldn't own that behavior. I think you should keep ActivityFilter as it is originally described.

You should have another class, perhaps FilterDateService that has a method like GetFilterDate(ActivityFilter) DateTime or something like that that handles that business logic of retrieving the actual date you want to use.

Thread Thread
 
developerscode profile image
Ka Wai Cheung

I think we're basically just having the very debate that Fowler is having between those that view anemic domain models as an anti-pattern and those that do this by design to adhere to another kind of architecture (aka a service-oriented).

From his post: "Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data."

I'd argue against the service-oriented approach for this specific case because it feels like a lot more hoops to jump across to get the right date. Why not just grab ActivityFilter.ActivityDate as opposed to instantiating a new service and calling a method against it?

I'm not against a service approach at all (I do it alot) but I think certain logic (my example being one) is better served inside a domain model, even if the models all get tossed around between services.

Collapse
 
antonfrattaroli profile image
Anton Frattaroli

Articles about rich domain models always have unfortunate examples where view logic is put into properties. I know there are meaningful uses of it, but you can't serialize logic. If you're running a service oriented architecture then rich domain models add unnecessary complexity.

Collapse
 
developerscode profile image
Ka Wai Cheung

Hey Anton-

I'm not sure where I've put view logic into properties here?

Also, I agree there are some gray areas between a service-oriented architecture and rich domains, but I think there is a pretty black-and-white middle ground. He writes about that in the 2nd half of this post: martinfowler.com/bliki/AnemicDomai...

Collapse
 
squarepusher profile image
Paul Mortimer

Hi Ka,

Great article, well written. I've been chewing over this topic for about ten years now. At various times my opinions change, I get new insight due to problems encountered in new business domains.

One of the largest blockers I see to the use of good design are large enterprise systems. Multi-national companies may want a common object foo, but foo.makepayment() means a different implementation in each national office. This complexity encourages an anaemic approach. It's far easier to create a central payment service which processes an anaemic foo and applies regional logic flow, than own fifty versions of foo.makepayment().

The constraint on leveraging good design appears to be scale and ownership of business rules.

Thanks

Paul

Collapse
 
feroxneto profile image
Fernando Ferox Neto

Hi Paul,

That is a quite interesting question.
It would be nice if anyone could suggest an approach to this scenario that favours the rich model over the anemic one.