Pular para o conteúdo principal
ImmutableLog logo
VoltarJava / JVM

Java

Guia completo de integração com o ImmutableLog em Java. Escolha entre middleware automático para Spring Boot, Quarkus e Micronaut, cliente HTTP direto para workers e jobs, ou @AuditEvent via Spring AOP para instrumentar métodos de serviço.

Filtros por framework

A forma mais rápida de integrar é via filtro HTTP nativo do framework. Toda requisição é capturada automaticamente — latência, status, client IP e hash do body — sem modificar nenhum controller. Clique no framework para ver o código completo.

Cliente HTTP direto

Use o java.net.http.HttpClient (Java 11+) para enviar eventos diretamente ao ImmutableLog sem depender de um framework web. Ideal para workers, Kafka consumers, scheduled jobs e qualquer código JVM que precise registrar eventos no ledger.

java
import java.net.URI;
import java.net.http.*;
import java.util.*;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ImmutableLogClient {

    private static final String API_URL = "https://api.immutablelog.com/v1/events";
    private final String apiKey;
    private final String serviceName;
    private final String env;
    private final HttpClient http = HttpClient.newHttpClient();
    private final ObjectMapper mapper = new ObjectMapper();

    public ImmutableLogClient(String apiKey, String serviceName, String env) {
        this.apiKey = apiKey;
        this.serviceName = serviceName;
        this.env = env;
    }

    public CompletableFuture<Void> sendEvent(
        String eventName,
        String type,          // "success" | "info" | "error"
        Map<String, Object> payload
    ) {
        try {
            String requestId = UUID.randomUUID().toString();

            Map<String, Object> meta = Map.of(
                "type", type,
                "event_name", eventName,
                "service", serviceName,
                "request_id", requestId,
                "env", env
            );

            Map<String, Object> body = Map.of(
                "payload", mapper.writeValueAsString(payload),
                "meta", meta
            );

            String json = mapper.writeValueAsString(body);

            var request = HttpRequest.newBuilder()
                .uri(URI.create(API_URL))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer " + apiKey)
                .header("Idempotency-Key", requestId)
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();

            // Fire and forget — never block the caller
            return http.sendAsync(request, HttpResponse.BodyHandlers.discarding())
                .thenApply(_ -> null);

        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}

// Usage:
var client = new ImmutableLogClient(
    System.getenv("IMTBL_API_KEY"), "my-service", "production"
);

client.sendEvent("user.created", "success", Map.of(
    "user_id", "usr_123",
    "email_hash", sha256("user@example.com"),
    "plan", "pro"
));

sendAsync() retorna um CompletableFuture sem bloquear a thread. O evento é enviado de forma assíncrona — a execução do código prossegue imediatamente após a chamada.

Retry com backoff exponencial

Para produção de alta disponibilidade, adicione retry automático com backoff exponencial. O ImmutableLog retorna 202 em sucesso e 429 quando o limite mensal é atingido — nunca faça retry em 429.

java
public CompletableFuture<Void> sendEventWithRetry(
    String eventName,
    String type,
    Map<String, Object> payload,
    int maxAttempts
) {
    return sendWithRetry(eventName, type, payload, maxAttempts, 0, 500);
}

private CompletableFuture<Void> sendWithRetry(
    String eventName, String type, Map<String, Object> payload,
    int maxAttempts, int attempt, long delayMs
) {
    return sendEventRaw(eventName, type, payload)
        .handle((res, ex) -> {
            // 202 = success, 429 = rate limited (never retry)
            if (ex == null) return CompletableFuture.<Void>completedFuture(null);
            if (attempt + 1 >= maxAttempts) return CompletableFuture.<Void>failedFuture(ex);

            return CompletableFuture
                .runAsync(() -> {}, CompletableFuture.delayedExecutor(
                    delayMs, TimeUnit.MILLISECONDS
                ))
                .thenCompose(_ -> sendWithRetry(
                    eventName, type, payload,
                    maxAttempts, attempt + 1, delayMs * 2   // exponential backoff
                ));
        })
        .thenCompose(f -> f);
}

Envio em lote (batch)

Em workers de alta frequência, acumule eventos e envie em paralelo usando CompletableFuture.allOf(). Cada evento é enviado individualmente — allOf() paraleliza sem falhar no conjunto inteiro.

java
public CompletableFuture<Void> sendBatch(List<EventDto> events) {
    // Send all events in parallel — fire and forget each one
    var futures = events.stream()
        .map(e -> sendEvent(e.eventName(), e.type(), e.payload())
            .exceptionally(ex -> null)  // never fail the batch
        )
        .toList();

    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}

// Usage:
client.sendBatch(List.of(
    new EventDto("order.created", "success", Map.of("order_id", "ord_1")),
    new EventDto("payment.captured", "success", Map.of("amount", 9900)),
    new EventDto("email.sent", "info", Map.of("template", "welcome"))
));

AOP @AuditEvent

Com Spring AOP, instrumente qualquer método de serviço sem modificar a lógica de negócio. O evento é emitido no bloco finally — registrado mesmo quando o método lança uma exceção.

java
// 1. Add dependency: spring-boot-starter-aop
// pom.xml: <artifactId>spring-boot-starter-aop</artifactId>

// 2. Define the annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuditEvent {
    String value() default "";
    String type() default "success";
}

// 3. Implement the aspect
@Aspect
@Component
public class AuditEventAspect {

    private final ImmutableLogClient client;

    public AuditEventAspect(ImmutableLogClient client) {
        this.client = client;
    }

    @Around("@annotation(auditEvent)")
    public Object audit(ProceedingJoinPoint pjp, AuditEvent auditEvent)
        throws Throwable {
        long startMs = System.currentTimeMillis();
        String name = auditEvent.value().isBlank()
            ? pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName()
            : auditEvent.value();

        try {
            Object result = pjp.proceed();
            long latency = System.currentTimeMillis() - startMs;
            client.sendEvent(name, auditEvent.type(), Map.of("latency_ms", latency));
            return result;
        } catch (Throwable ex) {
            long latency = System.currentTimeMillis() - startMs;
            client.sendEvent(name, "error", Map.of(
                "latency_ms", latency,
                "error_type", ex.getClass().getName(),
                "error_message", ex.getMessage()
            ));
            throw ex;
        }
    }
}

// 4. Use on any Spring bean:
@Service
public class OrderService {

    @AuditEvent("order.processed")
    public Order process(OrderRequest req) {
        // ... business logic
    }

    @AuditEvent(value = "payment.captured", type = "success")
    public void capturePayment(String orderId, long amountCents) {
        // ...
    }
}

@Around captura exceções

@Around é usado ao invés de @AfterReturning/@AfterThrowing para garantir que o evento seja emitido em todos os casos — sucesso, falha ou exceção.

Nome automático via reflection

Se value() estiver vazio, o aspecto usa o nome do método via pjp.getSignature(). Sempre prefira um nome semântico explícito via @AuditEvent("domain.action").

Configuração como Spring Bean

Para projetos Spring Boot, registre ImmutableLogClient como @Bean para injeção via @Autowired ou construtor em qualquer componente.

java
// Register ImmutableLogClient as a Spring bean for injection
@Configuration
public class ImmutableLogConfig {

    @Bean
    public ImmutableLogClient immutableLogClient(
        @Value("${immutablelog.api-key}") String apiKey,
        @Value("${immutablelog.service-name:my-service}") String service,
        @Value("${immutablelog.env:production}") String env
    ) {
        return new ImmutableLogClient(apiKey, service, env);
    }
}

// application.properties:
// immutablelog.api-key=${IMTBL_API_KEY}
// immutablelog.service-name=my-service
// immutablelog.env=production

Esta documentação reflete o comportamento atual da API. Para dúvidas ou integrações avançadas, entre em contato com o time de suporte.