Especialização em Backend com Java & Spring Boot • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 01 — Verificação Atômica de Idempotência com Redis

package com.empresa.api.service;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
public class IdempotencyService {

    private final StringRedisTemplate redisTemplate;

    public IdempotencyService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean verificarEBloquear(String chave) {
        // SETNX atômico: só uma requisição concorrente "ganha" e processa; as demais
        // encontram a chave já ocupada e sabem que é uma repetição em andamento.
        Boolean adquiriu = redisTemplate.opsForValue()
            .setIfAbsent("idempotency:" + chave, "PROCESSANDO", Duration.ofMinutes(10));
        return Boolean.TRUE.equals(adquiriu);
    }

    public void concluir(String chave, String respostaSerializada) {
        redisTemplate.opsForValue().set("idempotency:" + chave, respostaSerializada, Duration.ofHours(24));
    }

    public String buscarRespostaOriginal(String chave) {
        return redisTemplate.opsForValue().get("idempotency:" + chave);
    }
}
package com.empresa.api.controller;

import com.empresa.api.service.IdempotencyService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PagamentoController {

    private final IdempotencyService idempotencyService;
    private final PagamentoOrquestradorService orquestradorService;

    public PagamentoController(IdempotencyService idempotencyService,
                                PagamentoOrquestradorService orquestradorService) {
        this.idempotencyService = idempotencyService;
        this.orquestradorService = orquestradorService;
    }

    @PostMapping("/api/v1/pagamentos")
    public Object processar(@RequestHeader("Idempotency-Key") String idempotencyKey,
                             @org.springframework.web.bind.annotation.RequestBody NovoPagamentoRequest request) {
        if (!idempotencyService.verificarEBloquear(idempotencyKey)) {
            // Requisição duplicada: devolve o resultado original sem cobrar novamente.
            return idempotencyService.buscarRespostaOriginal(idempotencyKey);
        }
        return orquestradorService.processar(idempotencyKey, request.pedidoId(), request.valor());
    }
}

record NovoPagamentoRequest(Long pedidoId, java.math.BigDecimal valor) {}

💻 Código de Exemplo 02 — Auditoria Imutável com Hash Encadeado

package com.empresa.api.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;

import java.security.MessageDigest;
import java.time.Instant;

@Service
public class AuditPaymentService {

    private final AuditoriaRepository auditoriaRepository;
    private final ObjectMapper objectMapper;

    public AuditPaymentService(AuditoriaRepository auditoriaRepository, ObjectMapper objectMapper) {
        this.auditoriaRepository = auditoriaRepository;
        this.objectMapper = objectMapper;
    }

    public void registrarAuditoriaPagamento(Long transacaoId, String status) {
        try {
            String hashAnterior = auditoriaRepository.buscarUltimoHash();

            var registro = new RegistroAuditoria(transacaoId, status, Instant.now().toString(), hashAnterior);
            String jsonRegistro = objectMapper.writeValueAsString(registro);
            String hashAtual = calcularHash(jsonRegistro);

            // Cada registro referencia o hash do anterior: qualquer adulteração de um
            // registro passado quebra a cadeia de hashes de todos os registros seguintes.
            auditoriaRepository.salvar(jsonRegistro, hashAtual);
        } catch (Exception ex) {
            throw new IllegalStateException("Falha ao gravar auditoria imutável", ex);
        }
    }

    private String calcularHash(String conteudo) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hashBytes = digest.digest(conteudo.getBytes(java.nio.charset.StandardCharsets.UTF_8));
        return java.util.HexFormat.of().formatHex(hashBytes);
    }
}

record RegistroAuditoria(Long transacaoId, String status, String timestamp, String hashAnterior) {}

💻 Código de Exemplo 03 — Teste E2E do Fluxo Completo de Pagamento

package com.empresa.api;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
class PagamentoFluxoCompletoE2ETest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

    @org.springframework.test.context.DynamicPropertySource
    static void datasource(org.springframework.test.context.DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired private MockMvc mockMvc;

    @Test
    void deveProcessarPagamentoComIdempotenciaAuditoriaEEventoPublicado() throws Exception {
        String idempotencyKey = java.util.UUID.randomUUID().toString();
        String corpoRequisicao = """
            {"pedidoId": 501, "valor": 199.90}
            """;

        // 1ª chamada: processa de fato (autenticação, circuit breaker, auditoria, evento Kafka).
        mockMvc.perform(post("/api/v1/pagamentos")
                .header("Idempotency-Key", idempotencyKey)
                .contentType(MediaType.APPLICATION_JSON)
                .content(corpoRequisicao))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.status").value("APROVADO"));

        // 2ª chamada com a MESMA chave: deve retornar o resultado original, sem cobrar de novo.
        mockMvc.perform(post("/api/v1/pagamentos")
                .header("Idempotency-Key", idempotencyKey)
                .contentType(MediaType.APPLICATION_JSON)
                .content(corpoRequisicao))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.status").value("APROVADO"));
    }
}