Skip to content

Best Practices

Mario edited this page Sep 23, 2026 · 1 revision

Production best practices

Follow these recommendations when deploying Vyra in production.


The production checklist

1. Use stable wire identifiers, not class names

Always map your Java classes to versioned, domain-oriented string identifiers:

// Recommended: stable, versioned identifier
vyra.register("order.invoice.create.v1", CreateInvoiceRequest.class);

// Avoid: using raw Java class names as identifiers
vyra.register(CreateInvoiceRequest.class.getName(), CreateInvoiceRequest.class);

Why: if you refactor your Java packages or move classes between modules, the wire contract remains unchanged and compatible across nodes.


2. Wrap collections in concrete records

Java generics are erased at runtime. Therefore, responseType must always be a concrete Class<T>:

// Won't work: List<Player>.class is not valid Java syntax
client.request("players", req, List.class, timeout); // Unsafe casting required!

// Recommended: wrap the list inside a typed record
public record PlayerListResponse(List<Player> players) {}

client.request("players", req, PlayerListResponse.class, timeout)
      .thenAccept(res -> res.players().forEach(System.out::println));

3. Explicitly assign node IDs in production

While auto-generated UUIDs work fine in testing, production services should have clear, predictable instance identifiers:

Vyra vyra = RedisVyra.redis(client)
        .serializer(JacksonSerializer.jackson())
        .nodeId("billing-service-" + System.getenv("HOSTNAME")) // e.g. Kubernetes Pod name
        .build();

4. Enforce the uniform network constraint

All services connecting to the same broker must use the same serializer format and configuration:

  • If Service A uses JacksonSerializer.jackson(), Service B must also use JacksonSerializer.jackson().
  • Do not mix binary formats (Smile/CBOR) with JSON on the same transport.
  • Set up a shared common library in your organization that configures the serializer consistently.

5. Choose realistic timeouts with headroom

Never set arbitrary or infinite timeouts. Consider the complete request path: $$\text{Timeout} = \text{Network latency} + \text{Handler Execution Time} + \text{Safety Buffer}$$

// Give your remote database and service room to respond under load
Duration timeout = Duration.ofSeconds(3);
client.request("orders", req, OrderResponse.class, timeout);

6. Handle errors with structured codes, not raw messages

Exception messages can change between releases. Inspect the structured RemoteError.Code instead:

if (cause instanceof VyraRemoteException remote) {
    if (remote.getCode() == RemoteError.Code.SERVER_BUSY) {
        // Safe to retry with exponential backoff
        scheduleRetry(req);
    } else {
        // Log handler or domain failure
        logger.error("Remote failure: {}", remote.getMessage());
    }
}

7. Implement idempotency keys for state mutations

Because network timeouts can occur after a remote service has started or completed work, design state-mutating requests to be idempotent:

public record CreateChargeRequest(
    String idempotencyKey, // e.g. UUID generated on the client
    String customerId,
    double amount
) {}

The receiving handler can store processed idempotencyKey entries to avoid duplicate charges.


8. Use events only for tolerable loss

Events published via publish(...) provide at-most-once delivery:

  • Use events for: cache invalidations, audit logs, UI live updates, metrics.
  • Do not use events for: critical billing transactions or order processing. Use Request / response when delivery confirmation is required.

9. Clean up resources on shutdown

Integrate vyra.close() into your application shutdown hooks:

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    logger.info("Shutting down Vyra instance...");
    vyra.close();
    // redisClient.shutdown();
    // executorService.shutdown(); 
}));

Note

Following the lifecycle rule, calling vyra.close() closes only the connections opened by that specific Vyra instance.


Next steps

Vyra documentation

Getting started and messaging patterns

Transports and formats

Inside the framework

Reference

Clone this wiki locally