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 models. He writes about them rather eloquently here.
The gist of the idea is that we often create classes whose job entails nothing more than holding onto data. In Martin's words, "The catch comes when you...realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters."
In certain cases, these kinds of objects make sense. Data transfer objects (DTOs) that serve no other purpose than to transition data from one service to another is a prime example. However, Martin argues that in the business domain of your application code, objects should intermingle data and process together. It's the entire fruit of object-oriented programming's labor.
I liken object-oriented programming to writing great characters in fiction. It's one skill to write a linear story. It's yet another to write characters. Doing so allows a story to keep building in the reader's mind. The sequel, third installment, and beyond are easier for the author to describe because the characters are developed well from the outset.
This too, is true of object-oriented writing. Anemic objects are the equivalent of poorly described characters. The more you can avoid this tendency, the easier it is to build upon the story of your code as you write its natural iterations over time. Not only does your code gains all the tangible benefits of good design, but your audience (other programmers reading your code) can better consume what you're describing as well. Good characters are memorable in that way.
I stumbled upon a real example of this problem while working through some of my own code recently. I thought I'd write about it in hopes of clarifying the anti-pattern for anyone who hasn't quite grasped the idea yet.
Recently, I worked on some C# code for a new mobile application we're building with Xamarin for my product, DoneDone. DoneDone is a simple web-based issue tracker for small development teams. On the app, we want to display the activity for specific issues during a given day. In a database, we track any modifications to an issue (edits, comments, status changes, reassignments) alongside the date the modification happened.
In code, we have a class called ActivityFilter
which holds all of a user's search criteria for the activity on their issues. It looks something like this:
public class ActivityFilter
{
public DateTime ActivityDate { get; set; }
public int ProjectId { get; set; }
public List<int> TagIds { get; set; }
public FilterType SelectedFilterType { get; set; }
public DueType SelectedDueType { get; set; }
}
The ActivityFilter
object is where we store search inputs from the user. When a user chooses a project on the app, the object's ProjectId
is updated and cached. When a user chooses to see activity on issues with specific tags, the object's TagIds
are updated and cached...and so forth.
This object fits the description of Martin's anemic domain model like a glove—nothing more than "a bag of getters and setters." But, for now, I'm actually quite content with this. The class's sole purpose is to store some filtering data that other objects will use to do the dirty work. It's anemic because it needn't be anything more.
On the mobile interface, you can choose a specific day upon which activity occurs—today, yesterday, or a random day a few years back. The application stores this date in an ActivityFilter
object and the object is then cached in memory. If you come back to the application tomorrow, the application reads the data off the cached object and displays the activity that meets whatever search criteria you last set.
When I played around with this behavior, something felt a bit odd. Suppose you choose to view activity for today. Then, you leave the application. When you open it up again tomorrow afternoon, the app shows you activity from yesterday. That makes perfect sense programmatically because the last ActivityDate
stored happens to be yesterday's date. But, it doesn't feel natural.
Imagine you choose to view today's activity, and say today is January 16th, 2017. If you close out of the application and come back tomorrow, you'd actually expect the app to show you activity for today again. Today would be the 17th rather than the 16th. This expectation is made more obvious because on the UI we display the word "Today" above the activity instead of the date, if the date you've chosen is today's date.
However, suppose you explicitly choose to view activity from a day in the past (say July 8th, 2016). When you come back tomorrow, you should still see activity from that day—it shouldn't default you back to "today".
In summary, the business logic around the ActivityDate
that I really want looks like this:
- If you set the activity date to something other than today's date, you should always see activity from that day, until you choose a new date.
- However, if you set the activity date to today's date, you should always see activity from "today" (right now, tomorrow, or three weeks from now), until you choose a new date.
All things considered, this business logic isn't terribly complex. But, who should own this bit of logic? Certainly, the ActivityFilter
object needs to store that extra bit of information. At first glance, it feels like we should expose some extra boolean on the class that denotes whether the selected date was today. Let's call it IsActivityDateSetToToday
:
public class ActivityFilter
{
public DateTime ActivityDate { get; set; }
public bool IsActivityDateSetToToday { get; set; } // new param
// Hiding the other properties in this post for clarity
}
This way, when the ActivityDate
is set, we can check against the current system date to see whether to set IsActivityDateToday
as well. Assuming _CachedActivityFilter
is the name of the cached instance of the ActivityFilter
object for the application's session, the application code that sets the date might look like this:
public void onActivityDateChanged(DateTime selectedDate)
{
_CachedActivityFilter.ActivityDate = selectedDate;
_CachedActivityFilter.IsActivityDateSetToToday = false;
if (selectedDate == DateTime.Now.Date)
{
_cachedActivityFilter.IsActivityDateSetToToday = true;
}
}
Side note: In C#, there is no built-in class that just holds a date. Instead, the DateTime
class stores both date and time information. For our purposes, we only care about the date portion. The Date
property off the DateTime
class returns a DateTime
object with the time portion automatically set to 00:00:00. Hence, throughout our code snippets, we evaluate DateTime
objects via their Date
property.
With this extra piece of information, the application code that reads the cached ActivityFilter
object would perform some logic like this to grab the correct date to search activity against:
var dateToSearchFor = _CachedActivityFilter.ActivityDate;
if (_CachedActivityFilter.IsActivityDateSetToToday)
{
dateToSearchFor = DateTime.Now.Date;
}
There is nothing obviously wrong here. The ActivityFilter
object does what's being asked of it—it stores the information that the application needs to know what activity fits the user's criteria.
However, if the role of the ActivityFilter
object is to be the keeper of a user's selection criteria, then, shouldn't it also know what date the user is asking for? This would likely be the argument Martin would make here. In a sense, we're not developing the ActivityFilter
character particularly well.
We can fix this by pushing this activity date business logic into the ActivityFilter
object itself rather than expose that information to the code that sets and retrieves the date.
First, let's privatize IsActivityDateSetToToday
. Let's also create a new private activity date property activityDate
. Then, let's expand the public ActivityDate
to handle the "today" logic when a value is set:
public class ActivityFilter
{
private DateTime activityDate;
private bool isActivityDateSetToToday;
public DateTime ActivityDate
{
get
{
// TODO
}
set
{
// Set the activity date to the passed in value
activityDate = value.Date;
// Note whether the date set is 'today'
isActivityDateSetToToday = (DateTime.Now.Date == value.Date);
}
}
// Hiding the other properties in this post for clarity
}
By doing this, we no longer expose the properties involved in the "today" logic publicly. The work is relegated to the ActivityFilter
class itself. If the set date happens to be today, the class makes note of it internally.
Now, let's move on to the ActivityDate
public get
method. Here, we can check the value of isActivityDateSetToToday
. If it's true, then ActivityDate
will return today. Otherwise, it will return the date of the last stored activity date:
public class ActivityFilter
{
private DateTime activityDate;
private bool isActivityDateSetToToday;
public DateTime ActivityDate
{
get
{
// If last set date was that day, return 'today'
if (isActivityDateSetToToday)
{
return DateTime.Now.Date;
}
return activityDate;
}
set
{
var today = DateTime.Now.Date;
var setDate = value.Date;
// Note if the date set is 'today'
isActivityDateSetToToday = (today == setDate);
activityDate = value;
}
}
/// Hiding the other properties in this post for clarity
}
Now, let's rewind and run through the revitalized ActivityDate
property.
When the property is set, the ActivityFilter
object not only sets an internal property to that day, but also notes whether that day is, in fact, today. When the property is retrieved, the ActivityFilter
object hands back the date that it was last set to, unless it had been set to that day. In this case, it will hand back whatever "today" is.
But, does fighting this anemia make the codebase noticeably better? The beauty of this encapsulation comes to fruition when we refactor the original code that sets and gets the activity date. First, the code that originally handled the extra logic now need only set the activity date:
public void onActivityDateChanged(DateTime selectedDate)
{
_CachedActivityFilter.ActivityDate = selectedDate;
}
Similarly, the code that retrieves the date to then query for the right activity simply needs to retrieve the value _CachedActivityFilter.ActivityDate
.
We've now tucked away the activity date logic into the object who owns the data surrounding that logic. Whereas previously both the code that set the date and the code that retrieved the date needed to know about the nuanced logic around "today", now they don't. They also aren't holding onto multiple responsibilities anymore.
These objects are now responsible for processes they should rightfully own. In the end, we've built a better story around our code by developing our characters more properly.
Top comments (9)
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:
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:
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#:
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.
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.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 theActivityFilter
knows about so that it can deduce whatActivityDate
to hand back to whomever asks for it."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 likeGetFilterDate(ActivityFilter) DateTime
or something like that that handles that business logic of retrieving the actual date you want to use.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.
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.
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...
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
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.