8

I was reading about the clean architecture, and I don't get this part:

Don't use your Entity objects as data structures to pass around in the outer layers. Make separate data model objects for that.

As long as I don't let the entities leak out through an API - that's why I create Presenters -, what's wrong with letting Entities leave the domain inner layer? Why create DTOs for that? Entities are the most stable thing in the clean architecture.

The only thing I should avoid (in the onion model) is dependencies pointing outward.. but the case I presented is pointing inward (API depending on the Entities).

Luís Soares
  • 271
  • 1
  • 7
  • 2
    "As long as I don't let the entities leak out through an API - that's why I create Presenters" - that's the same as preventing entities being passed in the outermost layers. I think that's what the quote is referring to. The layer just above has to refer to the entities, DTOs can't avoid that. How exactly you go about it all depends on how many layers you actually have, and on where your architectural boundaries are. – Filip Milovanović May 05 '20 at 01:53
  • The representer is part of the web handler.. just a utility I guess. so after that there's no more layers. But I understood your point. It's about how you structure the code. – Luís Soares May 05 '20 at 07:36

2 Answers2

5

I wrote the article you referred to, not because I am an expert in software architecture, but because teaching other people helps me to learn these concepts better myself. So take my answer below with a grain of salt.

A case for separating entities and DTOs

The entities model the essence of the problem that your app is trying to solve. They are the business rules and the business data. Even if you didn't have an app, the data and the rules would be the same. That makes these business rules very stable. They don't have to change every time you switch your database or decide to use a different REST API.

Now you could pass an entity all around your app. It even feels natural to do that because it's a convenient representation of the data that you want to use. The problem, though, is that you will start being tempted to change it. For example, wouldn't it be nice if my entity class had a toJson() method? Why? Because the REST API uses JSON. But then the API changes, and guess what? You have to change the toJson() method in your entity. So much for having a stable core.

That's where the Data Transfer Objects (DTOs) that you mentioned come in. They seem a little redundant at first, but the benefit is that you can change them any time based on the needs of the infrastructure. The entities themselves, on the other hand, still stay the same. You get to keep your stable core. The Use Cases know about the entities, but the entities don't know about anything (except maybe other entities).

A counter argument

That said, I heard a quote once that went something like this: "Don't try a more sophisticated solution until you feel the pain of using the simpler one." I think that applies here. Go ahead and pass your entities (or models or whatever you want to call them) around all throughout your app. Just keep this conversation in the back of your mind. When you start to feel the pain of having to change things that shouldn't need changing, consider separating your entities and DTOs.

In several medium sized projects recently I've actually skipped the entities and just used data model classes with methods like toJson(). I have to admit, though, that I'm starting to feel the pain. I've already changed that toJson() method a couple of times....

Suragch
  • 151
  • 5
3

It’s called the Principle of Least Knowledge or Law of Demeter.

The more things that directly know about an object the more things break when it changes. This is why it’s not good for an object to pass through layer after layer.

Demeter says to only talk to your friends. Not friends of friends. We isolate knowledge to enable change.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • Ok I understand that. But the inner layers (domain) are more stable and the outer (infrastructure) more volatile, by definiton. Would you say that's ok to start a project that you can improve later? – Luís Soares May 05 '20 at 07:25
  • 1
    @LuísSoares nothing becomes more permanent in software than a decision that's been spread around. Make decisions in one place. – candied_orange May 05 '20 at 10:45
  • Was this meant ironically? **DTOs break the Law of Demeter** almost **by design**. The Law essentially says you can not call any methods on objects you got from a getter. You do that a lot with DTOs. [Law of Demeter explained](https://javadevguy.wordpress.com/2017/05/14/the-genius-of-the-law-of-demeter/) – Robert Bräutigam May 06 '20 at 08:30
  • @RobertBräutigam not at all. A DTO should be used to cross one boundary. Not multiples. – candied_orange May 06 '20 at 08:38
  • @RobertBräutigam: DTOs do not break the Law of Demeter unless you choose to pass it across several layers - which is a decision completely unrelated to the concept of a DTO. There is nothing stopping you from giving each layer its own DTO, which does not violate LoD. Secondly, there are no significant methods to speak of in a DTO, as it is treated as **data** (hence the **D** in DTO), not a logical component which represents business logic or behavior. – Flater Apr 12 '22 at 09:22
  • @Flater So your argument is that DTOs are just data, they're the "struct" from C, they don't have proper behavior, they're barely even an object, if at all. I don't think this argument leads where you think it leads. – Robert Bräutigam Apr 12 '22 at 10:55
  • @RobertBräutigam Well yes, a DTO's inherent duty is to represent data that is to be transferred between two interactors. That's pretty much the definition. No, that is not the same as saying they're a struct. Yes, they usually contain no real behavior (other than possibly a trivial transformation). They are an object, just not one that's heavy on behavior. – Flater Apr 12 '22 at 12:53
  • @RobertBräutigam "the Law prohibits 'sending a message' to any already existing object that is held in instance variables of other classes, unless it is also held by our class or passed to us as method parameters." Which literally means you can't use collections in the observer pattern. Which is silly. It would also mean you can't use Java8 Streams or any iDSL. LoD isn't bad. Follow LoD when you aren't thinking architecturally and are just trying to send your message. It keeps you safe. – candied_orange Apr 12 '22 at 17:00
  • @RobertBräutigam But don't think, "well it's data. I'm not sending a msg". That's silly. Isolate knowledge. Even if LoD says nothing against it. A typical DTO is a hand crafted collection of 1 shallow record. The contents should be a friend. If they aren't you have no business poking around in this DTO. Being contained in something that is meant to be your container doesn't make you friendless. – candied_orange Apr 12 '22 at 17:01
  • @candied_orange All those things are actually allowed by LoD. Observer pattern, collections, streams and also DSLs, builders, etc. The only questionable one is `list.get(i)`. Those are actually getting an instance that already existed and is held by another but not us. All the other ones, especially in streams (`filter()`, `map()`, `reduce()`, etc.) are all good. – Robert Bräutigam Apr 13 '22 at 07:32
  • @RobertBräutigam yeah? Why? A strict interpretation of LoD doesn’t allow accessing any reference holders. Which is why there have to be exceptions. My argument is that a DTO doesn’t violate LoD any more than list does. But the spirit of LoD (friends. Not friends of friends) still doesn’t like DTO’s that are known by multiple layers. Known by only one would be ideal but a DTOs job is to cross a boundary. That means it’s known by two. The question here was if three was ok. It’s not. – candied_orange Apr 13 '22 at 12:05
  • LoD is pretty well defined, it's a whitelist of 5 things you can do. Streams calls for example fall under either rule 2 or 3. The difference between Lists and DTOs is that lists have a "domain", a problem to solve, therefore behavior. DTOs don't. This is a very important distinction in OO. – Robert Bräutigam Apr 17 '22 at 08:00
  • @RobertBräutigam really? You think 2 and 3 allow [this](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html): int sum=widgets.stream().filter(w -> w.getColor() == RED).mapToInt(w -> w.getWeight()).sum(); – candied_orange Apr 17 '22 at 14:08
  • Yes, that's completely fine. It involves rule #4 too, i.e. objects created during the call. Getters are problematic as always, though technically not violating LoD in this case. I assume it's not important to your question anyway, you're asking about streams in general I assume. – Robert Bräutigam Apr 17 '22 at 19:13