57

Most of DDD tactical design patterns belong to object-oriented paradigm, and anemic model describes the situation when all business logic is put into services rather than objects thus making them a kind of DTO. In other words anemic model is a synonym of procedural style, which is not advised for complex model.

I am not very experienced in pure functional programming, yet I'd like to know how DDD fits into FP paradigm and whether term 'anemic model' still exists in that case.

Update: Recentlry published book and video on the subject.

And one more video from Scott.

5 Answers5

30

The way the "anemic model" problem is described doesn't translate well to FP as is. First it needs to be suitably generalized. At it's heart, an anemic model is a model which contains knowledge about how to properly use it that isn't encapsulated by the model itself. Instead, that knowledge is spread around a pile of related services. Those services should only be clients of the model, but due to its anemia they're held responsible for it. For example, consider an Account class that can't be used to activate or deactivate accounts or even lookup information about an account unless handled via an AccountManager class. The account should be responsible for basic operations on it, not some external manager class.

In functional programming, a similar problem exists when data types don't accurately represent what they're supposed to model. Suppose we need to define a type representing user IDs. An "anemic" definition would state that user IDs are strings. That's technically feasible, but runs into huge problems because user IDs aren't used like arbitrary strings. It makes no sense to concatenate them or slice out substrings of them, Unicode shouldn't really matter, and they should be easily embeddable in URLs and other contexts with strict character and format limitations.

Solving this problem usually happens in a few stages. A simple first cut is to say "Well, a UserID is represented equivalently to a string, but they're different types and you can't use one where you expect the other." Haskell (and some other typed functional languages) provides this feature via newtype:

newtype UserID = UserID String

This defines a UserID function which when given a String constructs a value that is treated like a UserID by the type system, but which is still just a String at runtime. Now functions can declare that they require a UserID instead of a string; using UserIDs where you previously were using strings guards against code concatenating two UserIDs together. The type system guarantees that can't happen, no tests required.

The weakness here is that code can still take any arbitrary String like "hello" and construct a UserID from it. Further steps include creating a "smart constructor" function which when given a string checks some invariants and only returns a UserID if they're satisfied. Then the "dumb" UserID constructor is made private so if a client wants a UserID they must use the smart constructor, thereby preventing malformed UserIDs from coming into existence.

Even further steps define the UserID data type in such a way that it's impossible to construct one that's malformed or "improper", simply by definition. For instance, defining a UserID as a list of digits:

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

To construct a UserID a list of digits must be provided. Given this definition, it's trivial to show that it's impossible for a UserID to exist that can't be represented in a URL. Defining data models like this in Haskell is often aided by advanced type system features like Data Kinds and Generalized Algebraic Data Types (GADTs), which allow the type system to define and prove more invariants about your code. When data is decoupled from behavior your data definition is the only means you have to enforce behavior.

Jack
  • 4,539
12

To a large degree, immutability makes it unnecessary to tightly couple your functions with your data as OOP advocates. You can make as many copies as you like, even making derivative data structures, in code far removed from the original code, without fear of the original data structure unexpectedly changing out from under you.

However, a better way to make this comparison is probably to look at which functions you are allocating to the model layer versus the services layer. Even though it doesn't look the same as in OOP, it's quite a common mistake in FP to try to cram what should be multiple levels of abstraction into one function.

As far as I know, no one calls it an anemic model, as that's an OOP term, but the effect is the same. You can and should reuse generic functions wherever applicable, but for more complex or application-specific operations, you should also provide a rich set of functions just for working with your model. Creating appropriate abstraction layers is good design in any paradigm.

Karl Bielefeldt
  • 148,830
11

When using DDD in OOP, one of the main reasons to put business logic in the domain objects themselves is that business logic is usually applyed by mutating the state of the object. This is related to encapsulation: Employee.RaiseSalary probably mutates the salary field of the Employee instance, which should not be publicly settable.

In FP, mutation is avoided, so you'd implement this behaviour by creating a RaiseSalary function that takes an existing Employee instance and returns a new Employee instance with the new salary. So no mutation is involved: only reading from the original object and creating the new object. For this reason, such a RaiseSalary function does not need to be defined as a method on the Employee class, but could live anywhere.

In this case, it becomes natural to separate the data from the behaviour: one structure represents the Employee as data (completely anemic), while one (or several) modules contain functions that operate on that data (preserving immutability).

Notice that when you couple data and behaviour as in DDD you generally violate the Single Responsibility Principle (SRP): Employee may need to change if the rules for salary changes change; but it may also need to change if the rules for calculating EOY bonus change. With the decoupled approach this is not the case, since you can have several modules, each with one responsibility.

So, as usual the FP approach provides greater modularity/composability.

la-yumba
  • 186
1

I think the essence of the matter is that an anemic model with all domain logic in services that operate on the model is basically procedural programming - as opposed to "real" OO programming where you have objects that are "smart" and contain not just data but also the logic that is most closely tied to the data.

And the same contrast exists with functional programming: "real" FP means using functions as first-class entities, which are passed around as parameters, as well as constructed on the fly and returned as return value. But when you fail to use all that power and have only functions that operate on data structures that are passed around between them, then you end up in the same place: you're basically doing procedural programming.

-3

I'd like to know how DDD fits into FP paradigm

I think it does, but largely as a tactical approach to transitioning between immutable Value Objects or as a way to trigger methods on entities. (Where most of the logic still lives in the entity.)

and whether term 'anemic model' still exists in that case.

Well, if you mean "in a way analogous to traditional OOP", then it helps to ignore the usual implementation details and go back to basics: What language do your domain-experts use? What intent are your capturing from your users?

Supposing they talk about chaining processes and functions together, then it seems functions (or at least "do-er" objects) basically are your domain-objects!

So in that scenario, an "anemic model" would probably occur when your "functions" aren't actually executable, and are instead just constellations of metadata that are interpreted by a Service which does the real work.

Darien
  • 3,463