Skip to content

1. Overview

Guide Home | Next: 2. Getting Started

View in another language — C#/.NET · C++ · Java · Kotlin · Node/TypeScript

The documents that own this chapter's contract — owned by the Framework Overview and the per-language public contract index.

This document is the entry point of the Java guide. The guide explains the concepts and usage of ZLink Framework directly so a Java developer can read it and start writing code right away. The language-neutral formal definition of a concept is owned by the common spec overview, and the formal contract of the Java public API is owned by the Java exact interface index document. If the two disagree, the spec wins.

1. One-Line Definition

ZLink Framework is a real-time messaging framework that integrates with the major framework you already use. The way Spring MVC sits on Spring as its web layer, ZLink Framework sits on Spring Boot as a real-time messaging layer. It's not a switch to a separate runtime or a dedicated server — it drops directly into the DI, hosted service, configuration, and logging model you're already using.

This layer provides inter-server calls, pub/sub, and real-time state units. Inter-server calls and pub/sub find their target purely by a logical channel name, with no separate gateway or dedicated load balancer. The real-time state units are SPOT (room · stage · zone), an actor (a stateful object representing one connection/user), and STREAM (an external client connection) — if these terms are unfamiliar, see the concept walkthrough in 03-concepts first. A developer writes a handler, client, and filter with the same feel as using HTTP/gRPC, and the framework handles connection, location lookup, routing, reconnect, and correlation.

ZLink is a framework used under the same contract across several languages. The same layer sits identically on Spring (Java/Kotlin) and NestJS (Node) too, and because the call contract is a language-neutral wire protocol (ZMP) + codec + logical channel/packet, services implemented in different languages call each other over the same channel (e.g., a room server in C++, an API server in .NET/Java). This guide is .NET-based and treats the .NET implementation as the reference implementation. The detailed cross-language model is covered by 17-alternative §2.1.

2. Situations Where You Need It

2.1 Building a Real-Time Game Server

What makes it hard. Game servers have no standardized framework like the web's ASP.NET Core/Spring. This isn't an accident — there's a reason.

  • The network topology each genre needs is different. Any web service is shaped the same way — "client request → server response" — which is why a framework could standardize around it. Games aren't. A board game needs room-based matching and turn progression, an MORPG needs a split between room/stage servers and matching/lobby, an MMORPG needs a zone/field server mesh and large-scale broadcast, an FPS needs a low-latency tick loop for a small session. Genre decides the topology, so there's no one fixed shape, and every team re-builds its own topology on top of raw sockets.
  • State stays in memory. The web can put state in a DB and scale out statelessly, but a game keeps room/participant state in-memory for fast processing and runs its logic across multiple threads. That's the moment locks, contention, deadlocks, and the synchronization question "which thread is touching this room" seep into business logic.
  • The connection itself is something to manage. Users keep long-lived connections. You handle socket framing and session lifetime directly, have to reconnect a user to whichever server and room they were in, and have to keep connected users and in-progress game state alive during a deployment or scale-down.

So up to now there were two choices — build all of this yourself, or move to a separate runtime, a game server engine, and relearn how you write logic, configure, deploy, and operate, on the engine's terms.

How it's actually been built. Using the names common in the industry, these approaches fall into roughly four patterns. Boxes like login/auth, gateway, and DB cache show up repeatedly no matter which pattern you pick — but since there's no common framework backing them, a team picks its genre's pattern and rebuilds that structure from the socket up.

↗ View larger

  • ① Zone-sharding. The world is split into geographic regions, one server (node) owns each region, and when a character crosses a boundary the simulation hands off to the adjacent region's server. This is the representative scaling approach for an MMORPG handling a large open world. Sharding (replicating the whole world and splitting players across copies) and instancing (spinning up several independent copies of the same region) are also common world-distribution approaches used together to handle a large number of concurrent players.
  • ② Lobby + room. Users are received in the lobby/matching stage and assigned to a room, which owns participant state until that match ends. A room is usually a logical unit, with several running together inside one process. Common in casual, mobile MO, and board games.
  • ③ Session-based dedicated fleet. Once matching tickets accumulate, the fleet assigns one dedicated server process for that match, and the client connects directly to that server. The process is returned once the match ends. Unlike ②, one match = one process is the base unit. The standard configuration for session-based games like competitive FPS and battle royale.
  • ④ Stateful actor. Entity state, like a player or guild, is kept as an actor in server memory, and the DB only serves as periodic storage. It reduces read-heavy load and removes the need for a separate caching layer, so it's commonly used for meta/social backends. The representative frameworks are Orleans and Akka. One conceptual difference — Akka's actor isn't one user, it's a general-purpose concurrency unit used anywhere, and ZLink splits this into Spot (an execution-isolation unit) and Actor (a domain entity). What's closer to Orleans's virtual actor/grain isn't ZLink's Actor — it's the Instance Spot this approach uses. The detailed comparison is covered in Chapter 17 §6.

What ZLink provides. A feature answers each difficulty, one by one.

Difficulty ZLink feature Details
Building a genre's topology from raw sockets Declare topology by combining channels — 1:N request/response, fan-out, a node-addressed route mesh, a room-scoped spot mesh, all composed in a few lines of registration; the location store keeps connections up automatically §3 Architecture · 05·06·10
Locks/contention on in-memory state SPOT serial execution — every message for one room lines up on a single execution line and runs in order. Locks disappear from business logic The code below · 06
Implementing socket framing/session lifetime directly STREAM — the framework owns connection lifetime, framing, and packet codec (TCP/TLS/WS/WSS) 09
Tracking a reconnected user's location Actor binding — a new connection after reconnect picks up the same actor 08
Users dropped during deployment Graceful drain — blocks new admission, hands off actors, finishes in-progress work, then shuts down. 0 lines of app code 12

And the four patterns above all become combinations on the same declarative model. There's no need to rebuild from the socket for each one.

  • ① Zone-sharding — set up a zone with addRouteMesh + a node-addressed route mesh. A player crossing a boundary is handed off by cross-node actor relocation (07) instead. ZoneWorld is exactly this approach.
  • ② Lobby + room — entry/matching is the Entry Spot, and a room is a room spot created with getOrCreate. Bingo is exactly this approach.
  • ③ Matchmaker + dedicated — matching is implemented as a channel handler (HTTP, etc.). Instead of spinning up a new process per match, the client connects over STREAM to the room spot that was getOrCreated as the matching result. TicTacToe is closest to this flow — matching request → room/connection info response → connect to the already-prepared room spot.
  • ④ Actor service — an Instance Spot is cold-activated by entity ID and serially processes the state of an entity that several users access at the same time, with no Redis distributed lock. Continued in the guild service example.

Where the "existing approaches" diagram above split into four, here's how each approach assembles with ZLink, in the same spots.

↗ View larger

Green (bold border) is the SPOT-family primitive. This is exactly where it contrasts with the "existing approaches" diagram above — each approach used to need its own infrastructure (a dedicated fleet orchestrator, sticky routing, an actor cluster), but in ZLink, all four are implemented with the same RouteMesh/Spot/Instance Spot combination. Switching approaches means no new runtime to learn.

A Twitch-scale FPS's ultra-low-latency snapshot netcode uses unreliable transport that tolerates loss. STREAM currently provides TCP/TLS/WS/WSS as transport, and unreliable transport (QUIC datagram/WebTransport) is planned. Even for that kind of game, though, matching/lobby/meta/social are handled just fine today by these four approaches. Exactly where the line falls is covered in Chapter 17 §4.

How is this different from a game server engine or service? Alternatives to building everything yourself include engines and managed services. Comparing what each provides by area makes ZLink's place clear.

Area provided Representative product Form provided
Connection/transport optimization — socket/session management, encryption/compression, TCP/UDP in parallel, splitting network I/O from logic threads ProudNet Dedicated server module + client SDK
Room/lobby/matching — creating/finding a room, lobby, match invites Photon·SmartFoxServer A room model on its own runtime
Hosting/fleet — dedicated server allocation, autoscaling, a matchmaking rules engine (FlexMatch) AWS GameLift·Agones A cloud-managed service
Social/meta features — friends, leaderboards, groups, chat Nakama A backend server product

ZLink provides connections/sessions (STREAM), rooms/state units (SPOT), inter-server messaging (channel), participant state (actor), and zero-downtime termination (host relocation) among these — but not as a dedicated runtime or managed service, as a library layer on the major framework you already use.

  • Hosting/fleet isn't ZLink's job. Whether K8s or GameLift, a ZLink server just runs on top of it — it doesn't compete with a hosting service, it composes with one.
  • Matchmaking rules and social features are app logic, not product features. You write them directly with a channel handler and spot. There's less pre-built for you, but the ownership and freedom over the logic stay with the app.

Instead of rebuilding for each language, ZLink puts the hard runtime in a single native Core (C API) and wraps it in per-language layers. Per-language bindings connect that C API to each language's socket API, and on top a per-language ZLink Framework provides surfaces like RouteMesh · SPOT · actor · STREAM. The reason for this thin 3-layer split is multi-language support — implement the Core once and swap only the language surface, and C++, .NET, the JVM, and Node share the same core. bindings and the Core are the framework's internal implementation, not exposed on the public API, and application code doesn't change even if they're replaced later — this backend boundary is explained separately by internals/backend-dependency-policy.

↗ View larger

As code. Declare one room, and write that room's progression logic.

// Registration — one room mesh and a room type
ZLinkMeshNodeBuilder node = options.addRouteMesh("game.room");
node.listen("tcp://0.0.0.0:9001");
// A mesh has at least 1 logical membership
node.channelName("game.room").server();
node.objects().server().addSpotFactory("room", BingoRoomSpot.class, factory -> factory.recreateOnRelocation());
// Bingo room progression code — no concurrency exists inside this.
public final class MarkNumberHandler
    implements ZLinkSpotRequestHandler<BingoRoomSpot, MarkNumber, MarkResult> {

    @Override
    public CompletionStage<MarkResult> handle(BingoRoomSpot room, MarkNumber request) {
        // No lock
        room.board().mark(request.number());
        room.setLastActivity(Instant.now());
        return CompletableFuture.completedFuture(new MarkResult(room.board().hasBingo()));
    }
}

Several players send requests at the same time and a timer runs in this room, yet there's no lock, no Interlocked, no Redis distributed lock. That's because the framework lines up every message for one room (requests, subscription events, timer ticks, actor packets) on a single execution line and runs them in order. Here, "serial" isn't codec serialization — it's serialization of execution order (06 §3).

Runnable reference samples: TicTacToe · Bingo · GameQuest

2.2 Concurrent Access to One Entity

Why it's hard. There are cases, like a guild, where several different users need to modify the same entity at the same time. Just like two users applying to join at the same time can exceed the roster cap, or two donations landing at once can lose one of them, several stateless API servers touching the same row at the same time creates a race condition.

  • Concurrent modifications collide. If several API instances read-modify-write the same guild row at the same time, a lost update happens.
  • You have to assemble your own serialization mechanism. A Redis distributed lock or DB row lock has to build a per-guild critical section.
  • The lock itself is a new failure mode. Lock acquisition failure, timeout, deadlock, and a stale write after lock expiry all land on the app to handle.

What ZLink provides. Instead of assembling a lock, it turns that entity into a serial execution unit.

What you used to assemble ZLink feature Details
A Redis distributed lock per guild id Instance Spot — one spot, cold-activated by guild id, processes every request for that guild serially 06
Lock acquire/release/timeout handling Serial execution — the lock concept disappears entirely; everything is always processed in spot queue order 06 §3
Inter-server calls/LB to find the guild spot channel name + location store 05·10
Pre-provisioning a new guild Cold-activated on the spot when the first request arrives — no separate preparation needed

The existing approach — lock acquire/release makes a round trip on every request.

↗ View larger

The ZLink approach — the lock disappears, and the guild id itself becomes the spot address the request will arrive at.

↗ View larger

A request for the same guild always passes through the same GuildSpot's queue, so the second request is only processed once the first finishes — it's not that another request is blocked for as long as the lock is held; two requests simply can never touch the same state at the same time in the first place.

As code. Where lock acquire/release used to sit, one call remains.

// Applying to join a guild — request directly by guild id. No prior lock, no prior creation.
spots.requestToSpot(guildId, new JoinGuildReq(userId))
    .instanceSpot("guild")
    .inMesh("social")
    .submit(JoinGuildRes.class);

There's no runnable reference sample for this scenario yet — the code above applies the same API surface as GameQuest's PlayerQuestSpot registration/call approach to a guild.

2.3 Adding Real-Time Features to an Existing Web Service

Why complexity goes up. Consider a food-delivery order app: placing and viewing an order are ordinary HTTP requests and responses, but status updates such as "preparing → out for delivery → arriving soon" need to be pushed in real time without requiring the user to refresh the app. The standard shape of a large web service — Spring/ASP.NET Core + Redis (cache) + Kafka (events) + LB/K8s — is optimized for stateless request/response. The moment you add a real-time feature like this, those assumptions stop fitting one by one, and complexity rises.

  • The connection becomes state. An HTTP request can land on any instance, but a WebSocket connection is pinned to one specific instance. That's how you end up with a sticky LB that pins connections, and the app starts managing "which instance is this user connected to right now" in Redis.
  • Real-time delivery between servers has to take a detour. Since connections are scattered across instances, server-to-server delivery routes through a broker (Redis pub/sub, or even Kafka when you don't actually need replay) — one more piece of infrastructure to operate.
  • Order-sensitive units appear. For an order or a conversation, the order events are processed in is correctness itself. Since several instances could touch the same order at the same time, you serialize with a distributed lock.

One feature bolted on, and you've grown a whole assembly kit — a WebSocket server, sticky LB, broker detour, distributed lock — plus the operational burden of running it.

What ZLink provides. A feature answers each piece of the kit.

What you used to assemble ZLink feature Details
A WebSocket server + sticky LB STREAM — the app server receives client connections directly 09
A distributed lock for ordering SPOT owner routing — the same order/conversation always executes serially in its own one Spot 06
Real-time delivery through a broker channel/fanout — inter-server delivery and fan-out go through transport directly 05
Managing "who's connected where" Actor binding + location store — the framework owns reconnect portability and location lookup 08·10

Drawing the same food-delivery order app — HTTP order processing + real-time delivery-status pushes — both ways shows the difference right in the picture.

The existing approach — the components for the real-time feature (orange) add up to as much as the main body.

↗ View larger

The ZLink approach — every orange piece disappears, leaving one location store that provides node/actor/spot location information.

↗ View larger

Three pieces of infrastructure — the sticky LB, pub/sub broker, and distributed lock — disappear. An Instance Spot preserves ordering, Session servers (STREAM) handle real-time connections instead of shell servers, and direct runtime connections handle inter-server delivery. The location store is the only new infrastructure.

As code. Where the distributed lock and sticky routing used to sit, the following code remains.

// Inside an HTTP handler — route an order event to that order's workflow Spot.
// The first request cold-activates the spot keyed on OrderId, and later requests arrive
// at the same already-created spot, always processed serially in one place (no distributed lock).
// request is already a StartOrderWorkflowReq body.
spots.requestToSpot(request.orderId(), request)
    .instanceSpot("order-workflow")
    .inMesh("commerce")
    .submit(StartOrderWorkflowRes.class);

// Inside an actor handler — push to a client that's still tied to the same actor after reconnect (no sticky LB).
actor.context().boundSession().send(new OrderStatusChanged(orderId, status)).submit();

Runnable reference samples: SupportChat · DeliveryDispatch

2.4 Simplifying Event-Driven Business Processing

Where ZLink applies isn't limited to real-time features. Business processes like order handling, settlement, and inventory — where the same entity's events must be processed in order, without duplication — run into the same complexity problem even with zero real-time push on screen.

Why it gets complicated. The standard answer for this kind of work is a log-based pipeline like Kafka (an event-sourcing setup is usually built on top of this too). But what the log actually solves is "gather the same key in one place, in order," and a whole train of pieces follows just to get that one thing.

  • Order is tied to a partition. To process the same order's events in order, you have to gather them by key partition, consumer count is tied to partition count, and consumer group rebalance and offset management follow as operational items.
  • Consumers are stateless, so state means a DB round trip every time. Processing one event means reading, modifying, and writing current state in the DB every time. Adding a cache to cut repeated reads brings an invalidation problem along with it.
  • At-least-once delivery pushes idempotency onto the app. Redelivery, rebalance, and reprocessing can bring the same event twice, so without a version check or a dedupe policy, it gets applied twice.
  • You build a separate read model to query the processing result, and once the pipeline falls behind, lag monitoring and a resync job stay as leftover work.

Keeping state next to the consumer with a stateful stream processor (Kafka Streams/Flink) cuts the DB round trips, but partition design, state-store recovery, and rebalance remain your operational responsibility — the detailed comparison is covered by GameQuest common scenario §3.

Drawing the same business process — an order workflow — both ways shows the difference in pieces right in the picture.

The existing approach — the pipeline pieces for ordered processing (orange) add up to as much as the main body.

↗ View larger

The ZLink approach — this doesn't replace Kafka. On the order-processing path, the pipeline pieces (orange) disappear, and Kafka stays in its natural role (gray) — propagating confirmed facts to independent systems and preserving events that need replay, as a durable log.

↗ View larger

The key thing across the two pictures is that Kafka's color changes. Kafka (orange), which used to own ordering inside the processing path, moves outside the processing path and only handles propagation/preservation (gray). And with that, the pieces assembled just for ordering — the order-processing consumer group (offset/rebalance/dedupe), the cache, the read model for queries, the resync job — disappear. Since the same OrderId is always processed serially by the same owner, there's no longer a need to assemble the ordering and duplicate-prevention a pipeline used to provide.

The inter-server-call LB disappears too. Order processing calls other services like inventory and payment synchronously, and the existing approach has to find and distribute to the peer on every one of those paths via a K8s Service or service discovery (you can't hardcode the address in code). In ZLink, you call by channel name, like "inventory", and the location store tells you the currently available peer, so there's no separate LB layer needed for inter-server calls — that's why the orange "LB for inter-server calls" is gone in the after picture.

What stays, stays. Client HTTP ingress is still stateless, so an L7 LB/Ingress distributes to API servers as usual (gray), and order state is still stored in the DB. Unlike gRPC, this HTTP ingress path also doesn't additionally require an L7 distribution device (the reason is covered in Chapter 17 §5.1).

What ZLink provides. Solving "gather the same key in one place, in order" with owner routing instead of a log means most of the pieces above simply never need to be assembled.

What you used to assemble ZLink feature Details
Key partition + consumer group SPOT owner routing — the same OrderId always executes serially on the same Spot. Whichever API instance receives it, it's routed to the same owner 06
DB load-modify-store per event The owner spot's hot state — state lives in the owner's memory, and the app decides when to persist based on business rules 06
Version check/distributed lock against redelivery Serial execution — no concurrent writer for the same unit, so there's no lock/version contention on the normal path 06 §3
LB/service discovery for inter-server calls channel name + location store — call by the name "inventory" and it sends directly to a currently available peer 05·10
Operating offset/lag/resync jobs With no consumption pipeline, that operational item doesn't exist at all

This doesn't replace your existing stack. Kafka stays exactly where it is, as a durable event stream, and Redis stays as cache/persistence support. What ZLink cuts is the complexity of connection, routing, and state management you used to assemble by hand in between.

The boundary stays where it is. Where a durable log is genuinely needed — event replay, long-term retention, broad fan-out to independent systems — Kafka is the right fit and stays exactly there (Chapter 17 §4). What ZLink cuts is the case where a log pipeline was assembled only for entity-scoped ordered processing. If order and consistency were the entire goal, owner routing achieves that goal directly, with no pipeline.

As code. Where the partition consumer used to sit, an owner Spot handler comes instead.

// Processing for the same OrderId always executes serially inside this Spot —
// no partition, no offset, no distributed lock, no idempotency retry policy to assemble.
public final class StartOrderWorkflowHandler
    implements ZLinkSpotRequestHandler<OrderWorkflowSpot, StartOrderWorkflowReq, StartOrderWorkflowRes> {

    @Override
    public CompletionStage<StartOrderWorkflowRes> handle(
        OrderWorkflowSpot spot, StartOrderWorkflowReq request) {
        // Accesses spot state without a lock
        return workflow.startInSpot(spot, request);
    }
}

Runnable reference sample: ShoppingMall — the reference sample for this exact situation, built with no real-time push at all, just an HTTP API + order workflow. It verifies order state transitions, compensation flow, duplicate prevention, and projection rebuild, all on top of owner routing.

The three situations differ only in entry point — the surface you use is the same. Products exist that provide one feature each — gRPC for RPC, Orleans for actors, a game engine for connections — but ZLink's niche is the combination that bundles major-framework integration + serial-execution state units + auto-connect topology into one.

3. Surface and Structure

3.1 The Call Unit — MeshName and ChannelName

An inter-server call in ZLink Framework picks its target by MeshName and ChannelName. In the application, you use it like "send a request over the orders channel in the services mesh." Which node handles that channel is decided by the framework, which checks the membership registered in the location store.

The framework handles what you'd otherwise have written by hand to build one server.

What you used to build yourself How the framework handles it
Opening an endpoint, managing peer connections Declare a MeshNode and STREAM node, and the hosted service connects them
Message serialization/deserialization Codec registration and the handler contract exchange DTOs directly
Request routing/dispatch Registering a typed handler on a ChannelName delivers the message to the right handler
Repeating common processing like logging/validation/authorization An HTTP route uses middleware; a ZLink handler separates this into ZLinkHandlerFilter
Protecting state under concurrent requests SPOT's serial execution manages state with no lock
Creating services, managing dependencies Spring DI creates the handler, client, and filter
Managing server addresses, deciding connections Tracks the currently active endpoint through the location store
Configuration, logging, monitoring Integrated with Spring configuration/logging/lifecycle

3.2 How It Feels Compared with the Existing Approach

The difference in the amount of code needed to wire up the same "inter-server request/response."

Directly with raw bindings (conceptual):

// Location-store lookup, connecting the endpoint, reconnect management,
// correlation id matching, serialization, receive loop ... dozens of lines of connection/setup code

ZLink Framework:

// Server: one handler
public final class GetPriceHandler implements ZLinkRequestHandler<PriceRequest, PriceReply> {

    @Override
    public CompletionStage<PriceReply> handle(PriceRequest request, ZLinkMessageContext context) {
        return CompletableFuture.completedFuture(
            // Fixed demo value (a real lookup result in practice)
            new PriceReply(request.symbol(), new BigDecimal("187.42")));
    }
}

// Registration — declares the MeshNode endpoint and the price membership's handler together.
// Scopes the communication range by MeshName.
options.addRouteMesh("services")
    // Opens this MeshNode's endpoint.
    .listen("tcp://0.0.0.0:7301")
    .setRoutingId(RoutingId.from("price-1"))
    // Registers the price-handling membership.
    .channelName("price")
    .server()
    .addRequestHandler(GetPriceHandler.class, PriceRequest.class, PriceReply.class);

// Client: inject the route client and call by ChannelName.
PriceReply reply = client
    .requestToChannel(
        // The ChannelName to look up process-locally
        "price",
        new PriceRequest("AAPL"))
    // Sends, then waits for the reply asynchronously.
    .submit(PriceReply.class)
    .toCompletableFuture().join();

The connection/setup code disappears, leaving a handler and a few lines of channel registration.

3.3 Layering and Registration Points

↗ View larger

Put the host framework you already use (ASP.NET Core · Spring Boot · NestJS · C++ host) at the bottom and register the ZLink Framework into it with AddZLinkFramework — the opposite of bringing in a new engine and moving to a separate ecosystem; all of this runs inside the framework you already use. On top of that, the only code you write is the business logic (Spot · Actor · handler), and the Framework exposes its own functionality through the DI · hosted service · handler · attribute model.

The point where the application meets this stack is one registration spot. This is where you declare the MeshNode, fanout, and STREAM node.

ZLinkFrameworkConfigurer zlink = options -> {
    // Provides node/actor/spot location info — connections between nodes are automatic on top of this
    options.addLocationStore(new ZLinkRedisLocationStore(...));

    // MeshNode for inter-server request/send
    options.addRouteMesh("services")
        .listen("tcp://0.0.0.0:7301")
        .setRoutingId(RoutingId.from("service-a"))
        // The logical membership to handle
        .channelName("orders").server();
    options.addFanoutChannel("events")
        // classic event fan-out
        .enablePublisher("tcp://0.0.0.0:7302");
    // SPOT/actor are also owned by a MeshNode
    options.addRouteMesh("game.room")
        .listen("tcp://0.0.0.0:7304")
        .setRoutingId(RoutingId.from("room-a"))
        .channelName("game.room").server();
    options.addStreamNode("gateway")
        // The external client endpoint
        .bind("tcp://0.0.0.0:7400");
};

Topologies you used to assemble separately with gRPC+LB, a broker, and a WebSocket server all collapse down to one declarative model. Once the location store is registered, connections auto-connect and auto-clean-up as servers scale up or down — nothing to edit in a config file, no LB to reconfigure. (05·06·09·10)

What you declare, and where, comes down to three spots.

Surface Role Chapter that covers it
builder.Services.AddZLinkFramework(...) Declare channel/SPOT/STREAM Chapter 5~Chapter 9
options.AddRouteMesh(...) / addFanoutChannel(...) Declare RouteMesh/fanout Chapter 5
IZLink*Runtime status Status observation and diagnostics Chapter 11

Every option settable at each surface, with its default, is collected in 16-options.

4. The Four Integration Axes, Summarized

↗ View larger

Axis What the user sees Guide chapter
channel messaging ZLinkRequestHandler, ZLinkSendHandler, ZLinkRouteClient, ZLinkHandlerFilter 05-channel-messaging
fanout addFanoutChannel, ZLinkFanoutHandler 05-channel-messaging
SPOT Typed spot factory, Spot context outbound, timer 06-spot
actor / session Actor factory, Entry Spot, ZLinkBoundSession, session actor dispatch 07-actor-spot · 08-actor-session
STREAM Framework session packet, Stream Connector 09-stream
Infrastructure Location-based auto-connect/operational queries, runtime monitoring 10-location, 11-monitoring
Operations Runtime metrics (one registration line), graceful drain, readiness probe 12-operations

5. The Overall Topology

An example showing how each feature fits together. Each feature's own chapter zooms into part of this map.

↗ View larger

  • Entry server — receives an external request over ASP.NET Core HTTP and delegates to the domain server.
  • Domain server — MeshNode channel membership + SPOT (state unit) + session relay + stream node.
  • Location store — manages server address information. The dotted lines are connections that find an endpoint through a store lookup.
  • Client app — sends requests over HTTP, and receives real-time state over stream.

6. Who This Guide Is for, and Its Scope

This guide focuses on when to reach for channel, handler, SPOT, STREAM, and the location store, rather than the runtime's internal structure.

The main readers are:

  • A backend API developer: someone sending a request to another internal service from inside an HTTP endpoint, or looking to replace an existing gRPC call with a logical channel name-based request/response.
  • A microservices operations developer: someone who wants server instances to scale up and down while auto-connecting through the current server list the location store manages, without hardcoding addresses in code.
  • A real-time service developer: someone who wants to bundle a stateful unit — a game room, stage, zone, or an order workflow — into a SPOT, and process packets arriving at the same state in one execution flow.
  • A gateway/connector developer: someone who wants to receive an external client over STREAM — TCP, TLS, WebSocket — and hand internal processing off to a channel or an actor.
  • A tech lead or reviewer: someone judging whether a problem is worth adopting ZLink for, and checking which responsibilities ZLink should own versus which should stay with the DB, broker, or domain service.

To see ZLink's use concretely, as a business flow, look at the common samples. See the real-time game server shape in TicTacToe and Bingo. ShoppingMall, DeliveryDispatch, GameQuest, and SupportChat are end-to-end samples that go all the way to the business domain — order workflow, assignment/status tracking, game progress, support/chat.

What this layer doesn't do is also clear. ZLink Framework isn't a layer that exposes a transport implementation to the application

code. An application developer uses the public feature set through the DI, hosted service, handler, and location store model. Anyone reviewing the formal public API contract should also read the spec/interfaces index; anyone changing the runtime's internal structure should also read internals/.

7. Naming Convention

The guide uses the following notation consistently throughout.

  • Java framework public types use the ZLink prefix (capital L). e.g. ZLinkRouteClient, ZLinkMessageContext, ZLinkFrameworkOptions.
  • Annotations use the same prefix. e.g. @ZLinkRequest, @ZLinkSpotActorSend.
  • The Maven coordinates and package are systems.zlink.*.
  • The client-side Stream Connector is an independent library with no dependency on the server framework artifact.
  • The underlying zlink core C API is zlink_* snake_case.

8. Guide Reading Order