ZLink Framework Java — User Guide¶
View in another language — C++ · C#/.NET · Java · Kotlin · Node/TypeScript
A Java application framework for building server systems where real-time messaging matters out of several cooperating processes. It goes straight into Spring Boot, so there is no separate runtime to move to.
// Both sides must name the mesh identically, or they never see each other
// as peers. The routing id names this node; without it the Framework assigns
// a generated one, which a caller cannot type into a URL.
//
// A caller that names a routing id in connect(...) compares the advertised
// endpoint against the endpoint it dialed. Both use 127.0.0.1 here.
ZLinkMeshNodeBuilder mesh =
options.addRouteMesh("game")
.listen("tcp://127.0.0.1:7501")
.setAdvertiseHost("127.0.0.1")
.setRoutingId(RoutingId.from("game-server-1"));
Register one handler and the framework takes care of message decoding, routing and encoding.
Systems It Is Built For¶
It is designed for systems where several server processes divide the roles between them and state changes reach the client in real time.
| Domain | Core scenario |
|---|---|
| Real-time games | Create a room -> player joins -> game state updates -> client push |
| Customer support chat | Open a conversation -> assign an agent -> relay messages -> push conversation state |
| Order workflow | Accept an order -> process each step -> change state -> notify the client |
| Delivery and dispatch | Request a dispatch -> assign and accept -> track state -> real-time push |
The shape they share is that server processes for each role talk in typed messages, and the client receives state changes over a real-time connection.
Core Capabilities¶
Channel messaging — typed request-reply between servers¶
A channel is a name given to a path between servers. One side sends a request to the channel name and the other side handles it and replies. Serialization (JSON · MessagePack · Protobuf) is the framework's work.
The sending side.
@GetMapping("/players/{playerId}/profile")
CompletionStage<Contracts.PlayerProfile> profile(@PathVariable String playerId) {
// The target is a channel name. Which node answers is decided at call time.
var profileReq = new Contracts.GetPlayerProfile(playerId);
return route.requestToChannel("profile", profileReq).submit(Contracts.PlayerProfile.class);
}
The receiving side.
// Answers a request addressed to the "profile" channel. Any node that exposes
// this channel may receive it; the caller does not pick one.
public final class GetPlayerProfileHandler
implements ZLinkRequestHandler<Contracts.GetPlayerProfile, Contracts.PlayerProfile> {
@Override
public CompletionStage<Contracts.PlayerProfile> handle(
Contracts.GetPlayerProfile request, ZLinkMessageContext context) {
var profile = new Contracts.PlayerProfile(request.playerId(), "rookie", 1);
return CompletableFuture.completedFuture(profile);
}
}
Besides request-reply there are the fanout (pub/sub) and route mesh (address routing) patterns. Channel messaging →
Spot — a unit of state without locks¶
A Spot binds one region of state and its participants into an execution unit: a game room, a support conversation, a unit of order processing. Everything that happens inside one Spot — participant packets, timers, joins and leaves — is processed serially. State is reached without a lock, and two requests never overlap in the same Spot even when the handling is asynchronous.
// A room owns its own state and is addressed by a global SpotId. Messages sent
// to one room run one at a time, so the fields below need no synchronization.
//
// Every Java user Spot names the actor type it can admit. This room admits
// none, so it names the base type and rejects every join. Actors are a later
// chapter.
public final class GameRoom implements ZLinkSpot<ZLinkActor> {
private final ZLinkSpotContext context;
private final List<String> chat = new ArrayList<>();
private String title = "untitled";
public GameRoom(ZLinkSpotContext context) {
this.context = context;
// Handler classes are named here rather than scanned, the same way the
// channel registrations name theirs. Each takes the target room as its
// first argument.
context.handlers().addHandler(PostChatHandler.class);
context.handlers().addHandler(GetRoomStateHandler.class);
}
@Override
public ZLinkSpotContext context() {
return context;
}
// Runs before the room accepts any message. Rejecting here means the create
// call fails and no room exists. Omit this method to accept every request.
@Override
public CompletionStage<ZLinkSpotCreateResponse> onCreate(ZLinkMessage request) {
var body = request.decode(Contracts.OpenRoom.class);
title = body.title();
var accepted = ZLinkSpotCreateResponse.accept();
return CompletableFuture.completedFuture(accepted);
}
// No actor ever joins this room, so the three membership callbacks below say
// so and do nothing else.
@Override
public CompletionStage<ZLinkSpotActorJoinResult> onActorJoin(
String actorId, ZLinkMessage request) {
var rejected = ZLinkSpotActorJoinResult.reject();
return CompletableFuture.completedFuture(rejected);
}
@Override
public CompletionStage<Void> onJoinedActor(ZLinkActor actor) {
return CompletableFuture.completedFuture(null);
}
@Override
public CompletionStage<Void> onLeaveActor(ZLinkActor actor) {
return CompletableFuture.completedFuture(null);
}
void append(String line) {
chat.add(line);
}
Contracts.RoomState state() {
return new Contracts.RoomState(title, List.copyOf(chat));
}
}
It divides into an entry spot that assigns (one per node) and a room spot that holds the state (one per unit). Periodic work is registered as a timer. Spot →
STREAM and Actor — the client's real-time connection¶
A client's real-time two-way connection is a STREAM, and the server-side object standing for one connection is an Actor. When a client connects, a session creates the Actor, and the Actor joins a Spot to take part in handling its state.
ZLinkSessionActor player = dispatch.actor();
if (player == null) {
var bound = context.actors().bound();
player =
switch (bound.size()) {
case 1 -> bound.getFirst();
case 0 ->
throw new IllegalStateException(
"Authenticate an Actor before sending player packets.");
default ->
throw new IllegalStateException(
"Select an Actor handle when more than one Actor is bound.");
};
}
return player.relay(dispatch, payload);
The client side of the connection is a separate product, the stream connector. STREAM → · Binding a session to an Actor →
Location — endpoints stay out of the code¶
When several servers of the same role are up, the address of the one to connect to is not written in the code. A shared location store keeps the addresses, and each server looks up the node an id is on right now.
// Rooms are addressed by id, not by host, so their current location is
// kept here. Every node reads and writes the same store under the same
// prefix.
var locationOptions =
new ZLinkRedisLocationOptions()
.setConnectionString("127.0.0.1:6379")
.setKeyPrefix("zlink-tutorial-java:location:");
options.addLocationStore(new ZLinkRedisLocationStore(locationOptions));
What Spring Boot provides¶
The DI container, configuration, HTTP endpoints and logging are Spring Boot's own. ZLink Framework registers its handlers and meshes on top of them, so REST endpoints and real-time connections run in the same process.
Table Of Contents¶
| Order | Document | Content |
|---|---|---|
| 1 | Overview | A quick map of the core surface, layers, and topology |
| 2 | Interactive Tour | Step through how each feature behaves, using a single MORPG |
| 3 | Quickstart | Install, a minimal project where two processes call each other, first-run checks |
| 4 | Core Concepts | What a channel, a Spot, an Actor and a session each are |
| 5 | Channel Messaging | The path that calls by name — registering and calling |
| 6 | Spot | Creating and calling a shared place by id |
| 7 | Actor | Creating and calling one entity by id |
| 8 | STREAM | A client outside the mesh attaching over one connection |
| 9 | Session and Actor | Binding one connection to one Actor |
| 10 | Location | Looking up the node something is on by id |
| 11 | Monitoring | A placeholder in the feature guide — no body yet |
| 12 | The Execution Model | Two queues, the serialization scope, the turn |
| 13 | Backpressure | When arrival outruns processing, and the options that affect it |
| 14 | Activation and Lifetime | Creation time per kind, lifecycle callbacks, injection lifetime |
| 15 | Actor Membership | Moving between Spots, reservations and limits |
| 16 | Timers and Workers | Periodic execution, running outside the line, giving the turn back |
| 17 | Relocation | What survives a move, the adapter, the unit |
| 18 | How Channels Work | Pattern differences, target selection, pub/sub, connection and discovery |
| 19 | Handlers and Message Processing | Registration variants, filters, codecs, handler kinds |
| 20 | How STREAM Works | Startup checks, error ownership, reply tokens, execution mode |
| 21 | How Session Binding Works | How many bindings, route refresh, disconnect, failures |
| 22 | Operations and Lifecycle | Runtime metrics, relocate, drain, readiness wiring |
| 23 | Options | The option list, the defaults and when to change them |
| 24 | Picking a Sample | Choosing which sample to read first and how to run it |
| 25 | Reading Along: Bingo | Authentication, matching, rooms, timers, multicast and cleanup in code order |
| 26 | Reading Along: TicTacToe | Manual connection and registration, an Actor join onto another node |
| 27 | Reading Along: SupportChat | Several Actors on one session, metadata relay, the idle timer |
| 28 | Reading Along: DeliveryDispatch | One-way sends, deadline records and reassignment, customer pushes |
| 29 | Reading Along: ShoppingMall | The owner Instance Spot, replay, next step and expected version |
| 30 | Reading Along: GameQuest | A per-player owner, best-effort pushes and reconciliation |
| 31 | Reading Along: ZoneWorld | Capacity placement, boundary joins and relocation, fanout and observation |
| 32 | E2E Testing | Verifying the whole system with the client library |
| 33 | Key Type Index | The contract interfaces indexed by their verification code |
| 34 | Monitoring | Awaiting rewrite — status snapshots and diagnostics |
The file number identifies the same chapter regardless of language. This table owns the reading order.
Chapters 01, 11, 13, and 16 are written separately for Java because the install steps and surface names differ per language. Open each chapter directly from the links in the table above.
How To Read The Diagrams¶
Every diagram in this guide uses the same visual language — the color is the concept.
Several chapters draw the same topology; what changes from chapter to chapter is where it is magnified.
Related Documents¶
- Public contract: Java public contract
- Language-neutral meaning: Common spec
- Client library: HTTP client · Stream connector