Injeção de serviços com Spring

Alex Santos
TOTVS Developers
Published in
3 min readAug 22, 2024

Atualmente, falar de Java é também falar de Spring. Pouco se usa a linguagem sem nenhum framework atrelado, e o ecossistema Spring é gigantesco.

Desenvolver orientado a Interfaces, nos possibilita deixar que o Spring gerencie a injeção dos serviços pertinentes, e assim também seguimos o conceito do “The Open-Closed Principle”, que é estender o comportamento de uma classe sem precisar saber a sua implementação. No nosso exemplo, iremos fazer o Spring injetar uma lista de serviços que implementam uma interface:

Photo by Michiel Leunens on Unsplash

Em alguns casos, há a necessidade em criarmos vários serviços do mesmo contrato, e com base nesse cenário, iremos simular o envio de mensagem por alguns meios de comunicações, podendo ser via WhatsApp, E-mail e ser implementado futuramente o envio por SMS.
A modelagem é bastante simples: iremos ter um serviço responsável por invocar os serviços de envio, que se chama SenderMessagesService, a interface demarcadora chamada SenderService, e as suas duas implementações iniciais que é a EmailSenderService e a WhatsAppSenderService.

Diagrama de hierarquia de classes, demonstrando o SenderMessagesService invocando o SenderService, implementado pelo EmailSenderService e WhatsAppSenderService

Implementação:

Primeiro de tudo, iremos definir qual será o nosso contrato, e quais métodos os serviços precisarão implementar:

public interface SenderService
{
String send(
String message );
}

Logo após, iremos começar a fazer as devidas implementações, e nesse caso iremos apenas retornar uma simples mensagem para ver no console cada serviço sendo chamado:

@Service
public class EmailSenderService
implements
SenderService
{

@Override
public String send(
final String message )
{
return "Send EmailMessage: %s".formatted( message );
}

}
@Service
public class WhatsAppSenderService
implements
SenderService
{

@Override
public String send(
final String message )
{
return "Send WhatsAppMessage: %s".formatted( message );
}

}

O nosso serviço de envio de mensagens, será mais ou menos alguém que delega a responsabilidade, podendo fazer algumas validações, e caso sejam satisfatórias, podemos repassar para os serviços que irão de fato enviar as mensagens. Ele irá utilizar uma lista com N serviços, porém injetamos a interface e não unitariamente esses serviços. Isso irá possibilitar que a cada novo serviço implementado, seja automaticamente invocado, pois imagina como seria se a cada demanda nova, fosse necessário injetar mais uma implementação?

@Service
public class SenderMessagesService
{

private final List<SenderService> senderServices;

@Autowired
public SenderMessagesService(
final List<SenderService> senderServices )
{
this.senderServices = senderServices;
}

public List<String> send(
final String message )
{
return senderServices.stream().map( service -> service.send( message ) ).toList();
}

}

Por fim, criamos o Controller, sendo o ponto de entrada da nossa aplicação. Ao enviar uma requisição de envio de mensagem, ele irá chamar o serviço de envio de mensagem, e nesse exemplo, será chamado as duas implementações da interface. Ressaltando mais uma vez: o ganho disso, é que ao implementar um serviço novo, ele será chamado, sem a necessidade de ficar fazendo a invocação de serviço a serviço. Esse cenário funciona bem para esses casos de enviar mensagens por vários meios, criar dados padrões de várias entidades, avisar algumas mudanças, e por aí vai.

@RestController
@RequestMapping( "send-message" )
public class SenderMessageController
{
@Autowired
private SenderMessagesService senderMessagesService;

@PostMapping
public List<String> sendMessage(
@RequestBody final String message )
{
return senderMessagesService.send( message );
}

}

Existe também a possibilidade de usar um @Autowired direto na lista, porém para fazer os testes é um pouco mais difícil. A alternativa é receber no construtor do serviço a lista do tipo da interface, e dar um @Autowired direto no construtor, com isso, conseguimos instanciar o nosso serviço passando uma lista de Mocks, que são os dublês que iremos testar.

@ExtendWith( MockitoExtension.class )
class SenderMessagesServiceTest
{

private SenderMessagesService subject;
@Mock
private SenderService whatsAppSenderService;
@Mock
private SenderService emailSenderService;

@Test
void shouldCallSenderServices()
{
subject = new SenderMessagesService( List.of( whatsAppSenderService, emailSenderService ) );

final String message = "Message";
subject.send( message );

verify( whatsAppSenderService ).send( message );
verify( emailSenderService ).send( message );
}

}

A consideração é que não usaremos essa abordagem para todos os casos, pois precisamos tomar cuidado com o conceito de que “Para quem só sabe usar martelo, todo problema é um prego”, mas saber disso irá nos auxiliar para utilizar quando for necessário.

--

--

Alex Santos
TOTVS Developers

Um amante de música e tecnologia, apaixonado por desenvolver soluções.