Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> It was always like that. And Fowler the same thing with his criticism of anemic domain model.

What leads you to disagree with the fact that anemic domain models are an anti-pattern?

https://martinfowler.com/bliki/AnemicDomainModel.html

I think it's obvious that his critique makes sense if you actually take a moment to try learn and understand what he says and where he comes from. Take a moment to understand what case he makes: it's not object-oriented programming. That's it.

See,in a anemic domain model, instead of objects you have DTOs that are fed into functions. That violates basic tenners of OO programming. It's either straight up procedural programming or, if you squint hard enough, functional programming. If you focus on OO as a goal, it's clearly an anti-pattern.

His main argument is summarized in the following sentence:

> In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits.

Do you actually argue against it?

Listen, people like Fowler and Uncle Bob advocate for specific styles. This means they have to adopt a rethoric style which focuses on stressing the virtues of a style and underlining the problems solved by the style and created by not following the style. That's perfectly fine. It's also fine if you don't follow something with a religious fervor. If you have a different taste, does it mean anyone who disagrees with you is wrong?

What's not cool is criticizing someone out of ignorance and laziness, and talking down on someone or something just because you feel that's how your personal taste is valued.



"It's not object oriented programming" is only a good case to make if you think object oriented programming is synonomous with good. I don't think that's true. It's sometimes good, often not good.

Why would focusing on OO be a goal? The goal is to write good software that can be easily maintained. Nobody outside of book writers are shipping UML charts


Why would you not focus on writing OO code in an OO language for example? Would you start writing OO code in a functional langugage? No you wouldn't, because it would be pointless. There are programming paradigms for a reason


> Why would you not focus on writing OO code in an OO language for example?

Often people do this to deliver higher quality software. Most languages still have some OO features, and people don't use them because they know they lead to bad code. Inheritence (a core OO feature) comes to mind. Most professionals nowadays agree that it should not be used.

OO designs are often over-abstracted which makes them hard to understand and hard to change. They lack "locality of behavior". Trivial algorithms look complicated because parts of them are strewn across several classes. This is why more modern langues tend to move away from OOP.

My guess is that im the long term, what we will keep from OO is the possibility to associate methods with structs.


> Why would you not focus on writing OO code in an OO language for example?

That's circular logic. I wouldn't focus on writing OO code because I know from experience that the result is usually worse. If I had to use a language that was oriented towards writing OO code, I'd still try to limit the damage.

> There are programming paradigms for a reason

Nah. A lot of them are just accidents of history.


> Why would you not focus on writing OO code in an OO language for example? Would you start writing OO code in a functional language? No you wouldn't, because it would be pointless. There are programming paradigms for a reason

I'm paid for efficiently solving business problems with software, not using a particular paradigm. If an FP solution is more appropriate and the team can support it, then that's what I'll use.


> Why would you not focus on writing OO code in an OO language

It should be the best solution to the problem direct whether or not use of OO is best, not the language.


> "It's not object oriented programming" is only a good case to make if you think object oriented programming is synonomous with good. I don't think that's true. It's sometimes good, often not good.

See, this is the sort of lazy ignorance that adds nothing of value to the discussion, and just reads as spiteful adhominems.

Domain models are fundamentally an object-oriented programming concept. You model the business domain with classes, meaning you specify in them the behavior that reflects your business domain. Your Order class has a collection of Product items, but you can update an order, cancel a order, repeat an order, etc. This behavior should be member functions. In Domain-Driven design, with its basis on OO, you implement these operations at the class level, because your classes model the business domain and implement business rules.

The argument being made against anemic domain models is that a domain model without behavior fails to meet the most basic requirements of a domain model. Your domain model is just DTOs that you pass around as if the were value types, and have no behavior at all. Does it make sense to have objects without behavior? No, not in OO and elsewhere as well. Why? Because a domain model without behavior means you are wasting all development effort building up a structure that does nothing and adds none of the benefits, and thus represents wasted effort. You are better off just doing something entirely different which is certainly not Domain-Driven design.

In fact, the whole problem with the blend of argument you are making is that you are trying to push a buzzword onto something that resembles none of it. It's like you want the benefit of playing buzzword bingo without even bothering to learn the absolute basics of it, or anything at all. You don't know what you're doing, and somehow you're calling it Domain-Driven design.

> Why would focusing on OO be a goal?

You are adopting a OO concept, which the most basic traits is that it models business domains with objects. Do you understand the absurdity of this sort of argument?


> Domain models are fundamentally an object-oriented programming concept.

They are not.

> You model the business domain with classes, meaning you specify in them the behavior that reflects your business domain.

I have better tools for doing that.

> In Domain-Driven design, with its basis on OO, you implement these operations at the class level, because your classes model the business domain and implement business rules.

You're still not explaining the "why". You're just repeating a bunch of dogma.

> a domain model without behavior means you are wasting all development effort building up a structure that does nothing and adds none of the benefits, and thus represents wasted effort.

I know from experience that this is completely false.

> You don't know what you're doing, and somehow you're calling it Domain-Driven design.

I don't call it domain-driven. You can call it domain-driven if you want, or not if you don't want. I don't care what it's called, I care whether it results in effective, maintainable software with low defect rates.


> I care whether it results in effective, maintainable software with low defect rates.

This is what it is about. All the other things that have been invented need to be in service of this goal.


> I have better tools for doing that.

For example?


I would assume he meant to use the typesystem of his PL.


> Your Order class has a collection of Product items, but you can update an order, cancel a order, repeat an order, etc. This behavior should be member functions.

This is how to fuck up OO and give it a bad name:

  order.update(..) // Now your Order knows about the database.

  order.cancel(..) // Now your Order can Email the Customer about a cancellation.

  order.repeat(..) // Now your Order knows about the Scheduler.
What else could Order know about? Maybe give it a JSON renderer .toJson(), a pricing mechanism .getCost(), discounting rules .applyDiscount(), and access to customer bank accounts for .directDebit(); Logging and backup too. And if a class has 10+ behaviours you've probably forgotten 5 more.

An Order is a piece of paper that arrived in your mailbox. You can't take a sharpie to it, you can't tell it to march itself into the filing cabinet. It's a piece of paper which you.read() so that you.pack() something into a box and take it to the post office. You have behaviours and the post office has behaviours. The Order and the Box do not. At best they have a few getters() or some mostly-static methods for returning aggregate data - but even then I'd probably steer clear. For instance: if the Order gave me a nice totalPrice() method, it simplifies things for later right? Well no, because in TaxCalculator (not order.calculateTax()) I will want to drill down into the details, not the aggregate. Likewise for DiscountApplier.

> Does it make sense to have objects without behavior? No, not in OO and elsewhere as well.

It does, just like in the Domain (real-world Orders). Incidentally, I believe objects-without-behaviours is one of the core Clojure tenets.

Since this is HN's monthly UB-bashing thread, I should point out that I learnt most of this stuff from him. (It's more from SOLID though, I don't think I have much to say on about cleanliness.)

The above examples violate SRP and DI.

"Single reason to change": If order.cancel(..) knows about email, then this is code I have to change if the cancellation rules change or if the email system changes. What if we don't notify over email anymore? Order has to become aware of SMS or some other tech which will cause more reasons for change.

"Dependency inversion": People know what Orders are, regardless of technical competence. They can exist without computers or any particular implementation. They are therefore (relative to other concerns here) high-level and abstract. Orders are processed using a database, Kafka and/or a bunch of other technologies (or implementation details). DI states that abstract things should not depend on concrete things.


We have a disagreement about the core of OOP. In English, a simple sentence like "The cat eats the rat" can be broken down as follows:

- Cat is the subject noun

- Eats is the verb

- Rat is the object noun

In object-oriented programming, the subject is most often the programmer, the program, the computer, the user agent, or the user. The object is... the object. The verb is the method.

So, imagine the sentence "the customer canceled the order."

- Customer is the subject noun

- Canceled is the verb

- Order is the object noun

In OOP style you do not express this as customer.cancel(order) even though that reads aloud left-to-right similarly to English. Instead, you orient the expression around the object. The order is the object noun, and is what is being canceled. Thus, order.cancel(). The subject noun is left implicit, because it is redundant. Nearly every subject noun in a given method (or even system) will be the same programmer, program, computer, user agent, or user.

For additional perspectives, I recommend reading Part I of "Object-Oriented Analysis and Design with Applications" (3rd edition) by Grady Booch et. al, and "Object Thinking" by David West.

---

That said, I think you're right about the single responsibility principle in this example. A class with too many behaviors should usually be decomposed into multiple classes, with the responsibilities distributed appropriately. However, the object should not be left behavior-less. It must still be an anthropomorphized object that encapsulates whatever data it owns with behavior.


> So, imagine the sentence "the customer canceled the order."

> - Customer is the subject noun

And this is wrong. Because the customer did not cancel the order. The customer actually asked for the order to be canceled. And the order was then canceled by "the system". Whatever that system is.

And that is the reason why it is not expressed as customer.cancel(order) but rather system.cancel(order, reason = "customer asked for it").

> Thus, order.cancel(). The subject noun is left implicit, because it is redundant.

Ah, is that so? Then, I would like you to tell me: what happens if there are two systems (e.g. a legacy system and a new system, or even more systems) and the order needs to be sometimes cancelled in both, or just one of those systems? How does that work now in your world?


mrkeen mentioned dependency inversion (DI). I think it makes sense in oop for an order to have a cancel method, but the selection of this method might be better as something configured with DI. This is because the caller might not be aware of everything involved, as well.

If the system is new and there's only one way to do it, it's not worth sweating over it. But if a new requirement comes up it makes sense to choose a way to handle that.

For example, an order may be entered by a salesperson or maybe by a customer on the web. The cancellation process (a strategy, perhaps) might be different. Different users might have different permissions to cancel one order or another. The designer of the website probably shouldn't have to code all that in, maybe they just should have a cancel function for the order and let the business logic handle it. Each order object could be configured with the correct strategy.

If you don't want to use OO, that's fine, but you still have to handle these situations. In what module do you put the function the web designer calls? And how do you choose the right process? These patterns will perhaps have other names in other paradigms but the model is effectively the same. The difference is where you stuff the complexity.


> The difference is where you stuff the complexity.

Exactly. If it were so simple, why not just put everything in one big file / class? I guess we both agree that this very quickly leads to an unmaintainable mess.

So my rule of thumb is: can a feature theoretically be removed without touching the Order entity at all? If so, then NONE of the features parts can live in the Order entity (or even be referred by it).

That means: the Order entity must know nothing about customers, sales, how it stored or cached, how prices and taxes are calculated, how an order is cancelled or repeated or orders can be archived and viewed.

Because any of those features can be removed while the others keep working and using the exact same Order entity.


> The customer actually asked for the order to be canceled.

This is why many object-oriented programmers prefer to talk about message passing instead of method calling. It is indeed about asking for the order to be canceled, and the order can decide whether to fulfill that request.


> the order can decide whether to fulfill that request.

In my world of thinking, orders don't make decisions. If I go to the business team and say "the order decided to" they'll look at me funny. And for good reasons.


Go back and read what I said about subject nouns and object nouns. When converting OO concepts to English for non-programmers, it is indeed confusing to say “the order decided not to”—you say “the order couldn’t be” instead.

I highly recommend reading the two books I recommended for further perspective on the topic. OO is predicated not on the idea that data is a bag of dead bits on which operations are performed, but that data is embodied within and encapsulated by anthropomorphic objects with their own behavior.

It is possible to get to that world of thinking from where you are now. But it is a different world. A different way of thinking.


> When converting OO concepts to English for non-programmers, it is indeed confusing to say “the order decided not to”—you say “the order couldn’t be” instead.

Passive language like "the order couldn't be" might be fine in some real world situations where I don't care about who caused the action. But in code I do care. Because somewhere in code the action has to be made. And yeah, you can put that logic into the Order entity, but then we are back to square one where "the order made the decision".

If we are talking about some event that happend, then sure, "the order was canceled" is perfectly fine. So making an "OrderWasCancelled" (or "OrderWasNotCancelled") object and storing it somewhere is intuitive. But we were talking about the action happening and that is a different thing.

Also, just to make that clear, I'm not talking just theoretically here. I started my career during the OOP hype time. I actually read books like head first design patterns and others about OOP. But ultimately, I found it's not productive at all, because it doesn't reflect how most people think - at least from my experience.

Therefore, I tend to write my code in the same way that non-technical people think. And it turns out, OOP is very far from that.


> I highly recommend reading the two books I recommended

From "Object-Oriented Analysis and Design with Applications" (3rd edition) by Grady Booch:

p.52: Separation of Concerns

  We do not make it a responsibility of the Heater abstraction to maintain a fixed temperature. Instead, we choose to give this responsibility to another object (e.g., the Heater Controller), which must collaborate with a temperature sensor and a heater to achieve this higher-level behavior. We call this behavior higher-level because it builds on the primitive semantics of temperature sensors and heaters and adds some new semantics, namely, hysteresis, which prevents the heater from being turned on and off too rapidly when the temperature is near boundary conditions. By deciding on this separation of responsibilities, we make each individual abstraction more cohesive.


The comparison to English grammar is unnecessary. I didn't use it in my argument and you said it doesn't work that way either, so when you arrive at

> The order is the object noun, and is what is being canceled. Thus, order.cancel()

You've just restated the position I argued against, without an argument.


Aw, you described it so much nicer than me. I feel bad now.


> Domain models are fundamentally an object-oriented programming concept

They are absolutely not. In fact, they are not even specific to even just programming, let alone OOP.


I really don't understand this fixation on domain modelling. It looks like a lot of UML mixed with a "*DD" (life-pro tip: pretty much any X Driven Development is something experienced programmers rarely care about. You can borrow good ideas from almost any methodology without becoming obsessed with its primary subject. Being obsessed with the One True Way is a great way to waste a lot of brain cells). Also nobody sane touches UML. Or makes big official charts of classes and their relationships. It's a massive waste of time. You might come up with some core concepts and relationships, like a B-REP, but you don't need some jargon-heavy official way to do this.

> The argument being made against anemic domain models is that a domain model without behavior fails to meet the most basic requirements of a domain model. Your domain model is just DTOs that you pass around as if the were value types, and have no behavior at all. Does it make sense to have objects without behavior? No, not in OO and elsewhere as well. Why? Because a domain model without behavior means you are wasting all development effort building up a structure that does nothing and adds none of the benefits, and thus represents wasted effort. You are better off just doing something entirely different which is certainly not Domain-Driven design.

I have barely any idea what you're saying, but I will agree that I'm probably better off without DDD.

> You are adopting a OO concept, which the most basic traits is that it models business domains with objects. Do you understand the absurdity of this sort of argument?

Except I'm not, because I don't care about DDD? My argument is simply: caring how much your code adheres to some third party methodology doesn't matter, what matters is if you're writing good code or not.


> it's not object-oriented programming. That's it.

Yes, exactly. And this "classical" object-oriented programming is an anti-pattern itself.

(That being said, OOP is not well defined. And, for example, I have nothing against putting related data structures and functionality into the same namespace. But that's not what OOP means to him here)


I'll reply here with a very quick example why the anemic domain model is superior in general, no matter if you do OOP or anything else.

You used the example of an "order" yourself, so I'll built upon it.

I would never combine functionality to update an order with the data and structure of the an order. The reason is simple: the business constraints don't always live inside the order.

Here's an example why such an approach inevitably must fail: if the business says that orders can only be made until 10000 items have been ordered in a month, then you cannot model that constraint inside of the order class. You must move it outside - to the entity that knows about all the orders in the system. That would be the OrderRepository or however you want to call it.

Remember, here is what you said in your other post:

> Your Order class has a collection of Product items, but you can update an order, cancel a order, repeat an order, etc. This behavior should be member functions.

So your Order should have a repeat function? But how can the order know if it can be repeated? It might violate the max-monthly-items constraint. The only way for the Order to do it is to hold a reference to the OrderRepository.

And this is a big problem. You have now entangled the concept of an OrderRepository and of an Order. In fact, Orders could totally live without an OrderRepository alltogether, for example when you build an OrderSimulation where no orders are actually being executed/persisted. But to do so, now you have this OrderRepository, even if you don't need it.

The rule of the thumb is: if the business says "we don't need feature A anymore, remove it" then you should be able to remove that feature from the code without touching any unrelated feature. If you now remove the OrderRepository and cause a bug in the Order class due to your code changes, the business will probably wonder how that could be, because while the OrderRepository cannot exist without Orders, Orders can exist without an OrderRepository.

And if that seems a bit unrealistic, think of users: A user can easily exist without a UserRepository, but not the other way around.

That makes clear, that you the rich domain model is an unsuitable and generally suboptimal solution to modeling the domain of a business. The anemic domain model on the other hand matches it perfectly.

And one more thing: even natural language disagrees with the rich domain model. Does an order repeat itself? No! An order is repeated and that is, it is repeated by something or someone. This alone makes clear that there is an entity beyond the Order that is responsible for such action. And again, the anemic domain model is a great solution for expressing this in code.

But if you disagree, I'd like you to explain what you believe the disadvantages of the anemic domain model are.


You made a great example here and I absolutely agree with you.

In fact I find this type of accidental / unneeded coupling is the number one cause of problems, bugs and limitations of re-use and thus development velocity in any software product. Concepts where a single way dependency is turned into a cycling dependency are really hard to evolve, maintain, test and understand.

In fact I'd go as far as to say that as a general rule of thumb if you have a situation where your class A depends on class B that depends on class A you've made a big doo doo and you should really seriously re-consider your design.

(Adjacent to this rule is that classes that exist in the same level of of the software hierarchy and are thus siblings should also not know about each other).

In fact when you structure your code so that the dependencies only go one way you end up with a neat lasagna code base and everything can easily slotted in. (Combined with this a secondary feature which is to eliminate all jumps upwards in the stack, i.e. callbacks)


> I would never combine functionality to update an order with the data and structure of the an order. The reason is simple: the business constraints don't always live inside the order.

> Here's an example why such an approach inevitably must fail: if the business says that orders can only be made until 10000 items have been ordered in a month, then you cannot model that constraint inside of the order class. You must move it outside - to the entity that knows about all the orders in the system. That would be the OrderRepository or however you want to call it.

It's not that hard.

If the constraint of your example is a domain constraint, and so it's *always* valid, then when you hydrate an Order entity, other than the Order data itself, you also need to provide the total number of orders.

```

// orders is the repository, and it hydrates the entity with the total number of orders for this month

order = orders.new()

// Apply the check

order.canBeCreated()

```

Where the `canBeCreated` method is as simple as:

```

if this.ordersInAMonth > TOTAL_NUMBER_OF_ORDER_IN_A_MONTH ...

```

Fixed.

It's the same as using the OrdersRepository to query the number of orders directly before creating one, but here, the logic is just in the class.

Now, your example is pretty stupid, so I know it must not be taken literally but...

PS: I'm all but not a DDD advocate.


I would say that this code is not good (or to be more diplomatic: not optimal). Firstly, because between `order = orders.new()` and `order.canBeCreated()`, it's possible to insert other calls or actions on or with that order, which should actually not be allowed/possible.

And second, because my original critics still holds: you now have some kind of order-entity-unrelated information inside the order (or inside its `canBeCreated()`). This will force you to touch the order-entity when removing a business constrain that is unrelated to the (single) order-entity. Because otherwise, where does "ordersInAMonth" come from? It must be able to talk to the database or something.

> Now, your example is pretty stupid, so I know it must not be taken literally but...

No no, you absolutely can take it literal. It might not be very realistic, but that doesn't change the fact that we can use it to discuss pros and cons of different designs.

> It's the same as using the OrdersRepository to query the number of orders directly before creating one, but here, the logic is just in the class.

As with my two issues that I mentioned above, the problem is the "just" in your sentence. It appears that your assessment is that the code living in a different place is merely a problem of the code being in a different place with no effect on productivity. But to me, having the code in a "wrong" place becomes a really big problem over time, especially in a big code base.

Also, we can extend this example. Let's say we have two or more entities. Like orders, users and stores and there are constraints that span and impact the state and/or creation of all of them at the same time.

Now let's compare the different approaches of us. In my case, it's rather easy: there must be some "system" or "service" the lives above all the entities that are constrained by a business rule. So if there is a business rule that touches entities A, B and C, then there must be some "system" or "service" that knows about all A, B and C and can control each of them. In other words, there cannot be a "system" or "service" that controls just A anymore. The logic to ensure the constraint then lives in that service.

With your approach, how and where do you put the code for that constraint?

And let's, just for the sake of the argument, assume that you cannot push the constraint into the database. Because that basically would be such an uber-service as described by me above. In reality, we might employ the database to (also) enforce constraints. But for the sake of the discussion, let's say we use a database where we cannot.

Looking forward to your response!


Links to any existing articles on this topic would be greatly appreciated.


I highly recommend a video if you don't mind the format: https://youtu.be/zHiWqnTWsn4?t=3134

The slide at 52:14 is on the SOLID principles, the first one is on SRP which gives pretty understandable advice about whether Order should have behaviours.


This is the original article from Fowler: https://martinfowler.com/bliki/AnemicDomainModel.html

By searching for that term, you'll easily find lots of other takes on the matter.


> I'll reply here with a very quick example why the anemic domain model is superior in general, no matter if you do OOP or anything else.

I can search for it of course but results that aren't about OOP purism appear to be rare.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: