DTO in OOP world
I suppose that everyone knows what the DTO is.Object oriented fans hate this pattern because it
literally converts classes into plain old C structures.There is also the discussion in stackoverflow
about this topic.I personally don't like DTO either because they are not reusable at all.Let's say I
have a User class which is managed by persistence framework.Sometimes we want to return a user with
the list of associated orders as json. Sometimes we are interested in user's data only.Sometimes the
validation logic depends on the specific rules. For each case we will add new and new logic into
User and as a result
this class becomes completely unmaintainable.But what if we can create a separate DTO for each new
requirement. Then all of them will have a lot of similar methods,logic...
One solution for this issue was proposed in this
blog post and
I want to show you how I applied it in the REST API implementation.
Example
Let me show you an example from my plain REST API implementation called widrest but before this, you have to understand the business logic. The main domain of the business is called Widget. Widget has some properties namely:
- Y coordinate
- X coordinate
- Z index
- Width
- Height
- Database id
Simple enough to understand, and now we have the following requirements:
- User can't specify id during POST and PUT requests
- Z-index is optional when widget is created and it's obligatory during update
Keep it small and simple
When I design classes , I prefer to make them as stupid as I can. The main reason is Single Responsibility.To be honest I have never googled what is the real definition of this sentence is until a while ago and here is the definition by Robert C. Martin from wikipedia:
A class should have only one reason to change.
Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to be changed
With this quote in mind let's write a simple solution to the problem above
This class could be used in order to parse json request and to send response to a user.
- We have to keep in mind that
user can't specify id in request for widget that is why we should add
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
annotation above id field in Widget class. - z-index is optional if user wants to create widget and it's mandatory if user wants to update an existing widget.If we use Javax validation API then we have to add Validation groups to z-index field inside Widget.
The new version of Widget could have the following structure:
Widget had two reasons to be changed depending on new requirements for update and create.It doesn't follow single responsibility principle.
Possible solution
As a solution, I decided that Widget should be an interface and all validation logic should depend
on a specific implementation. Widget interface gives you an access
to
x,y,width,height,z
.
After this I created an envelop for the Widget called ImmutableWidget which just has a single constructor that takes all widget's arguments and delegate them to method calls(Notice that this class is not final but all methods are finals which means that class could be extended but non of the methods could be overridden,you will see the logic behind it a little bit later)
Now let's return to the requirements, create and update widget. Simply enough let's create two
different Widget
implementations namely
WidgetToCreate
and WidgetToUpdate
This is how WidgetToUpdate
looks like
As you may guess , WidgetToCreate
won't have @NotNull
annotation for
z-index.
Here comes the power of ImmutableWidget
. Two classes above just call the parent's
constructor
with custom validation logic for Jackson.And now, if requirements for update will change , then we
will change only one class that won't effect create logic.
What about id?
What about id field? This field could be returned in a response body , but user can't specify this
field in request. Again, let's create another interface called
DbWidget
that will extend Widget
interface because both of them have the
same
parameters and let's add a new method to DbWidget
called getId().
Now we could accept some implementation of Widget in controller and return DbWidget implementation as a response.
Conclusion
Right now, I see a few problems with this approach but the biggest one is the problem with the amount of classes.For every unique action we have to create a new implementation of the widget.However each implementation would be super small (if it extends ImmutableWidget).At the same time , with this approach, it would be much easier to change business logic because we can be sure that changes in one single class won't effect the whole system but only a small part of it