Part 2 of the series OO-Design
The Anemic Domain Model is often quoted as an anti-pattern in Domain-Driven Design. Martin Fowler goes so far as to term the alternative a “rich domain model”.
A rich domain model makes perfect sense until you attempt to write your first unit test. You discover that rich domain models are very hard to test. Not only that, but most examples of unit-testing use Anemic Domain Models! Do you really have to pick between good testing and good design?
Let’s Talk About Purity
Functions are considered pure if they produce the same output when given the
same input. Addition is a pure function. We expect
add(1,1) will always
2. Pure functions are the easiest to test. The more logic you have
in pure functions, the easier your code is to test.
Consider which of the following is easier to test. A pure function with the signature:
GameState movePlayerNorth(GameState g)
or an impure function with the signature:
The first function has explicit inputs and outputs. The second function has
implicit, hidden state. While the
void function is easier to call, it
impurity makes it much harder to test. Testing a impure function requires
implicit state to be setup before each test. Since GameState is encapsulated,
you would need to either expose it (breaking encapsulation) or call dozens of
methods to get it into the correct state for your test!
a rich domain modelimpure.
The point of encapsulation is to hide state from the caller; it is impure by definition. Impure functions require global or class-level state to operate.
Pure functions have been maligned as a bad design. Purity has been given nasty sounding names like “anemic domain model” or “exposed state”. In reality, there is nothing wrong with pure programming, it produces good designs that are easy to understand and test.
Purity is a good design!
We should strive for purity as much as possible. In most languages, purity is an ideal to strive for, not a goal that can ever be fully achieved. Language features like exceptions and null will always prevent complete purity.
Purity in object-oriented languages is always on a continuum. Most codebases have a variety of functions: some pure, some impure. On the one side you have rich domain models with completely encapsulated state and void methods. On the other side you have easily-tested code with pure functions and separate data structures.
If you need or desire testability, strive to put as much logic as possible inside pure functions. Avoid encapsulated state: put all state in data structures with all public fields. Strive to make your codebase as pure as possible.