Como Isolar Regras de Negócio em Projetos Java com Clean Architecture — Guia Prático com Exemplos Reais

Se você está desenvolvendo sistemas em Java e se pergunta:
“Como lidar com regras de negócio que mudam o tempo todo?”
Então este artigo é para você.

Neste guia, vamos explorar como projetar um sistema Java modular e flexível, usando Clean Architecture, DDD leve e boas práticas para isolar regras de negócio da infraestrutura.

Você vai aprender:

  • Por que as regras de negócio mudam tanto
  • Como isolar essas regras do resto do sistema
  • Como aplicar isso com uma arquitetura limpa e escalável
  • Exemplos reais em Java com Use Cases, DTOs e controle de transações

🎯 Por Que Isolar as Regras de Negócio?

No desenvolvimento de software, as regras de negócio são o que mais muda ao longo do tempo.

Por isso, deixar essas regras espalhadas entre controllers, repositórios, serviços e bancos de dados cria uma bagunça difícil de manter e testar.

A solução? Separar bem as responsabilidades.


🧠 Princípios Fundamentais

1. SRP — Single Responsibility Principle

Cada classe deve ter apenas um motivo para mudar.
Ou seja: controle de acesso, persistência e lógica de cálculo devem viver em lugares diferentes.


2. Arquitetura em Camadas / Hexagonal / Clean Architecture

A lógica de negócio deve ficar no centro (domínio), desacoplada de:

  • Banco de dados
  • Frameworks
  • APIs externas

3. Inversão de Dependência

As regras de negócio definem interfaces.
As implementações concretas ficam nas camadas externas, e são injetadas.


📁 Estrutura de Projeto Sugerida

src/
├── domain/
│ ├── entities/ → Objetos do negócio
│ ├── services/ → Regras de negócio
│ └── ports/ → Interfaces (gateways e repositórios)
├── application/
│ ├── usecases/ → Orquestra os serviços
│ └── dtos/ → Inputs/Outputs
├── infrastructure/
│ ├── gateways/ → Implementações reais
│ ├── repositories/
│ └── config/
└── api/
└── controllers/

🧾 Exemplo Prático: Criando Pedido e Emitindo Nota Fiscal

1. Entidade de Domínio

public class Order {
private String id;
private List<String> items;
private boolean isPaid;

public Order(List<String> items) {
this.id = UUID.randomUUID().toString();
this.items = items;
this.isPaid = false;
}

public void markAsPaid() {
if (isPaid) throw new IllegalStateException("Pedido já pago");
this.isPaid = true;
}

public List<String> getItems() { return items; }
}

2. Serviço de Domínio

public class OrderService {
public void validate(Order order) {
if (order.getItems().isEmpty()) {
throw new IllegalArgumentException("Pedido não pode estar vazio");
}
}
}

3. Interface para o Repositório

public interface OrderRepository {
void save(Order order);
Optional<Order> findById(String id);
}

4. Use Case: Criar Pedido

public class CreateOrderUseCase {

private final OrderRepository orderRepository;
private final OrderService orderService;

public CreateOrderUseCase(OrderRepository repo, OrderService service) {
this.orderRepository = repo;
this.orderService = service;
}

public String execute(List<String> items) {
Order order = new Order(items);
orderService.validate(order);
orderRepository.save(order);
return order.getId();
}
}

💳 Use Case Avançado: Emitir Nota Fiscal com Transação

@Service
public class EmitirNotaFiscalUseCase {

private final NFeService nfeService;
private final EstoqueGateway estoqueGateway;
private final NFeRepository nfeRepository;
private final SefazGateway sefazGateway;

@Transactional
public void execute(Pedido pedido) {
NotaFiscal nf = nfeService.gerarNota(pedido);
estoqueGateway.baixarEstoque(pedido.getItens());
nfeRepository.salvar(nf);
sefazGateway.enviarXML(nf); // se falhar, rollback automático
}
}

⚠️ O @Transactional garante rollback no banco se algo falhar.


📦 Interfaces (Ports)

public interface EstoqueGateway {
void baixarEstoque(List<ItemPedido> itens);
}

public interface NFeRepository {
void salvar(NotaFiscal notaFiscal);
}

public interface SefazGateway {
void enviarXML(NotaFiscal notaFiscal);
}

📐 Quando o Estoque está em outro Microserviço?

Você não pode usar transação distribuída confiável entre microserviços. O que fazer?

✅ Use Padrões como:

  • SAGA Coreografada: serviços publicam eventos e reagem
  • Outbox Pattern: salva evento no banco e publica de forma segura
  • Fila com retry e dead letter

📦 Interface Genérica para Use Cases

public interface UseCase<I, O> {
O execute(I input);
}

DTO de Entrada e Saída

public class EmitirNotaFiscalInput {
private Pedido pedido;
private String usuarioResponsavel;
}

public class EmitirNotaFiscalOutput {
private String numeroNotaFiscal;
private boolean sucesso;
}

🧠 Cálculo de Impostos como Regra de Negócio

Refatorado com Polimorfismo:

public interface RegraICMS {
double calcular(double valorBase, ConfiguracaoFiscal config, String origem, String destino);
}

public class ICMS00 implements RegraICMS {
public double calcular(...) { /* lógica */ }
}

Classe Tributação:

public class Tributacao {
public double calcularICMS(...) {
RegraICMS regra = RegraICMSFactory.getRegra(cst);
return regra.calcular(...);
}
}

Aplicando o Princípio do Aberto/Fechado (OCP), sua lógica se torna testável, extensível e robusta.


✅ Conclusão

Separar regras de negócio da infraestrutura é essencial para a saúde do seu projeto Java. Com Clean Architecture e a estrutura de Use Cases genéricos:

  • Seu sistema fica flexível para mudanças
  • Os testes ficam simples e isolados
  • As integrações são desacopladas
  • O código expressa com clareza o domínio

Posts Similares