Capítulo 20: Projeto Corporativo: Microsserviço de Pagamentos com Auditoria JSON
Especialização em Backend com Java & Spring Boot • Spring Boot 3 & Java 21 LTS • Spring Data JPA, Security, Microsserviços e Cloud
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Projeto Corporativo: Microsserviço de Pagamentos com Auditoria JSON"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Fundamentos Técnicos de Projeto Corporativo: Microsserviço de Pagamentos com Auditoria JSON
O capítulo integrador. Este projeto não introduz um tópico isolado — ele sintetiza os capítulos 09 a 19 em um único microsserviço corporativo de pagamentos: modelagem de domínio rica e persistência (Spring Data JPA), segurança e autenticação (Spring Security/JWT), mensageria assíncrona para notificar outros serviços (Kafka), resiliência diante do gateway externo de pagamento (Resilience4j), testes automatizados de todas as camadas (MockMvc/Testcontainers) e observabilidade completa (Actuator/Micrometer). Um domínio de pagamentos é o cenário ideal para essa síntese porque exige rigor simultâneo em todas essas frentes — erro em qualquer uma delas tem custo financeiro e reputacional direto.
Modelagem do domínio: a máquina de estados da transação. Um pagamento não é um registro estático — é uma máquina de estados com transições controladas: PENDENTE → PROCESSANDO → APROVADO | RECUSADO, com ESTORNADO como transição adicional a partir de APROVADO. Modelar isso com um enum StatusTransacao e validar transições explicitamente no service (nunca permitir, por exemplo, que uma transação RECUSADA vá direto para APROVADO sem reprocessamento) é o que separa um domínio de pagamentos correto de um conjunto de campos soltos em uma tabela.
Idempotência: o requisito não-negociável de uma API de cobrança. Timeouts de rede são inevitáveis em sistemas distribuídos — e quando o cliente não recebe resposta a tempo, o comportamento natural (do usuário ou de uma lógica de retry automática) é reenviar a mesma requisição. Sem proteção, isso significa cobrar o cliente duas vezes pela mesma compra. O padrão de mercado é exigir um cabeçalho Idempotency-Key (UUID gerado pelo cliente, único por intenção de pagamento): o serviço verifica atomicamente (putIfAbsent em Redis/banco, nunca “verificar depois inserir” de forma não-atômica, que reabre a mesma corrida) se aquela chave já foi processada — se sim, retorna a resposta original armazenada em cache, sem reprocessar nada; se não, processa e grava a resposta associada à chave para futuras repetições.
O padrão Saga para transações distribuídas. Uma cobrança completa frequentemente envolve múltiplos serviços — reservar estoque, processar pagamento, emitir nota fiscal — e um banco de dados relacional não pode garantir uma transação ACID através de fronteiras de serviço. O padrão Saga Orquestrada resolve isso com um orquestrador central que emite comandos sequenciais a cada serviço e, se qualquer passo falhar (ex.: saldo insuficiente no passo de pagamento após o estoque já ter sido reservado), dispara transações compensatórias que revertem os passos anteriores já concluídos (liberar o estoque reservado) — trocando atomicidade forte por consistência eventual e reversibilidade controlada.
Trilha de auditoria imutável. Diferente de logs de aplicação (que podem ser rotacionados/descartados), um registro de auditoria financeira precisa ser append-only e verificável: cada evento de transição de estado da transação é gravado como um registro JSON estruturado com timestamp, sem permitir alteração posterior — funcionando como evidência para reconciliação contábil e investigação de disputas. Em cenários de conformidade mais rígidos, esse registro é complementado com assinatura digital/hash encadeado (semelhante a um blockchain simplificado), tornando qualquer adulteração posterior detectável.
Conformidade PCI-DSS: o que nunca deve tocar seu banco de dados. O padrão PCI-DSS proíbe expressamente armazenar dados sensíveis de autenticação de cartão (CVV) sob qualquer circunstância, e exige que o número completo do cartão (PAN) nunca seja persistido em texto claro — a prática correta é delegar a captura do cartão a um gateway/adquirente certificado (tokenização), armazenando apenas um token opaco e uma versão mascarada (**** **** **** 1234) para exibição. Um microsserviço de pagamentos bem projetado nunca vê o número completo do cartão — ele orquestra a cobrança via token, reduzindo drasticamente sua superfície de responsabilidade de compliance.
Fechando o ciclo: teste E2E do fluxo completo. O teste de aceitação final deste projeto integra tudo: uma requisição com Idempotency-Key chega, passa pela autenticação JWT, o CircuitBreaker protege a chamada ao gateway externo, a transação muda de estado de forma auditada, um evento é publicado no Kafka para os serviços consumidores (notificação, faturamento), e as métricas do Actuator refletem o resultado — validando, com um único cenário de teste, que a arquitetura estudada ao longo de toda a especialização funciona como um sistema coeso, não como capítulos isolados.
💻 2. Código de Demonstração Corporativo
package com.empresa.api.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public enum StatusTransacao { PENDENTE, PROCESSANDO, APROVADO, RECUSADO, ESTORNADO }
@Entity
public class TransacaoPagamento {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String idempotencyKey;
private Long pedidoId;
private BigDecimal valor;
@Enumerated(EnumType.STRING)
private StatusTransacao status = StatusTransacao.PENDENTE;
private LocalDateTime dataCriacao = LocalDateTime.now();
public void transicionarPara(StatusTransacao novoStatus) {
// Validação de máquina de estados: nenhuma transição arbitrária é permitida.
boolean transicaoValida = switch (status) {
case PENDENTE -> novoStatus == StatusTransacao.PROCESSANDO;
case PROCESSANDO -> novoStatus == StatusTransacao.APROVADO || novoStatus == StatusTransacao.RECUSADO;
case APROVADO -> novoStatus == StatusTransacao.ESTORNADO;
case RECUSADO, ESTORNADO -> false; // estados finais
};
if (!transicaoValida) {
throw new IllegalStateException("Transição inválida: " + status + " -> " + novoStatus);
}
this.status = novoStatus;
}
}
package com.empresa.api.service;
import com.empresa.api.model.StatusTransacao;
import com.empresa.api.model.TransacaoPagamento;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
@Service
public class PagamentoOrquestradorService {
private final IdempotencyService idempotencyService;
private final TransacaoPagamentoRepository transacaoRepository;
private final AuditPaymentService auditService;
private final FaturamentoEventPublisher eventPublisher;
public PagamentoOrquestradorService(IdempotencyService idempotencyService,
TransacaoPagamentoRepository transacaoRepository,
AuditPaymentService auditService,
FaturamentoEventPublisher eventPublisher) {
this.idempotencyService = idempotencyService;
this.transacaoRepository = transacaoRepository;
this.auditService = auditService;
this.eventPublisher = eventPublisher;
}
@CircuitBreaker(name = "pagamentoGateway", fallbackMethod = "fallbackProcessar")
public TransacaoPagamento processar(String idempotencyKey, Long pedidoId, java.math.BigDecimal valor) {
// 1. Idempotência: requisição duplicada retorna o resultado já processado.
if (!idempotencyService.verificarEBloquear(idempotencyKey)) {
return transacaoRepository.buscarPorIdempotencyKey(idempotencyKey);
}
// 2. Máquina de estados + trilha de auditoria a cada transição relevante.
TransacaoPagamento transacao = new TransacaoPagamento(idempotencyKey, pedidoId, valor);
transacao.transicionarPara(StatusTransacao.PROCESSANDO);
transacao.transicionarPara(StatusTransacao.APROVADO); // chamada real ao gateway ocorreria aqui
transacaoRepository.salvar(transacao);
auditService.registrarAuditoriaPagamento(transacao.getId(), transacao.getStatusAsString());
// 3. Evento assíncrono para desacoplar consumidores (faturamento, notificação).
eventPublisher.publicar(new FaturamentoEvento(pedidoId, valor.doubleValue()));
return transacao;
}
private TransacaoPagamento fallbackProcessar(String idempotencyKey, Long pedidoId,
java.math.BigDecimal valor, Throwable t) {
// Gateway indisponível: marca como pendente de reprocessamento, sem perder a transação.
return transacaoRepository.salvarComoPendente(idempotencyKey, pedidoId, valor);
}
}
🔗 Recursos Pedagógicos do Capítulo 20
| Recurso Didático | Finalidade | Link de Acesso |
|---|---|---|
| 📊 Slides de Aula | Apresentação visual interativa com Dark Mode e suporte a teclado | Ver Slides |
| 🧠 Quiz Formativo | Teste interativo de fixação com feedback imediato por alternativa | Fazer Quiz |
| 💻 Exemplos de Código | Demonstrações funcionais com código executável | Ver Exemplos |
| 🧩 Exercícios em 4 Níveis | Lista progressiva de fixação com gabarito em bloco colapsável | Resolver Exercícios |
| ⬅️ Capítulo Anterior | 📚 Sumário de Tópicos | 🏁 Conclusão |