This content originally appeared on DEV Community and was authored by Uiratan Cavalcante
Introdução
Imagine que vocĂȘ estĂĄ desenvolvendo um sistema de streaming de mĂșsica. Sempre que um usuĂĄrio assina o serviço, queremos realizar vĂĄrias açÔes automaticamente, como enviar um e-mail de boas-vindas, conceder um perĂodo de teste gratuito e atualizar o status da assinatura.
Em vez de acoplar essas açÔes diretamente no fluxo de cadastro, podemos utilizar eventos para manter o cĂłdigo mais organizado, flexĂvel e desacoplado. Vamos ver como o Spring pode nos ajudar a fazer isso automaticamente!
O Problema de NegĂłcio
Ao cadastrar um novo assinante, precisamos executar diversas açÔes:
- Enviar um e-mail de boas-vindas
- Conceder um perĂodo de teste grĂĄtis
- Atualizar o status do usuĂĄrio
Uma abordagem ingĂȘnua seria simplesmente chamar esses mĂ©todos diretamente dentro do fluxo de cadastro, mas isso criaria forte acoplamento entre as açÔes. Queremos um sistema mais flexĂvel, onde possamos adicionar ou remover eventos sem modificar a lĂłgica principal.
Implementação com Eventos
Utilizaremos o Spring para gerenciar automaticamente os eventos e suas respectivas açÔes.
1⃣ Definição da Classe Assinatura
Criamos a classe Assinatura
que representa um usuĂĄrio assinante:
public class Assinatura {
private String usuario;
public Assinatura(String usuario) {
this.usuario = usuario;
}
public String getUsuario() {
return usuario;
}
}
2⃣ Definição da Interface do Evento
Primeiro, criamos uma interface que define o contrato para todos os eventos que devem ser executados apĂłs um novo cadastro:
public interface EventoNovaAssinatura {
void processa(Assinatura assinatura);
}
Essa interface serĂĄ implementada por todas as classes que precisam reagir a uma nova assinatura.
3⃣ Implementação dos Eventos
Agora, implementamos as açÔes especĂficas que devem ocorrer ao criar uma nova assinatura.
Enviar e-mail de boas-vindas
@Service
public class EmailBoasVindas implements EventoNovaAssinatura {
@Override
public void processa(Assinatura assinatura) {
System.out.println("[EMAIL] Bem-vindo, " + assinatura.getUsuario() + "! Sua assinatura foi ativada.");
}
}
Conceder teste gratuito
@Service
public class TesteGratis implements EventoNovaAssinatura {
@Override
public void processa(Assinatura assinatura) {
System.out.println("[TESTE GRĂTIS] UsuĂĄrio " + assinatura.getUsuario() + " recebeu um perĂodo de teste de 7 dias!");
}
}
Atualizar status da assinatura
@Service
public class AtualizaStatus implements EventoNovaAssinatura {
@Override
public void processa(Assinatura assinatura) {
System.out.println("[STATUS] Assinatura de " + assinatura.getUsuario() + " ativada com sucesso!");
}
}
4⃣ Classe que Orquestra os Eventos
Criamos um serviço que gerencia a execução dos eventos automaticamente:
@Service
public class EventosNovaAssinatura {
@Autowired
private Set<EventoNovaAssinatura> eventos;
public void processa(Assinatura assinatura) {
eventos.forEach(evento -> evento.processa(assinatura));
}
}
Aqui, o Spring automaticamente injeta todos os beans que implementam EventoNovaAssinatura
no Set<EventoNovaAssinatura> eventos
. Isso significa que sempre que adicionarmos uma nova implementação, ela serå registrada sem precisar modificar nada no código principal!
Como o Spring Injeta Automaticamente os Eventos?
O Spring escaneia automaticamente todas as classes anotadas com @Service
que implementam a interface EventoNovaAssinatura
. Ele injeta todas essas instĂąncias no Set<EventoNovaAssinatura> eventos
, garantindo que todos os eventos sejam processados corretamente sem precisar de configuração manual.
Isso segue o princĂpio da inversĂŁo de controle e permite adicionar novos eventos sem modificar o cĂłdigo principal, tornando o sistema mais modular e fĂĄcil de manter.
OpçÔes de Expansão com Eventos
O uso de eventos nos permite adicionar novas açÔes de forma simples. Algumas possĂveis implementaçÔes que poderiam ser adicionadas ao nosso fluxo:
Integração com mensagerias: Publicar eventos no RabbitMQ ou Kafka para processamentos assĂncronos.
NotificaçÔes push: Enviar notificaçÔes para o celular do usuårio.
Cobrança automåtica: Integrar com um sistema de pagamentos para ativar a cobrança recorrente.
Registro de métricas: Enviar dados para um sistema de monitoramento, como o Prometheus.
CustomizaçÔes avançadas: Criar um workflow que permita aos administradores ativar ou desativar eventos dinamicamente.
PadrĂ”es e PrincĂpios
PadrĂŁo Observer
No nosso exemplo, utilizamos o PadrĂŁo Observer, onde a classe EventosNovaAssinatura
age como um sujeito que notifica um conjunto de observadores (EventoAssinaturaConfirmada
). Esse padrão permite desacoplar a lógica de notificação do fluxo principal, garantindo que novas açÔes possam ser adicionadas sem modificar código existente.
Isso se alinha ao PrincĂpio Aberto-Fechado (OCP – Open/Closed Principle), pois podemos adicionar novos eventos sem modificar a implementação de EventosNovaAssinatura
. Basta criar uma nova classe que implemente EventoAssinaturaConfirmada
, e o Spring automaticamente a registrarĂĄ.
InversĂŁo de Controle e Injeção de DependĂȘncias
O Spring faz o gerenciamento automĂĄtico das dependĂȘncias ao injetar dinamicamente todas as implementaçÔes de EventoAssinaturaConfirmada
dentro do Set<EventoAssinaturaConfirmada> eventoAssinaturaConfirmada
. Isso elimina a necessidade de instanciar manualmente esses objetos, facilitando a escalabilidade e testabilidade do cĂłdigo.
Essa abordagem segue o PrincĂpio da InversĂŁo de DependĂȘncia (DIP – Dependency Inversion Principle), pois as classes de alto nĂvel (EventosNovaAssinatura
) não dependem diretamente das implementaçÔes concretas dos eventos, mas sim da abstração (EventoAssinaturaConfirmada
).
PrincĂpio da Responsabilidade Ănica (SRP – Single Responsibility Principle)
Cada classe do nosso cĂłdigo tem uma Ășnica responsabilidade:
-
EventosNovaAssinatura
: responsĂĄvel por processar assinaturas e notificar os eventos. -
NotificacaoEmail
: envia um e-mail confirmando a assinatura. -
GerarRelatorio
: atualiza o sistema com um novo relatĂłrio. -
Assinatura
: representa uma assinatura com status de confirmação.
Esse design modular melhora a organização do cĂłdigo e facilita a manutenção, pois cada classe foca em uma Ășnica tarefa.
ConclusĂŁo
Com essa abordagem, conseguimos um sistema flexĂvel, modular e extensĂvel, permitindo adicionar novas notificaçÔes sem modificar cĂłdigo existente. O uso do PadrĂŁo Observer, combinado com a injeção de dependĂȘncias do Spring, nos ajuda a manter um cĂłdigo alinhado aos princĂpios do SOLID, melhorando a testabilidade, manutenção e escalabilidade da aplicação.
Se gostou desse conteĂșdo, compartilhe com outros desenvolvedores e ajude a disseminar boas prĂĄticas no desenvolvimento Java!
This content originally appeared on DEV Community and was authored by Uiratan Cavalcante