Backend

Groovy / Vert.x

Referência completa de Groovy e Vert.x — closures avançadas, meta-programação, DSLs, Verticles, Event Bus, SQL Client, Circuit Breaker e mais

1. Groovy — Closures Avançadas

Uma closure é um bloco de código de primeira classe: pode ser armazenada em variáveis, passada como argumento e retornada de métodos. Ao contrário de lambdas Java, closures capturam o contexto (this, variáveis locais) e têm um delegate configurável — o que habilita DSLs poderosas.

// Definição e chamada básica
def saudar = { String nome -> "Olá, $nome!" }
println saudar("Rafael")  // Olá, Rafael!

// Parâmetro implícito 'it' para closures de um argumento
def dobrar = { it * 2 }
println dobrar(5)  // 10

// Closure com múltiplos parâmetros
def calcularFrete = { BigDecimal peso, String regiao ->
    def taxa = regiao == "SP" ? 2.5 : 4.0
    (peso * taxa).setScale(2, BigDecimal.ROUND_HALF_UP)
}
println calcularFrete(3.5, "SP")  // 8.75

// curry — fixa parâmetros parcialmente, retorna nova closure
def calcularFreteRegiao = calcularFrete.curry(_, "RJ")  // fixa regiao = "RJ"
println calcularFreteRegiao(3.5)   // 14.00
println calcularFreteRegiao(10.0)  // 40.00

// Currying posicional — rcurry fixa pelo fim
def somar = { a, b, c -> a + b + c }
def somarMais10 = somar.rcurry(10)  // fixa c = 10
println somarMais10(1, 2)  // 13

memoize — cache automático de resultado:

// Sem memoize — recalcula toda vez (custoso para Fibonacci)
def fib
fib = { long n -> n <= 1 ? n : fib(n - 1) + fib(n - 2) }

// Com memoize — resultado armazenado na primeira chamada
fib = { long n -> n <= 1 ? n : fib(n - 1) + fib(n - 2) }.memoize()
println fib(40)  // rápido mesmo para n grande

// memoizeAtMost — limita tamanho do cache (evita vazamento de memória)
def calcularDesconto = { String clienteId, BigDecimal valor ->
    // simulação de chamada custosa
    clienteService.buscarPercentualDesconto(clienteId) * valor / 100
}.memoizeAtMost(500)

delegate — base para DSLs:

// O delegate de uma closure determina quem responde às chamadas de método
class PedidoBuilder {
    String clienteId
    List<Map> itens = []
    BigDecimal desconto = BigDecimal.ZERO

    void item(String sku, int qty, BigDecimal preco) {
        itens << [sku: sku, quantidade: qty, preco: preco]
    }

    void desconto(BigDecimal percentual) {
        this.desconto = percentual
    }
}

def pedido(Closure config) {
    def builder = new PedidoBuilder()
    config.delegate = builder               // redireciona chamadas para o builder
    config.resolveStrategy = Closure.DELEGATE_FIRST
    config.call()
    return builder
}

def p = pedido {
    clienteId = "cliente-42"
    item("SKU-001", 2, 49.90)
    item("SKU-002", 1, 99.90)
    desconto(10.0)
}
println p.itens.size()  // 2

2. Groovy — GDK: Métodos em Coleções

O Groovy enriches todas as coleções Java com métodos funcionais via GDK (Groovy Development Kit). Esses métodos recebem closures e eliminam a necessidade de loops explícitos.

def pedidos = [
    [id: "p1", cliente: "Ana",    total: 150.00, status: "pago",      itens: 3],
    [id: "p2", cliente: "Bruno",  total: 89.90,  status: "pendente",  itens: 1],
    [id: "p3", cliente: "Ana",    total: 299.00, status: "pago",      itens: 5],
    [id: "p4", cliente: "Carlos", total: 49.90,  status: "cancelado", itens: 2],
    [id: "p5", cliente: "Bruno",  total: 120.00, status: "pago",      itens: 4],
]

// each — itera (sem retorno útil)
pedidos.each { println "$it.id: R$ $it.total" }

// collect — transforma cada elemento (equivale ao map)
def totais = pedidos.collect { it.total }           // [150.00, 89.90, 299.00, 49.90, 120.00]
def resumos = pedidos.collect { "$it.id ($it.status)" }

// findAll — filtra (mantém os que retornam true)
def pagos   = pedidos.findAll { it.status == "pago" }  // [p1, p3, p5]
def grandes = pedidos.findAll { it.total > 100 }       // [p1, p3, p5]

// find — retorna o PRIMEIRO que satisfaz a condição (ou null)
def primeiroAnaPago = pedidos.find { it.cliente == "Ana" && it.status == "pago" }

// inject — fold/reduce (acumulador)
BigDecimal totalGeral = pedidos.inject(BigDecimal.ZERO) { acc, p -> acc + p.total }
// 708.80

// sum com closure — atalho para inject com soma
def totalPagos = pedidos.findAll { it.status == "pago" }.sum { it.total }
// 569.00

// min e max
def maisBarato = pedidos.min { it.total }    // [id: "p4", ...]
def maisValioso = pedidos.max { it.total }   // [id: "p3", ...]

// groupBy — agrupa em Map[chave → lista]
Map<String, List> porCliente = pedidos.groupBy { it.cliente }
// [Ana: [p1, p3], Bruno: [p2, p5], Carlos: [p4]]

Map<String, List> porStatus = pedidos.groupBy { it.status }

// sort e sortBy
def ordenados = pedidos.sort { a, b -> b.total <=> a.total }  // decrescente por total
def porNome   = pedidos.sort { it.cliente }                   // crescente por cliente

// countBy — conta por critério
def contagemPorStatus = pedidos.countBy { it.status }
// [pago:3, pendente:1, cancelado:1]

// any e every
boolean temCancelado = pedidos.any { it.status == "cancelado" }   // true
boolean todosPagos   = pedidos.every { it.status == "pago" }      // false

// flatten e collectMany (flatMap)
def todosSkus = pedidos.collectMany { p ->
    (1..p.itens).collect { "SKU-${p.id}-${it}" }
}

// unique
def clientesUnicos = pedidos.collect { it.cliente }.unique()  // [Ana, Bruno, Carlos]

// intersect, disjoint, minus
def listaA = [1, 2, 3, 4, 5]
def listaB = [3, 4, 5, 6, 7]
println listaA.intersect(listaB)  // [3, 4, 5]
println listaA.minus(listaB)      // [1, 2]

3. Groovy — Meta-Programação

Meta-programação permite modificar o comportamento de classes em tempo de execução ou de compilação. É a base de frameworks como Grails, Spock e de muitos DSLs.

// methodMissing — intercepta chamadas a métodos inexistentes
class PedidoQuery {
    Map<String, Object> criterios = [:]

    def methodMissing(String nome, args) {
        if (nome.startsWith("porStatus")) {
            criterios.status = args[0]
            return this  // permite encadeamento
        }
        if (nome.startsWith("porCliente")) {
            criterios.clienteId = args[0]
            return this
        }
        throw new MissingMethodException(nome, PedidoQuery, args)
    }

    List executar() {
        // filtra usando os criterios coletados
        pedidoRepo.buscarPor(criterios)
    }
}

def query = new PedidoQuery()
def resultado = query.porStatus("pago").porCliente("Ana").executar()
// propertyMissing — intercepta acesso a propriedades inexistentes
class FlexivelConfig {
    Map<String, String> valores = [:]

    def propertyMissing(String nome) {
        valores[nome] ?: "não configurado"
    }

    def propertyMissing(String nome, valor) {
        valores[nome] = valor.toString()
    }
}

def cfg = new FlexivelConfig()
cfg.host = "localhost"
cfg.porta = 5432
println cfg.host   // localhost
println cfg.user   // não configurado
// ExpandoMetaClass — adiciona métodos a classes existentes em runtime
// Útil para enriquecer classes Java ou de terceiros

// Adicionar método a String
String.metaClass.paraSlug = { ->
    delegate.toLowerCase().replaceAll(/\s+/, "-").replaceAll(/[^a-z0-9-]/, "")
}

println "Meu Pedido Especial".paraSlug()  // meu-pedido-especial

// Adicionar método a uma classe específica
BigDecimal.metaClass.formatarBRL = { ->
    "R\$ ${delegate.setScale(2, BigDecimal.ROUND_HALF_UP)}"
}

println 149.9.formatarBRL()  // R$ 149.90

// Adicionar método estático
Integer.metaClass.static.diasUteisDoMes = { ->
    // simplificado
    20
}
println Integer.diasUteisDoMes()  // 20

// ExpandoMetaClass em instância específica (não afeta outras instâncias)
def pedido = new Pedido(id: "p1", total: 100.0)
pedido.metaClass.calcularComDesconto = { BigDecimal perc ->
    pedido.total * (1 - perc / 100)
}
println pedido.calcularComDesconto(10)  // 90.0

4. Groovy — Anotações de Transformação AST

As anotações @groovy.transform.* injetam código em tempo de compilação via AST (Abstract Syntax Tree) transformations. Eliminam boilerplate mantendo código legível.

import groovy.transform.*

// @Canonical — gera toString, equals, hashCode e construtor com argumentos posicionais
@Canonical
class Produto {
    String sku
    String nome
    BigDecimal preco
    String categoria
}

def p1 = new Produto("SKU-001", "Notebook", 3499.99, "Eletronicos")
def p2 = new Produto(sku: "SKU-001", nome: "Notebook", preco: 3499.99, categoria: "Eletronicos")
println p1 == p2   // true (equals por valor)
println p1         // Produto(SKU-001, Notebook, 3499.99, Eletronicos)
// @Builder — gera builder fluente
@Builder
class Pedido {
    String clienteId
    String enderecoEntrega
    List<ItemPedido> itens
    BigDecimal desconto
    LocalDate dataEntrega
}

def pedido = Pedido.builder()
    .clienteId("cliente-42")
    .enderecoEntrega("Rua das Flores, 123")
    .itens([new ItemPedido("SKU-1", 2)])
    .desconto(10.0)
    .build()
// @Delegate — delega métodos para um campo interno (composição sem herança)
class LoggingPedidoService {
    @Delegate PedidoService service = new PedidoServiceImpl()

    // Todos os métodos de PedidoService ficam disponíveis aqui
    // Podemos sobrescrever apenas os que queremos interceptar
    PedidoResponse criar(CriarPedidoRequest req) {
        log.info("Criando pedido para cliente {}", req.clienteId)
        def resp = service.criar(req)
        log.info("Pedido {} criado com sucesso", resp.id)
        resp
    }
}
// @TypeChecked — ativa checagem de tipos em tempo de compilação
// Útil para pegar erros antes de rodar
@TypeChecked
class CalculadoraDesconto {
    BigDecimal calcular(BigDecimal valor, int percentual) {
        // O compilador verifica tipos aqui
        valor * (100 - percentual) / 100
    }
}

// @CompileStatic — como @TypeChecked mas também gera bytecode eficiente (como Java)
@CompileStatic
class ProcessadorPedido {
    Pedido processar(CriarPedidoRequest req) {
        def pedido = new Pedido()
        pedido.clienteId = req.clienteId
        // Erros de tipo são pegos em compilação
        pedido
    }
}

// @Immutable — value object imutável completo
@Immutable
class Dinheiro {
    BigDecimal valor
    String moeda

    Dinheiro somar(Dinheiro outro) {
        assert moeda == outro.moeda : "Moedas diferentes: $moeda x $outro.moeda"
        new Dinheiro(valor + outro.valor, moeda)
    }
}

def preco = new Dinheiro(99.90, "BRL")
def frete = new Dinheiro(15.00, "BRL")
def total = preco.somar(frete)
println total  // Dinheiro(114.90, BRL)
// preco.valor = 50.00  // MissingPropertyException — imutável!

5. Groovy — DSLs com Closures e Builders

O delegate de closure é o mecanismo central para criar DSLs internas em Groovy. Frameworks como Gradle, Spock e Ratpack usam extensivamente esse padrão.

// DSL para definir regras de frete
class RegrasFreteBuilder {
    List<Map> regras = []
    BigDecimal freteGratisMinimoDefault = 150.0

    void para(String regiao, Closure config) {
        def regraBuilder = new RegraBuilder(regiao: regiao)
        config.delegate = regraBuilder
        config.resolveStrategy = Closure.DELEGATE_FIRST
        config.call()
        regras << regraBuilder.build()
    }

    void freteGratisAcimaDe(BigDecimal valor) {
        freteGratisMinimoDefault = valor
    }
}

class RegraBuilder {
    String regiao
    BigDecimal taxa
    int prazoEmDias
    BigDecimal maximoPesoKg

    void taxa(BigDecimal t) { this.taxa = t }
    void prazo(int dias) { this.prazoEmDias = dias }
    void maxPeso(BigDecimal kg) { this.maximoPesoKg = kg }

    Map build() { [regiao: regiao, taxa: taxa, prazo: prazoEmDias, maxPeso: maximoPesoKg] }
}

def configurarFrete(Closure config) {
    def builder = new RegrasFreteBuilder()
    config.delegate = builder
    config.resolveStrategy = Closure.DELEGATE_FIRST
    config.call()
    return builder
}

// Uso da DSL — lê como configuração, não como código
def regras = configurarFrete {
    freteGratisAcimaDe 200.0

    para("SP") {
        taxa 8.50
        prazo 2
        maxPeso 30
    }

    para("RJ") {
        taxa 12.00
        prazo 3
        maxPeso 25
    }

    para("OUTROS") {
        taxa 18.00
        prazo 7
        maxPeso 20
    }
}

println regras.regras.size()         // 3
println regras.freteGratisMinimoDefault  // 200.0

DSL para testes (estilo Spock/BDD):

// Mini-framework BDD com closures
class Cenario {
    String descricao
    Closure dado, quando, entao

    void dado(Closure c) { this.dado = c }
    void quando(Closure c) { this.quando = c }
    void entao(Closure c) { this.entao = c }

    void executar() {
        println "Cenário: $descricao"
        dado?.call()
        def resultado = quando?.call()
        entao?.call(resultado)
    }
}

def cenario(String desc, Closure config) {
    def c = new Cenario(descricao: desc)
    config.delegate = c
    config.resolveStrategy = Closure.DELEGATE_FIRST
    config.call()
    c.executar()
}

cenario("Pedido com desconto deve ter valor correto") {
    def pedido
    dado { pedido = new Pedido(total: 100.0) }
    quando { pedido.aplicarDesconto(10) }
    entao { assert pedido.total == 90.0 }
}

6. Groovy — SQL com groovy.sql.Sql

groovy.sql.Sql simplifica operações JDBC eliminando o boilerplate de try/catch/close. Funciona com qualquer driver JDBC.

import groovy.sql.Sql

// Conectar via DataSource (preferível em aplicações reais)
def sql = Sql.newInstance(dataSource)

// Também é possível conectar diretamente
def sql2 = Sql.newInstance(
    "jdbc:postgresql://localhost:5432/pedidos",
    "dev", "dev",
    "org.postgresql.Driver"
)

// SELECT — eachRow itera sem carregar tudo na memória
sql.eachRow("SELECT id, cliente_id, total FROM pedidos WHERE status = ?", ["pago"]) { row ->
    println "Pedido ${row.id}: R$ ${row.total}"
}

// SELECT — rows carrega todos os resultados como List<GroovyRowResult>
List resultados = sql.rows("SELECT * FROM pedidos WHERE criado_em > ?", [dataInicio])

// SELECT um único registro
def pedido = sql.firstRow("SELECT * FROM pedidos WHERE id = ?", [pedidoId])
println pedido?.cliente_id  // null-safe

// INSERT
sql.execute("""
    INSERT INTO pedidos (id, cliente_id, status, total, criado_em)
    VALUES (?, ?, ?, ?, NOW())
""", [UUID.randomUUID().toString(), "cliente-1", "aberto", 99.90])

// Batch INSERT — performance para múltiplos registros
sql.withBatch(50) { stmt ->  // flush a cada 50 registros
    pedidos.each { p ->
        stmt.addBatch([p.id, p.clienteId, p.status, p.total] as Object[])
    }
}

// UPDATE com execUpdate — retorna número de linhas afetadas
int afetadas = sql.executeUpdate(
    "UPDATE pedidos SET status = ? WHERE id = ? AND status = ?",
    ["pago", pedidoId, "pendente"]
)
println "Pedidos atualizados: $afetadas"

// Transação manual
sql.withTransaction {
    sql.execute("INSERT INTO pedidos ...", params1)
    sql.execute("UPDATE estoque ...", params2)
    // rollback automático se qualquer Exception for lançada
}

// Named parameters — mais legível para muitos parâmetros
sql.eachRow("""
    SELECT * FROM pedidos
    WHERE cliente_id = :clienteId
      AND status = :status
      AND total >= :minTotal
""", [clienteId: "cliente-42", status: "pago", minTotal: 100.0]) { row ->
    println row.id
}

// Fechamento automático com use
Sql.withInstance(dataSource) { sqlInstance ->
    sqlInstance.eachRow("SELECT count(*) as total FROM pedidos") {
        println "Total de pedidos: ${it.total}"
    }
}
// connection fechada automaticamente

7. Vert.x — Verticles e Deployment Options

Um Verticle é a unidade básica de Vert.x. Roda em um event loop (thread dedicada) e deve ser não-bloqueante. Para código bloqueante (banco legado, arquivos), use Worker Verticle ou execute em vertx.executeBlocking.

// Verticle padrão — roda no event loop; NUNCA bloquear aqui
public class PedidoVerticle extends AbstractVerticle {

    private PedidoRepository repository;
    private Router router;

    @Override
    public Future<?> start() {
        repository = new PedidoRepository(vertx);
        router = Router.router(vertx);
        configurarRotas();

        return vertx.createHttpServer()
            .requestHandler(router)
            .listen(8080)
            .onSuccess(server ->
                System.out.println("Pedidos API na porta " + server.actualPort()));
    }

    private void configurarRotas() {
        router.get("/api/pedidos/:id").handler(this::buscarPedido);
        router.post("/api/pedidos").handler(BodyHandler.create()).handler(this::criarPedido);
    }

    private void buscarPedido(RoutingContext ctx) {
        String id = ctx.pathParam("id");
        repository.findById(id)
            .onSuccess(pedido -> ctx.json(pedido))
            .onFailure(err -> ctx.fail(404, err));
    }

    private void criarPedido(RoutingContext ctx) {
        JsonObject body = ctx.body().asJsonObject();
        repository.save(body)
            .onSuccess(id -> ctx.response().setStatusCode(201).end(id))
            .onFailure(ctx::fail);
    }
}
// Worker Verticle — para código bloqueante (I/O síncrono, CPU intensivo)
public class RelatorioWorkerVerticle extends AbstractVerticle {

    @Override
    public Future<?> start() {
        // Worker pode bloquear — roda em thread pool separada
        vertx.eventBus().<JsonObject>consumer("relatorio.gerar", msg -> {
            try {
                // Operação bloqueante — OK aqui porque é Worker
                byte[] pdf = gerarPdfBloqueante(msg.body());
                msg.reply(Buffer.buffer(pdf));
            } catch (Exception e) {
                msg.fail(500, e.getMessage());
            }
        });
        return Future.succeededFuture();
    }
}

// Deployment com opções
public class Main {
    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();

        // Múltiplas instâncias do mesmo Verticle (uma por CPU)
        DeploymentOptions webOptions = new DeploymentOptions()
            .setInstances(Runtime.getRuntime().availableProcessors());

        // Deploying como worker
        DeploymentOptions workerOptions = new DeploymentOptions()
            .setThreadingModel(ThreadingModel.WORKER)
            .setInstances(4);

        vertx.deployVerticle(PedidoVerticle.class.getName(), webOptions)
            .compose(id -> vertx.deployVerticle(RelatorioWorkerVerticle.class.getName(), workerOptions))
            .onSuccess(id -> System.out.println("Todos os verticles deployados"))
            .onFailure(err -> {
                System.err.println("Falha no deploy: " + err.getMessage());
                vertx.close();
            });
    }
}

Executar código bloqueante a partir do event loop:

// Quando você não pode usar Worker Verticle mas precisa de operação bloqueante
vertx.executeBlocking(() -> {
    // Este código roda em thread pool — pode bloquear
    return arquivoService.processarCsvBloqueante(caminho);
}).onSuccess(resultado -> {
    // De volta ao event loop — não-bloqueante
    ctx.json(resultado);
}).onFailure(err -> ctx.fail(500, err));

8. Vert.x — HTTP Server e Router

O Router do Vert.x Web organiza handlers por rota e método HTTP. Suporta path params, body parsing, static files, CORS, autenticação e muito mais.

// Configuração completa de router
Router router = Router.router(vertx);

// Body handler — necessário antes de ler o body
router.route().handler(BodyHandler.create().setBodyLimit(1024 * 1024)); // 1MB

// CORS
router.route().handler(CorsHandler.create()
    .addOrigin("https://meusite.com.br")
    .allowedMethod(HttpMethod.GET)
    .allowedMethod(HttpMethod.POST)
    .allowedHeader("Content-Type")
    .allowedHeader("Authorization"));

// Logging de requests
router.route().handler(LoggerHandler.create());

// Static files
router.route("/static/*").handler(StaticHandler.create("webroot"));

// Rotas por método HTTP
router.get("/api/pedidos").handler(pedidoHandler::listar);
router.get("/api/pedidos/:id").handler(pedidoHandler::buscarPorId);
router.post("/api/pedidos").handler(pedidoHandler::criar);
router.put("/api/pedidos/:id").handler(pedidoHandler::atualizar);
router.patch("/api/pedidos/:id/status").handler(pedidoHandler::atualizarStatus);
router.delete("/api/pedidos/:id").handler(pedidoHandler::deletar);

// Sub-router por prefixo
Router adminRouter = Router.router(vertx);
adminRouter.get("/usuarios").handler(adminHandler::listarUsuarios);
router.mountSubRouter("/api/admin", adminRouter);

// Handler de erro global
router.errorHandler(404, ctx ->
    ctx.response()
        .setStatusCode(404)
        .putHeader("Content-Type", "application/json")
        .end(new JsonObject().put("error", "Recurso não encontrado").encode()));

router.errorHandler(500, ctx -> {
    ctx.failure().printStackTrace();
    ctx.response().setStatusCode(500)
        .end(new JsonObject().put("error", "Erro interno").encode());
});
// Handler de pedidos — exemplo completo
public class PedidoHandler {

    private final PedidoService service;

    public PedidoHandler(PedidoService service) {
        this.service = service;
    }

    public void buscarPorId(RoutingContext ctx) {
        String id = ctx.pathParam("id");
        String clienteId = ctx.queryParam("clienteId").stream().findFirst().orElse(null);

        service.buscar(id)
            .onSuccess(pedido -> {
                ctx.response()
                    .setStatusCode(200)
                    .putHeader("Content-Type", "application/json")
                    .end(JsonObject.mapFrom(pedido).encode());
            })
            .onFailure(err -> {
                if (err instanceof PedidoNaoEncontradoException) {
                    ctx.fail(404, err);
                } else {
                    ctx.fail(500, err);
                }
            });
    }

    public void criar(RoutingContext ctx) {
        JsonObject body = ctx.body().asJsonObject();

        // Validação básica
        if (body == null || !body.containsKey("clienteId")) {
            ctx.fail(400, new IllegalArgumentException("clienteId é obrigatório"));
            return;
        }

        service.criar(body)
            .onSuccess(pedido -> {
                ctx.response()
                    .setStatusCode(201)
                    .putHeader("Location", "/api/pedidos/" + pedido.getString("id"))
                    .putHeader("Content-Type", "application/json")
                    .end(pedido.encode());
            })
            .onFailure(ctx::fail);
    }

    public void listar(RoutingContext ctx) {
        int page = Integer.parseInt(ctx.queryParam("page").stream().findFirst().orElse("0"));
        int size = Integer.parseInt(ctx.queryParam("size").stream().findFirst().orElse("20"));

        service.listar(page, size)
            .onSuccess(lista -> ctx.json(lista))
            .onFailure(ctx::fail);
    }
}

9. Vert.x — HTTP Client para Chamadas Externas

O WebClient do Vert.x é um cliente HTTP não-bloqueante com API fluente. Ideal para chamadas a APIs externas, microsserviços e webhooks.

import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.codec.BodyCodec;

// Criação do cliente — reutilize a instância (não crie por request)
WebClient client = WebClient.create(vertx, new WebClientOptions()
    .setDefaultHost("api.pagamento.com")
    .setDefaultPort(443)
    .setSsl(true)
    .setConnectTimeout(3000)
    .setIdleTimeout(10));

// GET simples
client.get("/v2/status")
    .putHeader("Authorization", "Bearer " + token)
    .send()
    .onSuccess(response -> {
        System.out.println("Status: " + response.statusCode());
        JsonObject body = response.bodyAsJsonObject();
    })
    .onFailure(err -> System.err.println("Erro: " + err.getMessage()));

// POST com JSON
JsonObject payload = new JsonObject()
    .put("pedidoId", pedidoId)
    .put("valor", 99.90)
    .put("moeda", "BRL")
    .put("metodoPagamento", "pix");

client.post("/v2/cobrancas")
    .putHeader("Content-Type", "application/json")
    .putHeader("Authorization", "Bearer " + token)
    .sendJsonObject(payload)
    .onSuccess(response -> {
        if (response.statusCode() == 201) {
            String cobrancaId = response.bodyAsJsonObject().getString("id");
            processarCobranca(cobrancaId);
        } else {
            log.warn("Cobrança rejeitada: {} - {}", response.statusCode(), response.bodyAsString());
        }
    })
    .onFailure(err -> log.error("Falha ao criar cobrança", err));

// GET com BodyCodec — desserializa automaticamente
client.get("/v2/pedidos/" + pedidoId)
    .as(BodyCodec.jsonObject())
    .send()
    .map(HttpResponse::body)
    .onSuccess(pedido -> System.out.println("Cliente: " + pedido.getString("clienteId")));

// Retry com backoff usando Vert.x Future
Future<HttpResponse<JsonObject>> retentativa = client
    .get("/v2/status")
    .as(BodyCodec.jsonObject())
    .send()
    .expecting(HttpResponseExpectation.SC_SUCCESS)
    .recover(err -> {
        log.warn("Tentativa 1 falhou, retentando...");
        return client.get("/v2/status").as(BodyCodec.jsonObject()).send();
    });

10. Vert.x — Future e Promise — Composição Assíncrona

Future<T> representa o resultado de uma operação assíncrona. Promise<T> é o produtor que completa um Future. Use compose para encadear e Future.all/Future.join para paralelismo.

// Fluxo de checkout — encadeamento de operações assíncronas
public Future<ConfirmacaoPedido> checkout(String pedidoId, DadosPagamento dados) {
    return pedidoRepo.findById(pedidoId)

        // compose — transforma o resultado em outro Future (flatMap)
        .compose(pedido -> {
            if (pedido == null) return Future.failedFuture("Pedido não encontrado");
            return estoqueService.reservar(pedido.getItens())
                .map(reservaId -> new PedidoComReserva(pedido, reservaId));
        })

        // compose — pagamento
        .compose(pedidoReserva ->
            pagamentoService.cobrar(dados, pedidoReserva.getPedido().getTotal())
                .map(pagId -> new PedidoPago(pedidoReserva, pagId))
        )

        // compose — confirmar
        .compose(pedidoPago ->
            pedidoRepo.confirmar(pedidoPago.getPedido().getId(), pedidoPago.getPagamentoId())
        )

        // map — transformação síncrona (sem I/O)
        .map(pedido -> new ConfirmacaoPedido(pedido.getId(), Instant.now()))

        // recover — fallback ou tratamento de erro
        .recover(err -> {
            log.error("Checkout falhou para pedido {}: {}", pedidoId, err.getMessage());
            // Tentativa de estornar reserva se necessário
            estoqueService.liberarReserva(pedidoId).onFailure(e -> log.error("Falha ao liberar reserva", e));
            return Future.failedFuture(new CheckoutException(err.getMessage()));
        });
}

// Execução em paralelo — Future.all espera todos concluírem
public Future<DetalhePedido> buscarDetalhes(String pedidoId, String clienteId) {
    Future<Pedido> pedidoFuture   = pedidoRepo.findById(pedidoId);
    Future<Cliente> clienteFuture = clienteRepo.findById(clienteId);
    Future<List<Pagamento>> pagFuture = pagamentoRepo.findByPedidoId(pedidoId);

    // Executa os três em paralelo, aguarda todos
    return Future.all(pedidoFuture, clienteFuture, pagFuture)
        .map(r -> new DetalhePedido(
            r.resultAt(0),   // Pedido
            r.resultAt(1),   // Cliente
            r.resultAt(2)    // List<Pagamento>
        ));
}

// Promise — quando você controla quando o Future completa
public Future<String> processarComCallback(String dados) {
    Promise<String> promise = Promise.promise();

    // Simula callback de biblioteca legada
    legacyLib.processar(dados, new Callback() {
        @Override public void onSuccess(String resultado) { promise.complete(resultado); }
        @Override public void onError(Throwable t)        { promise.fail(t); }
    });

    return promise.future();
}

11. Vert.x — Event Bus

O Event Bus é o sistema de mensagens interno do Vert.x. Permite que Verticles se comuniquem sem referências diretas, habilitando baixo acoplamento e facilidade de teste.

// ========== CONSUMIDOR (receiver) ==========

// Point-to-point — recebe mensagens enviadas com send()
vertx.eventBus().<JsonObject>consumer("pedido.processar", message -> {
    JsonObject body = message.body();
    String pedidoId = body.getString("pedidoId");

    pedidoService.processar(pedidoId)
        .onSuccess(resultado -> {
            // Responder ao remetente (apenas para request/reply)
            message.reply(new JsonObject()
                .put("status", "processado")
                .put("pedidoId", pedidoId));
        })
        .onFailure(err -> {
            message.fail(500, err.getMessage());
        });
});

// Publish/subscribe — recebe mensagens enviadas com publish()
vertx.eventBus().<JsonObject>consumer("pedido.criado", message -> {
    String clienteId = message.body().getString("clienteId");
    emailService.enviarConfirmacao(clienteId);
    // Não precisa responder — publish é fire-and-forget
});

// Segundo consumidor do mesmo endereço (pub/sub — ambos recebem)
vertx.eventBus().<JsonObject>consumer("pedido.criado", message -> {
    estoqueService.reservar(message.body().getString("pedidoId"));
});
// ========== PUBLICADOR / REMETENTE ==========

JsonObject payload = new JsonObject()
    .put("pedidoId", pedidoId)
    .put("clienteId", clienteId)
    .put("total", 149.90);

// send — point-to-point (entrega a UM consumidor em round-robin)
vertx.eventBus().send("pedido.processar", payload);

// publish — broadcast (entrega a TODOS os consumidores registrados)
vertx.eventBus().publish("pedido.criado", payload);

// request/reply — envia e aguarda resposta como Future
vertx.eventBus().<JsonObject>request("pedido.processar", payload)
    .onSuccess(reply -> {
        String status = reply.body().getString("status");
        log.info("Pedido processado com status: {}", status);
    })
    .onFailure(err -> log.error("Processamento falhou: {}", err.getMessage()));

Codecs para tipos customizados:

// Codec para enviar objetos Java (não apenas JsonObject)
public class PedidoCodec implements MessageCodec<Pedido, Pedido> {
    @Override
    public void encodeToWire(Buffer buffer, Pedido pedido) {
        // Serializa para bytes
        byte[] bytes = objectMapper.writeValueAsBytes(pedido);
        buffer.appendInt(bytes.length);
        buffer.appendBytes(bytes);
    }

    @Override
    public Pedido decodeFromWire(int pos, Buffer buffer) {
        int length = buffer.getInt(pos);
        byte[] bytes = buffer.getBytes(pos + 4, pos + 4 + length);
        return objectMapper.readValue(bytes, Pedido.class);
    }

    @Override
    public Pedido transform(Pedido pedido) { return pedido; } // local delivery

    @Override public String name() { return "PedidoCodec"; }
    @Override public byte systemCodecID() { return -1; }
}

// Registrar o codec
vertx.eventBus().registerCodec(new PedidoCodec());

// Enviar objeto tipado
vertx.eventBus().send("pedido.processar", pedido,
    new DeliveryOptions().setCodecName("PedidoCodec"));

12. Vert.x — SQL Client PostgreSQL

O PgBuilder cria um pool de conexões não-bloqueante. Todas as operações retornam Future<T>.

import io.vertx.pgclient.PgBuilder;
import io.vertx.pgclient.PgConnectOptions;
import io.vertx.sqlclient.*;

// Configuração do pool
PgConnectOptions conectOptions = new PgConnectOptions()
    .setHost(System.getenv().getOrDefault("DB_HOST", "localhost"))
    .setPort(5432)
    .setDatabase("pedidos")
    .setUser("dev")
    .setPassword("dev")
    .setReconnectAttempts(3)
    .setReconnectInterval(1000);

PoolOptions poolOptions = new PoolOptions()
    .setMaxSize(10)
    .setMaxWaitQueueSize(30);

Pool pool = PgBuilder.pool()
    .with(poolOptions)
    .connectingTo(conectOptions)
    .using(vertx)
    .build();
// SELECT com parâmetros preparados (evita SQL injection)
pool.preparedQuery("SELECT id, cliente_id, status, total FROM pedidos WHERE id = $1")
    .execute(Tuple.of(pedidoId))
    .onSuccess(rows -> {
        if (rows.size() == 0) {
            // não encontrado
            return;
        }
        Row row = rows.iterator().next();
        String clienteId = row.getString("cliente_id");
        String status     = row.getString("status");
        BigDecimal total  = row.getBigDecimal("total");
    })
    .onFailure(err -> log.error("Erro ao buscar pedido", err));

// INSERT
pool.preparedQuery("""
        INSERT INTO pedidos (id, cliente_id, status, total, criado_em)
        VALUES ($1, $2, $3, $4, NOW())
    """)
    .execute(Tuple.of(UUID.randomUUID().toString(), clienteId, "aberto", total))
    .onSuccess(result -> log.info("Pedido inserido, rows: {}", result.rowCount()))
    .onFailure(err -> log.error("Falha ao inserir pedido", err));

// Batch INSERT — insere múltiplos registros com um único preparedQuery
List<Tuple> batch = itens.stream()
    .map(item -> Tuple.of(UUID.randomUUID().toString(), pedidoId, item.getSku(), item.getQuantidade()))
    .toList();

pool.preparedQuery("INSERT INTO itens_pedido (id, pedido_id, sku, quantidade) VALUES ($1, $2, $3, $4)")
    .executeBatch(batch)
    .onSuccess(result -> log.info("{} itens inseridos", result.rowCount()))
    .onFailure(err -> log.error("Falha no batch insert", err));

// Transação — garante atomicidade
pool.withTransaction(conn ->
    conn.preparedQuery("UPDATE pedidos SET status = $1 WHERE id = $2")
        .execute(Tuple.of("pago", pedidoId))
        .compose(r -> conn.preparedQuery(
            "INSERT INTO pagamentos (id, pedido_id, valor) VALUES ($1, $2, $3)")
            .execute(Tuple.of(UUID.randomUUID().toString(), pedidoId, valor)))
).onSuccess(result -> log.info("Pedido pago e pagamento registrado"))
 .onFailure(err -> log.error("Falha na transação de pagamento", err));

13. Vert.x — Circuit Breaker

O Circuit Breaker protege serviços externos de falhas em cascata. Quando o número de falhas ultrapassa o limite, o circuito “abre” e chamadas subsequentes falham imediatamente sem tentar o serviço externo, dando tempo para ele se recuperar.

import io.vertx.circuitbreaker.CircuitBreaker;
import io.vertx.circuitbreaker.CircuitBreakerOptions;

// Usando Resilience4j (recomendado com Vert.x 5)
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.vertx.circuitbreaker.VertxCircuitBreaker;

// Configuração do Circuit Breaker
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .minimumNumberOfCalls(5)           // mínimo de chamadas para avaliar
    .failureRateThreshold(50)          // % de falhas para abrir o circuito
    .waitDurationInOpenState(Duration.ofSeconds(10))  // tempo em estado aberto
    .slidingWindowSize(10)             // janela de avaliação
    .build();

io.github.resilience4j.circuitbreaker.CircuitBreaker cb =
    io.github.resilience4j.circuitbreaker.CircuitBreaker.of("pagamento-gateway", config);

// Uso no handler HTTP
router.post("/api/pagamentos").handler(ctx -> {
    JsonObject dados = ctx.body().asJsonObject();

    VertxCircuitBreaker.executeFuture(cb, () ->
        webClient.post("/cobrar")
            .sendJsonObject(dados)
            .expecting(HttpResponseExpectation.SC_SUCCESS)
    )
    .onSuccess(response ->
        ctx.response()
            .setStatusCode(201)
            .end(response.bodyAsString()))
    .onFailure(err -> {
        if (err instanceof io.github.resilience4j.circuitbreaker.CallNotPermittedException) {
            // Circuito aberto — serviço indisponível
            ctx.response()
                .setStatusCode(503)
                .end(new JsonObject().put("error", "Gateway de pagamento temporariamente indisponível").encode());
        } else {
            ctx.fail(500, err);
        }
    });
});

Circuit Breaker nativo do Vert.x (alternativa mais simples):

CircuitBreaker breaker = CircuitBreaker.create("estoque-service", vertx,
    new CircuitBreakerOptions()
        .setMaxFailures(5)
        .setTimeout(3000)
        .setFallbackOnFailure(true)
        .setResetTimeout(10000)  // tenta fechar após 10s
);

breaker.execute(promise -> {
    estoqueClient.verificarDisponibilidade(skuList)
        .onSuccess(promise::complete)
        .onFailure(promise::fail);
})
.onSuccess(disponivel -> ctx.json(disponivel))
.onFailure(err -> {
    // Fallback — pode retornar valor conservador ou cache
    ctx.json(new JsonObject().put("disponivel", false).put("fonte", "fallback"));
});

14. Vert.x — Health Checks

Health checks expõem endpoints /health/live e /health/ready para Kubernetes e load balancers saberem se o serviço está saudável.

import io.vertx.ext.healthchecks.HealthCheckHandler;
import io.vertx.ext.healthchecks.HealthChecks;
import io.vertx.ext.healthchecks.Status;

// Criar o sistema de health checks
HealthChecks healthChecks = HealthChecks.create(vertx);

// Health check de banco de dados
healthChecks.register("database", 3000 /* timeout em ms */, promise ->
    pool.getConnection()
        .compose(SqlConnection::close)
        .onSuccess(v -> promise.complete(Status.OK()))
        .onFailure(err -> promise.complete(Status.KO(
            new JsonObject().put("motivo", err.getMessage()))))
);

// Health check do Event Bus
healthChecks.register("event-bus", promise ->
    vertx.eventBus().<String>request("health.ping", "ping")
        .onSuccess(msg -> promise.complete(Status.OK()))
        .onFailure(err -> promise.complete(Status.KO()))
);

// Health check de serviço externo (gateway de pagamento)
healthChecks.register("pagamento-gateway", promise ->
    webClient.get("/ping")
        .send()
        .onSuccess(resp -> {
            if (resp.statusCode() == 200) {
                promise.complete(Status.OK(new JsonObject()
                    .put("latencyMs", resp.getHeader("X-Response-Time"))));
            } else {
                promise.complete(Status.KO(new JsonObject()
                    .put("statusCode", resp.statusCode())));
            }
        })
        .onFailure(err -> promise.complete(Status.KO(
            new JsonObject().put("motivo", "timeout ou erro de conexão"))))
);

// Registrar os endpoints no router
HealthCheckHandler livenessHandler  = HealthCheckHandler.createWithHealthChecks(healthChecks);
HealthCheckHandler readinessHandler = HealthCheckHandler.createWithHealthChecks(healthChecks);

router.get("/health/live").handler(livenessHandler);
router.get("/health/ready").handler(readinessHandler);
router.get("/health").handler(livenessHandler);  // atalho

15. Referência Rápida — Operadores e GDK

Operadores Groovy:

OperadorDescriçãoExemplo
?.Safe navigation — para em nulluser?.address?.city
?:Elvis — valor padrãonome ?: "Anônimo"
*.Spread — aplica em todoslista*.toUpperCase()
**Power2 ** 10 // 1024
<=>Spaceship — compara (-1, 0, 1)a <=> b
inPertence"x" in ["x", "y"]
!inNão pertence (Groovy 3+)"z" !in ["x", "y"]
instanceofTeste de tipox instanceof String
!instanceofNão é tipo (Groovy 3+)x !instanceof String
=== / !==Identidade de referência (Groovy 3+)a === b
~Regex pattern~/\d+/.matcher(s)
=~Regex find"abc123" =~ /\d+/
==~Regex match completo"abc" ==~ /[a-z]+/
asConversão de tipo"123" as Integer

GDK — Métodos de Coleções:

MétodoDescrição
each { }Itera sem retorno
collect { }Transforma cada elemento
findAll { }Filtra elementos
find { }Retorna o primeiro que satisfaz
inject(seed) { acc, v -> }Fold/reduce
sum { }Soma com transformação
min { } / max { }Menor/maior por critério
groupBy { }Agrupa em Map
countBy { }Conta por critério
any { } / every { }Existencial / universal
sort { }Ordena por closure
unique()Remove duplicatas
flatten()Achata listas aninhadas
collectMany { }flatMap
intersect()Interseção de listas
minus()Diferença de listas

16. Tabelas de Versões

Groovy

VersãoAnoPrincipais novidades
2.x2012–2019Closures, GDK, Groovydoc, @CompileStatic, @TypeChecked, traits, @Delegate; Groovy 2.5 adicionou @Builder melhorado, @AutoFinal e suporte a Java 9
3.x2020Parrot parser — sintaxe Java 8+ completa (lambdas, method refs, default em interfaces); operadores !in, !instanceof, ===, !==; GINQ (query SQL sobre coleções); @POJO; suporte a Java 14+
4.x2022Suporte a JPMS (Java modules); paridade com Java 17 (sealed classes, records, switch expressions); novo esquema de versionamento (4.x em vez de 3.0.x); remoção do groovy-all monolith (use módulos); remoção de APIs depreciadas 2.x/3.x; suporte a Java 19+
5.x2025 (preview)Suporte a Java 21+ features (virtual threads, pattern matching melhorado); alinhamento com GDK para tipos novos; melhorias em GINQ

Vert.x

VersãoAnoPrincipais novidades
3.x2015–2020Estabilização do modelo de Verticles; Web Router; Event Bus melhorado; suporte a Kotlin; gRPC; Service Proxy; métricas básicas
4.x2021Future/Promise substituem completamente callbacks; Web Client reescrito; métricas com Micrometer integrado; gRPC client nativo; EventBus com codecs melhorados; Java 11+ mínimo; SQL Client redesenhado (reactive)
5.x2024Suporte a Virtual Threads (Project Loom) — código bloqueante sem penalidade de performance; API unificada e simplificada; SQL Client melhorado; remoção das APIs depreciadas do 4.x; Java 11 mínimo mantido, Java 21 recomendado; integração Resilience4j oficial