Módulo Back-end: Componentes e Injeção de Dependência 🧩
Este material aborda os conceitos fundamentais de componentização de software e injeção de dependência, pilares para o desenvolvimento de sistemas modernos, flexíveis e de fácil manutenção.
Sistema e Componentes 🏗️
Um sistema de software é, em sua essência, um conjunto de componentes que trabalham juntos para entregar uma funcionalidade. Para que o sistema seja robusto e escalável, esses componentes devem seguir dois princípios-chave:
- Coesão: Cada componente deve ter uma responsabilidade clara e única. Por exemplo, um componente calcula impostos, outro gerencia o acesso a dados de usuários, e nada mais.
- Baixo Acoplamento: Os componentes devem ser o mais independentes possível uns dos outros.
Adoar essa abordagem nos traz os seguintes objetivos e benefícios:
- Flexibilidade: Sistemas flexíveis se adaptam mais facilmente a novas regras de negócio.
- Manutenção Facilitada: É muito mais simples atualizar ou substituir um componente isolado do que modificar um sistema monolítico e fortemente acoplado.
- Reaproveitamento: Componentes bem definidos, como um serviço de cálculo de frete, podem ser reutilizados em diferentes projetos.
Exemplo Didático 🧑🏫
Vamos trabalhar com um problema prático para ilustrar esses conceitos.
O Problema: Criar um programa que leia os dados de um funcionário (nome e salário bruto). Em seguida, o programa deve calcular e exibir o salário líquido, aplicando os descontos de imposto e previdência.
Regras de Negócio:
- Imposto: 20% sobre o salário bruto.
- Previdência: 10% sobre o salário bruto.
Exemplo com Novos Dados:
- Nome: João Silva
- Salário Bruto: R$ 6000,00
- Cálculo:
6000.00 - (6000.00 * 0.20) - (6000.00 * 0.10) = 6000.00 - 1200.00 - 600.00 - Salário Líquido Esperado: R$ 4200,00
Solução “Rápida” 💨
Uma primeira abordagem, focada apenas em resolver o problema rapidamente, poderia ser um único bloco de código no método main.
package aplicacao;
import java.util.Locale;
import java.util.Scanner;
public class Programa {
public static void main(String[] args) {
Locale.setDefault(Locale.US);
Scanner sc = new Scanner(System.in);
System.out.print("Nome: ");
String nome = sc.nextLine();
System.out.print("Salário bruto: ");
double salarioBruto = sc.nextDouble();
// Cálculo "mágico" com forte acoplamento
double salarioLiquido = salarioBruto * 0.7; // 100% - 20% - 10% = 70%
System.out.printf("Salário líquido = %.2f%n", salarioLiquido);
sc.close();
}
}Essa solução funciona, mas é problemática. E se a alíquota do imposto mudar? Teríamos que encontrar e alterar o “número mágico” 0.7 diretamente no código, quebrando a coesão e dificultando a manutenção.
Solução Pensando em Componentes 🧩
Uma abordagem mais profissional é separar as responsabilidades em componentes.
Modelo de Domínio (Entidade): Primeiro, modelamos os dados do funcionário.
Componentes (Serviços):
Depois, criamos componentes para cada responsabilidade de negócio. O ServicoDeSalario usará os outros dois serviços para realizar seu trabalho.
Dica: Repare que temos dois tipos de objetos no sistema: componentes (como
ServicoDeImposto), que contêm lógica e regras, e objetos de dados (comoFuncionario), que apenas carregam informações.
Inversão de Controle (IoC) 🔄
A Inversão de Controle (do inglês, Inversion of Control) é um princípio de design que prega que um componente não deve criar ou gerenciar suas próprias dependências.
Analogia do Carro: Pense no motor de um carro. Ele depende da bateria para funcionar, mas o suporte da bateria não fica dentro do motor. Por quê? Para que você possa trocar a bateria sem precisar abrir e desmontar o motor inteiro. O controle sobre qual bateria usar é “invertido” para fora do motor.
No nosso exemplo, o ServicoDeSalario depende do ServicoDeImposto e do ServicoDePrevidencia. Se o ServicoDeSalario instanciar esses serviços diretamente, ele fica fortemente acoplado a eles.
Forma Errada (Alto Acoplamento):
Isto está errado, pois o ServicoDeSalario está controlando diretamente suas dependências.
Injeção de Dependência (DI) 💉
A Injeção de Dependência (do inglês, Dependency Injection) é a técnica que usamos para aplicar a Inversão de Controle. Em vez de o componente criar suas dependências, elas são “injetadas” de fora.
Essa injeção pode ser feita de algumas formas:
- Via Construtor (Recomendado): As dependências são passadas como parâmetros no construtor do objeto.
- Via Método Set: Métodos
setsão usados para fornecer a dependência. - Via Container (Framework): Um framework gerencia todo o processo automaticamente.
Exemplo com Injeção de Dependência via Construtor:
public class ServicoDeSalario {
// Dependências não são mais instanciadas aqui
private ServicoDeImposto servicoDeImposto;
private ServicoDePrevidencia servicoDePrevidencia;
// As dependências são "injetadas" pelo construtor
public ServicoDeSalario(ServicoDeImposto servicoDeImposto, ServicoDePrevidencia servicoDePrevidencia) {
this.servicoDeImposto = servicoDeImposto;
this.servicoDePrevidencia = servicoDePrevidencia;
}
public double calcularSalarioLiquido(Funcionario funcionario) {
double imposto = servicoDeImposto.tax(funcionario.getSalarioBruto());
double previdencia = servicoDePrevidencia.discount(funcionario.getSalarioBruto());
return funcionario.getSalarioBruto() - imposto - previdencia;
}
}Agora, o ServicoDeSalario é desacoplado e muito mais fácil de testar e manter.
Frameworks 🛠️
Um framework é uma estrutura de trabalho que oferece uma infraestrutura completa para desenvolver sistemas de forma mais produtiva e organizada. Em vez de você chamar o código do framework, é o framework que chama o seu código (isso é Inversão de Controle!).
Frameworks modernos, como o Spring para Java, gerenciam automaticamente:
- Injeção de dependências
- Ciclo de vida e escopo dos componentes
- Configurações
- Transações com banco de dados, segurança, integrações e muito mais.
Usando Injeção de Dependência com Frameworks ⚙️
Para usar a injeção de dependência em um framework como o Spring, o processo geralmente se resume a dois passos simples:
- Registrar os Componentes: Você “avisa” ao framework quais classes são componentes. No Spring, isso é feito com anotações como
@Component,@Serviceou@Repository. - Indicar as Dependências: Você declara onde um componente precisa de outro. No Spring, a anotação
@Autowiredé usada para isso (geralmente no construtor da classe).
Depois disso, o framework cuida de todo o trabalho pesado:
- Instanciar os componentes na ordem correta.
- Resolver e injetar as dependências automaticamente.
- Reaproveitar componentes, gerenciando seu ciclo de vida.
Essa abordagem permite que o desenvolvedor foque no que realmente importa: as regras de negócio da aplicação.