The class PessoaMapperUtil
does not represent a concept of object-oriented programming!
Starting from a MVC model, the class Pessoa
is your class responsible for shaping the business rules. That is, it is in the layer Model.
The class PessoaResponse
would be something that carries data from the model outwards, towards the vision layer, and therefore is something that is in the layer of the Ccontroller together with the PessoaService
.
The class PessoaMapperUtil
is intended to take something from the model layer (Pessoa
) and manufacture a control layer object (PessoaResponse
).
Since the model should not know the controller (only the opposite is true), then the class Pessoa
must not reference PessoaResponse
or PessoaMapperUtil
. So far so good.
However, since PessoaResponsse
and PessoaMapperUtil
are in the same layer and in the same module/JAR/package/whatever-thing, so we started to wonder if they should actually be separated.
In general, classes that have names ending in Manager
, Util
, Handler
, Helper
do not represent real concepts of object-oriented programming, being often:
A case of static methods piled up (does not seem to be your case).
An object that is short-lived and constructed only for a method to be called, separating some task into two steps (the construction and invocation of the method itself) without many reasons for such.
Something else with serious problems of cohesion.
Your class PessoaMapperUtil
seems to be the second case. You even instantiates it, but it’s just to call the method toResponse()
right away. In that case, the best way would be to take a step back and put a static method:
public static PessoaResponse criar(Pessoa p) {
// ...
}
What is the best place to put this method? Well, it is something that is on the controller layer and produces an instance of PessoaResponse
that is already in that layer, so this should be a static (or a constructor) method of PessoaResponse
, and not a class apart.
Returning to class PessoaService
, Let’s see what she does:
public PessoaResponse converter(Pessoa pessoa, Long valor1, Long valor2) {
// 1. Cria um objeto PessoaResponse.
PessoaResponse pessoaResponse = new PessoaMapperUtil(pessoa).toResponse();
Long resultado = valor1 + valor2;
// 2. O objeto PessoaResponse foi criado incompleto!
// Portanto, conserta ele chamando setters!
pessoaResponse.setResultado(resultado);
// 3. Agora sim, o PessoaResponse está completo e pode ser retornado.
return pessoaResponse;
}
That is, the PessoaMapperUtil(pessoa).toResponse()
does not create the PessoaResponse
ready and this has to be finished from the outside. Now, the responsibility to create the PessoaResponse
correct and completely should be in class PessoaResponse
(or even that it was in PessoaMapperUtil
), and therefore should not be the responsibility of PessoaService
finish this unfinished service (see more about this in that other answer). The responsibility of PessoaService
should only be to expose the functionality of the application to Spring and nothing more.
I mean, the ideal would be for you to have this in class PessoaResponse
:
public static PessoaResponse criar(Pessoa p, Long valor1, Long valor2) {
Long resultado = valor1 + valor2;
// ...
}
With that your class PessoaService
gets like this:
@Service
class PessoaService {
public PessoaResponse converter(Pessoa pessoa, Long valor1, Long valor2) {
return PessoaResponse.criar(pessoa, valor1, valor2);
}
}
Okay, but then, how do we mock it? In that case we fall into what I explained in that answer here, it should not make sense to mock static method. In this case, if we wanted to mock it, the class itself PessoaService
tested wouldn’t make any sense. She would be nothing more than something that would return in a stupid and blind way, any junk that the mock returned without looking at anything else and nor do any criticism or verification. That is, by mocking the method it calls, we would produce an innocuous test. The reason is that what has to be tested here is the method PessoaResponse.criar(Pessoa, Long, Long)
, to make sure that the PessoaResponse
produced is correct. The class PessoaService
is reduced to just a very verbose way of telling Spring that the method criar(Pessoa, Long, Long)
class PessoaResponse
is a feature exposed as a service.
And to test the method criar(Pessoa, Long, Long)
class PessoaResponse
? Simple, create an object Pessoa
(here can be a mock without problems), pass it to that method and do a lot of assertXXX
s in the returned object. Note that testing whether the PessoaResponse
returned is right, it is much easier if it is an immutable class - without setters, with all fields final
and without any other things that can modify an instance after the constructor has finished executing, even more so considering the purpose for which the class PessoaResponse
exists is to transport data to other parts of the application that must be decoupled from the origin of the same. Adopting this practice causes the problem of creating incomplete objects to be solved and prevents it from being reintroduced through carelessness.
Therefore, to test the method converter(Pessoa, Long, Long)
class PessoaService
just do the same tests that were done with criar(Pessoa, Long, Long)
class PessoaResponse
, after all the two must have exactly the same behavior without taking or putting anything.
Even better would you be able to convince Spring to use the method criar(Pessoa, Long, Long)
class PessoaResponse
as a service by means of some annotations without needing the class PessoaService
, but I think that this is something that you can’t convince Spring to swallow (at least not easily). With other frameworks, you may or may not succeed.
I know this says little about what happens when class complexity Pessoa
begin to get too big, but that would be something that would be in the responsibility of PessoaResponse
map. However, in that case, you would have to provide more data about your application.
Well, there’s still the case that you really don’t want to put the method at all criar(Pessoa, Long, Long)
in class PessoaResponse
(perhaps because the two should not know each other at all). So yes, in this case, you would have to put this method in a class by itself, but this tends to impair the cohesion and encapsulation of PessoaResponse
.
Related question (is not duplicate).
– Victor Stafusa
Another related question (is also not duplicate).
– Victor Stafusa
It’s hard to answer that without knowing for sure what’s inside
PessoaResponse
andPessoaMapperUtil
. In the link from my previous comment, I talk a little bit about anemic model, and I think that’s your case here.– Victor Stafusa
Why do you downvote?
– Dherik
I voted positive. Receiving random downvotes without justification is always very boring, but unfortunately there is nothing to do when it happens.
– Victor Stafusa
@Victorstafusa, thank you! I usually do the same, rs
– Dherik