When should a utility class become an injectable dependency?

Asked

Viewed 253 times

2

I often write small utility classes to solve some minor problem in my code. For example (I will use Spring for DI):

@Service
class PessoaService {

    public PessoaResponse converter(Pessoa pessoa, Long valor1, Long valor2) {
        PessoaResponse pessoaResponse = new PessoaMapperUtil(pessoa).toResponse();
        Long resultado = valor1 + valor2;
        pessoaResponse.setResultado(resultado);
        return pessoaResponse;
    }

}

Man PessoaMapperUtil is very simple, it just converts the data from the Pessoa for a new class PessoaResponse, using an internal mapper.

However, let’s say that the PessoaMapperUtil begin to become more complex because the Pessoa begins to get bigger, disrupting my unit testing method converter, as more and more fields need to be included in the Pessoa so that the PessoaMapperUtil work.

Getting more complex, I’ll need to identify the moment when it’s worth doing the PessoaMapperUtil become a dependency and be "mockable", getting more or less like this:

@Service
class PessoaService {

    private final PessoaMapperUtil mapper;

    @AutoWired
    PessoaService(PessoaMapperUtil mapper) { this.mapper = mapper; }

    public PessoaResponse converter(Pessoa pessoa, Long valor1, Long valor2) {
        PessoaResponse pessoaResponse = mapper.toResponse(pessoa);
        Long resultado = valor1 + valor2;
        pessoaResponse.setResultado(resultado);
        return pessoaResponse;
    }
}

Causing PessoaMapperUtil turn a component (@Component), it becomes simpler to mock it and from this I would only worry about the rest of the code.

Anyway, what would be the symptom in the code in which a utility should become a dependency? Actually I do this when he starts messing up the tests... That’s right?

  • 1

    Related question (is not duplicate).

  • 1

    Another related question (is also not duplicate).

  • 2

    It’s hard to answer that without knowing for sure what’s inside PessoaResponse and PessoaMapperUtil. In the link from my previous comment, I talk a little bit about anemic model, and I think that’s your case here.

  • 1

    Why do you downvote?

  • 2

    I voted positive. Receiving random downvotes without justification is always very boring, but unfortunately there is nothing to do when it happens.

  • @Victorstafusa, thank you! I usually do the same, rs

Show 1 more comment

1 answer

5


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 assertXXXs 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.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.