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