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
  • 1
    If you're saying what I think you're saying here, DTO's are anemic, but first-class objects in DDD, and that there is a natural separation between the DTO's and the services that process them. I agree in principle. So does this blog post, apparently. Commented May 4, 2016 at 22:29
  • 2
    Strongly related, if not an outright duplicate: Why is an anemic domain model considered bad in OOP, but very important in FP? Commented May 4, 2016 at 22:56
  • 1
    "whether term 'anemic model' still exists in that case" Short answer, the anemic model term was coined in the context of OO. Speaking of an anemic model in the context of FP makes no sense at all. There may be equivalents in the sense of describing what is idiomatic FP, but it has nothing to do with anemic models.
    – plalx
    Commented May 4, 2016 at 23:14
  • 7
    Eric Evans once was asked what he says to people who accuse him that what he describes in his book is just good object-oriented design, and he answered that it's not an accusation, it's the truth, DDD is just good OOD, he just wrote down some recipes and patterns and gave them names, so that it's easier to follow them and talk about them. So, it's no surprise that DDD is linked to OOD. The broader question would be, what are intersections and differences between OOD and FPD, although you would have to define what you mean by "functional programming" first. Commented May 4, 2016 at 23:14
  • 2
    @JörgWMittag: You mean other than the usual definition? There are plenty of illustrative platforms, Haskell being the most obvious one. Commented May 4, 2016 at 23:16

5 Answers 5

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.

2
  • 2
    And what about aggregates and aggregate roots guarding invariantы, are the last ones also can be expressed and easily comprehended by developers afterwards ? For me the most valuable part of DDD is a direct mapping of business model to the code. And you answer is exactly about that. Commented May 5, 2016 at 11:03
  • 7
    Nice speech, but no answer to OP question.
    – SerG
    Commented Sep 12, 2017 at 16:05
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.

2
  • 2
    "To a large degree, immutability makes it unnecessary to tightly couple your functions with your data as OOP advocates.": Another reason for coupling data and procedures is implementing polymorphism though dynamic dispatch.
    – Giorgio
    Commented May 5, 2016 at 7:31
  • 4
    The main advantage of coupling behavior with data in the context of DDD is providing meaningful business related interface; it's just always at hand. We have a natural way of self documenting code (at least it is what I am used to), and that is the key to successful communications with business experts. How then is it accomplished in FP? Probably piping helps, but what else? Doesn't generic nature of FP make business requirements harder to reverse engineer from the code? Commented May 5, 2016 at 7:40
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.

2
  • 2
    On the isssue of violating SRP in the DDD context, You can still try to inject some services into domain entity - basing on Your example, those calculating salary or bonus.
    – Mic
    Commented Mar 20, 2020 at 14:08
  • 1
    When data (I mean domain models) and functions that operate upon that data (like raiseSalary() are decoupled, where should those functions be placed so they keep organized, modular and discoverable? I'm thinking in Java/Kotlin/Scala, but also interested on how to organize it in Haskell. Should it be extension methods in a companion object? Thx! Commented May 14, 2021 at 10:44
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.

1
  • 5
    Yes, that's basically what the OP says in his question. Both of you seem to have missed the point that you can still have functional composition. Commented May 4, 2016 at 22:33
-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.

3
  • 2
    An anemic model would occur when you pass abstract data types around, such as tuples, records or lists, to different functions for processing. You don't need anything nearly as exotic as a "function that doesn't execute" (whatever that is). Commented May 4, 2016 at 23:03
  • Hence the quotation-marks around "functions", to emphasize how inappropriate the label becomes when they are anemic.
    – Darien
    Commented May 5, 2016 at 2:01
  • If you're being ironic, it's a bit subtle. Commented May 5, 2016 at 2:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.