Skip to content

Getting Started

Mario edited this page Sep 23, 2026 · 1 revision

Getting started with Vyra

This guide walks you through setting up Vyra, writing your first typed request/response interaction, and broadcasting events.


Prerequisites

  • Java 17 or later: Vyra uses records and modern concurrency primitives.

Note

To follow along without installing a broker, use the vyra-inmemory transport. Everything in this guide runs in a single JVM.


Step 1: add dependencies

Vyra is modular: a typical project needs three modules:

  1. vyra-core, the messaging bus abstractions.
  2. A transport, such as vyra-inmemory for tests and local dev, vyra-redis for distributed deployments.
  3. A serializer, such as vyra-jackson (JSON, Smile, CBOR) or vyra-gson (JSON).

This guide uses the in-memory transport and Jackson.

Gradle (Kotlin DSL)

dependencies {
    // Core framework abstractions
    implementation("com.marioded.vyra:vyra-core:0.1.0")

    // Transport (choose one)
    implementation("com.marioded.vyra:vyra-inmemory:0.1.0") // Local dev
    // implementation("com.marioded.vyra:vyra-redis:0.1.0") // Distributed Redis transport

    // Serializer (choose one)
    implementation("com.marioded.vyra:vyra-jackson:0.1.0")  // JSON through Jackson (also Smile, CBOR)
    // implementation("com.marioded.vyra:vyra-gson:0.1.0")  // JSON through Gson
}

Maven

<dependencies>
    <!-- Core framework abstractions -->
    <dependency>
        <groupId>com.marioded.vyra</groupId>
        <artifactId>vyra-core</artifactId>
        <version>0.1.0</version>
    </dependency>
    <!-- Transport (choose one) -->
    <dependency>
        <groupId>com.marioded.vyra</groupId>
        <artifactId>vyra-inmemory</artifactId>
        <version>0.1.0</version>
    </dependency>
    <!-- <dependency>
        <groupId>com.marioded.vyra</groupId>
        <artifactId>vyra-redis</artifactId>
        <version>0.1.0</version>
    </dependency> -->
    <!-- Serializer (choose one) -->
    <dependency>
        <groupId>com.marioded.vyra</groupId>
        <artifactId>vyra-jackson</artifactId>
        <version>0.1.0</version>
    </dependency>
    <!-- <dependency>
        <groupId>com.marioded.vyra</groupId>
        <artifactId>vyra-gson</artifactId>
        <version>0.1.0</version>
    </dependency> -->
</dependencies>

Important

A serializer is required. Vyra does not bundle a default serializer so that vyra-core stays dependency-free. See Serialization for details.


Step 2: define a common wire contract

Network messages are plain, immutable Java records:

package com.example.usingvyra;

// 1. A request to fetch player information
public record GetPlayerRequest(String playerId) {}

// 2. The response returned by the backend
public record PlayerResponse(String playerId, String name, int score) {}

// 3. A one-way notification event broadcast to all subscribers
public record PlayerJoinedEvent(String playerId, long timestamp) {}

Step 3: create Vyra instances

This example uses two Vyra instances: one for the backend service, one for the client. Each instance needs a transport and a serializer.

Tip

For local development, use the InMemoryVyra factory. To connect multiple instances in the same JVM, share an InMemoryBus.

package com.example.usingvyra;

import com.marioded.vyra.core.Vyra;
import com.marioded.vyra.inmemory.InMemoryVyra;
import com.marioded.vyra.inmemory.InMemoryBus;
import com.marioded.vyra.jackson.JacksonSerializer;

// A shared bus represents the local network. In production this is your broker.
InMemoryBus sharedBus = new InMemoryBus();

// Server node
Vyra backend = InMemoryVyra.inMemory(sharedBus)
        .serializer(JacksonSerializer.jackson())
        .nodeId("backend-service")
        .build();

// Client node
Vyra client = InMemoryVyra.inMemory(sharedBus)
        .serializer(JacksonSerializer.jackson())
        .nodeId("api-gateway")
        .build();

Step 4: register message types

Before sending or handling messages, map each class to a stable string identifier. This identifier travels over the wire instead of Java class names:

backend.register("player.get", GetPlayerRequest.class);
backend.register("player.info", PlayerResponse.class);
backend.register("player.joined", PlayerJoinedEvent.class);

client.register("player.get", GetPlayerRequest.class);
client.register("player.info", PlayerResponse.class);
client.register("player.joined", PlayerJoinedEvent.class);

Tip

Stable identifiers decouple your wire protocol from Java package names and class refactoring.


Step 5: implement request / response

The backend handles incoming requests; the client sends them.

sequenceDiagram
    autonumber
    actor Caller as Client Application
    participant Client as Vyra Client
    participant Bus as Transport (InMemory / Redis)
    participant Backend as Vyra Backend
    actor Handler as Service Handler

    Caller->>Client: request("backend-service", req, PlayerResponse.class, 3s)
    Client->>Bus: send(REQUEST, destination: "backend-service")
    Bus->>Backend: deliver to worker queue
    Backend->>Handler: invoke handler on thread pool
    Handler-->>Backend: CompletableFuture<PlayerResponse>
    Backend->>Bus: send(RESPONSE, destination: clientNodeId)
    Bus->>Client: deliver to response listener
    Client-->>Caller: complete CompletionStage<PlayerResponse>
Loading

Backend: register a handler

handle(...) registers your business logic and immediately starts listening for requests:

import java.util.concurrent.CompletableFuture;

backend.handle("backend-service", GetPlayerRequest.class, req -> {
    System.out.println("Processing request for player: " + req.playerId());

    // Handlers return a CompletionStage, enabling async database or API integrations
    PlayerResponse response = new PlayerResponse(req.playerId(), "Mario", 1500);
    return CompletableFuture.completedFuture(response);
});

Client: send a request

request(...) returns a standard Java CompletionStage and requires an explicit timeout:

import java.time.Duration;

client.request("backend-service", new GetPlayerRequest("p-100"), PlayerResponse.class, Duration.ofSeconds(3))
        .thenAccept(player -> {
            System.out.println("Player found: " + player.name() + " (Score: " + player.score() + ")");
        })
        .exceptionally(throwable -> {
            System.err.println("Request failed: " + throwable.getMessage());
            return null;
        });

Important

responseType must be a concrete Class<T>. To return a collection such as List<PlayerResponse>, wrap it in a record (e.g. record PlayerList(List<PlayerResponse> players) {}).

Tip

Need to query all instances of a service to locate a resource? Use client.broadcastRequest("backend-service", ...) instead of client.request(...). See Request / response for broadcast routing and the silent non-owner pattern.


Step 6: publish and subscribe to events

Events are fire-and-forget broadcasts delivered to all active subscribers on a channel:

// 1. Subscribe on backend (or multiple microservices)
backend.subscribe("game-events", PlayerJoinedEvent.class, event -> {
    System.out.println("Broadcast received: " + event.playerId() + " joined at " + event.timestamp());
});

// 2. Publish from client
client.publish("game-events", new PlayerJoinedEvent("p-100", System.currentTimeMillis()));

Step 7: graceful shutdown

When your application stops, call close() to release transport connections and cancel pending timeouts:

client.close();
backend.close();

Moving to production

Switching from the in-memory transport to a distributed broker changes only the factory configuration. With Redis, for example:

import io.lettuce.core.RedisClient;
import com.marioded.vyra.redis.RedisVyra;
import com.marioded.vyra.jackson.JacksonSerializer;

// 1. Create your RedisClient
RedisClient redisClient = RedisClient.create("redis://localhost:6379");

// 2. Build the Vyra instance with the redis transport
Vyra vyra = RedisVyra.redis(redisClient)
        .serializer(JacksonSerializer.jackson())
        .nodeId("backend-node-1")
        .build();

Your message definitions, handlers and requests remain unchanged.


Next steps

Vyra documentation

Getting started and messaging patterns

Transports and formats

Inside the framework

Reference

Clone this wiki locally