Where to put the behaviour of DTOs ? Object vs Data structure clean code

behaviordatadtoobject

Similar question was posted here https://stackoverflow.com/questions/21064164/clean-code-how-to-design-this-class

I still don't find an answer though, I'm confused!

I read the book "clean code" too.He is saying in some part you shouldn't mix data structure/Object, whether data structure with no behaviour or an object with behaviour.

In my application we have Data tranfer objects which carry data from external services .These DTO have just data accessors and mutators. So I was considering them as Data structure type.

However Robert Martin is saying in his book that client.isMarried() is better than isMarried(client) I found this logical as isMarried function use attributes only from client class.. it is cleaner.

In many areas in my application we need some behaviour on a certain DTOs I'm confused where to put this behaviour.
We have made Utils classes that has business logic like

ClientUtils { 

   boolean isMarried(Client client) { ...} 
   String getCompleteName(Client client) { ...} 

}

Should this go to the service layer ? even if these methods does not manipulate any thing else other than the input object It does not interact with another layer (DAL, services .. )

Best Answer

DTOs are not part of the domain model, and do not have any behaviour. DTOs represent the data structures that are transferred across your system boundary, e.g. data that is serialized to be sent across a network. DTOs are also great for representing incoming data that is possibly inconsistent, before it is validated and translated to your domain model.

Within the domain model, the objects representing the concepts within the problem domain do typically contain associated business logic. The interesting part isn't behaviour that queries the state of the model, but methods that change the model, beyond simple setters. This can lead to a very elegant mapping of the problem domain into your code because the model basically manages itself.

Sometimes we don't want to do that, and keep most behaviour out of the immediate model. This is called an anemic domain model, and is a more procedural than object-oriented design technique. As such, it is considered to be an anti-pattern by some. However, this works very well if there is a lot of business logic that doesn't clearly belong to a single domain model object. Instead, we can structure the business logic by use case. Each use case then manipulates the domain model. The drawback is that it becomes more difficult to keep the model consistent.

These two approaches are not exclusive, but are a sliding scale. At one end, all behaviour is directly in the model, at the other the domain model consists of dumb records. In the middle, the domain model contains generic operations that ensure consistency, but specialized logic lives outside in services which orchestrate changes to the model.

In your case, an isMarried() accessor has nothing to do in a DTO. A DTO can just expose its data. Within your domain model, it seems that such a method clearly belongs to some kind of Person class. If you are introducing Util classes that are named after another class, that is a clear warning sign that your design might be off: in many cases that behaviour should be part of the original class or else should be modelled separately as it's not part of the modelled problem domain.

However, I think that an isMarried() accessor is a weak example of this because it's basically just a getter, and a getter is basically just fancy syntax for a field. As a different example, let's consider a web application that allows students to enrol in lectures. If this is directly part of the domain model, we would see method calls like student.enrolIn(lecture) or lecture.enrol(student) in our code. But sometimes enrolment isn't simple and has complex business rules, e.g. how limited seats are given to the applying students. If enrolling is a whole process of its own, we might want to model that process as a separate object: lectureEnrolmentProcess.run(lecture, student). Coming back to your isMarried() behaviour: since this behaviour likely isn't a complex process with its own concerns, it shouldn't be kept separate.

So I think there should be a clear bias to put most behaviour directly into the model, but we shouldn't be afraid to extract any behaviour that has an entirely different concern than the concept being modelled.

Related Topic