02. Topology discovery¶
This category covers the topology registration entry points ZLinkFrameworkOptions provides,
and the entry points that query RouteMesh/ClientServer/Fanout operational status. The exact
signatures are owned by the
Java configuration and host exact interface
and the
Java channel messaging exact interface
(Korean-only). Every registration entry point is a configuration-time call made inside
ZLinkFrameworkConfigurer.configure(...).
addRouteMesh (configuration time)¶
Registers one physical MeshNode. The starting point for RouteMesh-based topology.
ZLinkMeshNodeBuilder play = options.addRouteMesh("play")
.listen(5501)
.setRoutingIdPrefix("play")
.setPlacementWeight(100);
Options. Commonly used modifiers are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.listen(endpoint) / .listen() / .listen(port) |
Does not bind if omitted | The receiving endpoint for this MeshNode |
.setBindHost(host) / .setAdvertiseHost(host) |
Follows configureNetwork()'s root default (BindHost 127.0.0.1) |
The bind/advertise host that applies only to this MeshNode |
.setRoutingId(routingId) / .setRoutingIdPrefix(prefix) |
Issued by the Framework | A fixed RID, or the prefix of an issued RID (1..64 ASCII characters of [A-Za-z0-9._-]) |
.setPlacementWeight(weight) |
100 (range 0..10000) |
The relative weight for placing new Actors/Spots on this node |
.setActorCapacity(max) / .setSpotCapacity(max) |
active 10,000 / pending 128 | The Actor/Spot capacity this node accepts |
.setActivationConcurrency(max) |
Framework default | The concurrent-execution cap for activation admission |
.setDefaultRequestTimeout(timeout) |
This MeshNode's default request timeout | The value requestToNode/requestToChannel (messaging-execution category) uses when .timeout(...) is omitted |
.setInstanceSpotIdleTimeout(timeout) |
Duration.ZERO (never reclaims) |
The Instance Spot idle reclaim time |
.configureRouterSocket() |
ZLinkMeshNodeSocketConfig default |
This MeshNode's ROUTER socket HWM/buffer/timeout (maxMessageSize defaults to 16_777_216L, etc.) |
.configureSpotPublisher() |
ZLinkSpotPublisherConfig default |
The Logical Multicast publisher socket's HWM/timeout/linger |
.objects() |
— | Enters Object role registration. See the Object role registration entry |
.channel(channelName) |
— | Enters this MeshNode's RouteMesh Channel role registration. See the RouteMesh Channel registration entry |
.peerConnections() |
— | See the Manual peer connections entry |
.addRouteSendHandler(handlerType, messageType) |
The packet name is determined from the message type | Registers a Node-direct one-way handler. The target sendToNode (messaging-execution category) calls |
.addRouteRequestHandler(handlerType, requestType, replyType) |
The packet name is determined from the message type | Registers a Node-direct request handler. The target requestToNode calls |
Completion result. Registers synchronously with no return value. An invalid combination (a
duplicate MeshName, a missing listener setting, etc.) surfaces as a ZLinkConfigurationException
in startup validation at Spring context initialization time.
When to use. Every host that uses RouteMesh registers at least one MeshNode. A node that only uses manual peers and needs no distributed discovery can start without a Location Store.
Object role registration (configuration time)¶
Registers how a MeshNode treats Actors/Spots (whether it only acts as a Client, or hosts them as a Server).
play.objects().server()
.addEntrySpot(GameEntrySpot.class)
.addSpotFactory("room", RoomSpot.class, factory -> factory
.executionMode(ZLinkUserSpotExecutionMode.SPOT_WIDE)
.preserveStateWith(RoomRelocationAdapter.class))
.addActorFactory("player", PlayerActor.class, PlayerActorFactory.class, factory ->
factory.preserveStateWith(PlayerRelocationAdapter.class));
Options. Commonly used modifiers are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.client() / .server() |
— | Selects the Object Client (reference only) or Object Server (hosting) role. server() includes client() capability, and both require a Location Store. None if omitted |
.addEntrySpot(entrySpotClass) |
None | Registers an Entry Spot type dedicated to external entry |
.addSpotFactory(spotType, spotClass, configure) |
None | Registers a stable User Spot type. configure must call exactly one of disableRelocation()/recreateOnRelocation()/preserveStateWith(...), in addition to stableTypeLimit/executionMode/relocationCoordinationMode |
.addInstanceSpotFactory(instanceSpotType, spotClass, configure) |
None | Registers a cold-activation Instance Spot type. configure must call exactly one relocation behavior, in addition to stableTypeLimit |
.addActorFactory(actorType, actorClass, factoryClass, configure) |
None | Registers a stable Actor type. configure must call exactly one relocation behavior (an Actor factory has no stableTypeLimit) |
Completion result. Registers synchronously with no return value. The Framework runs the
configure callback synchronously exactly once inside the registration call — calling a retained
builder again after the callback returns is a configuration error. Omitting the relocation
behavior, or calling more than one, is a startup configuration error.
When to use. Register the corresponding role when this node actually hosts Actors/Spots (Server), or only references Actors/Spots another node hosts as a messaging target (Client). See the actor-relocation category for relocation-policy selection criteria.
RouteMesh Channel registration (configuration time)¶
Registers logical ChannelName membership within the same MeshNode.
play.channel("play.api").server()
.setWeight(100)
.addHandlerGroup("api")
.addRequestHandler(GetPlayerHandler.class, GetPlayer.class, Player.class);
play.channel("play.events").client();
Options. After channel(channelName), call .client() or .server() exactly once.
.client() only creates the send path and has no modifiers. Commonly used modifiers of
.server() are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.setWeight(weight) |
100 (range 0..10000) |
The relative weight for this Server to be selected as a request/send target. 0 excludes it from selection |
.addHandlerGroup(groupName) |
None | Specifies the handler group that annotation-based handler scanning finds |
.addSendHandler(handlerType, messageType) |
The packet name is determined from the message type | Registers a one-way handler directly on this channel |
.addRequestHandler(handlerType, requestType, replyType) |
The packet name is determined from the message type | Registers a request/reply handler directly on this channel |
Completion result. Registers synchronously with no return value. A duplicate handler key
under the same owner surfaces as a ZLinkConfigurationException in startup validation.
When to use. Use .server() when registering a handler that sendToChannel/
requestToChannel (messaging-execution category) will receive. If this MeshNode only calls
another node's Server and places no handler of its own, register only .client(). Use
addClientServerChannel instead if communication must cross different processes.
addClientServerChannel (configuration time)¶
Registers an independent ClientServer Channel unrelated to RouteMesh.
options.addClientServerChannel("payments.api").server()
.listen(6001)
.setWeight(100)
.addRequestHandler(ChargeHandler.class, Charge.class, ChargeResult.class);
options.addClientServerChannel("payments.api").client()
.connect("payments-1:6001");
Options. Commonly used modifiers are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.server().listen() / .listen(port) |
Automatic bind | This Server's receiving port |
.server().setBindHost(host) / .setAdvertiseHost(host) |
Root default | The bind/advertise host that applies only to this Server |
.server().setWeight(weight) / .addSendHandler/.addRequestHandler |
Same as RouteMesh Channel Server | Weight and handler registration |
.client().connect(endpoint) |
manual | Connects to a specific Server manually. Omitting it finds the target via automatic discovery |
Completion result. Registers synchronously with no return value. A Client/Server using automatic discovery without a Location Store registration surfaces as a configuration error in startup validation. Client and Server can each be registered once on the same ChannelName, but registering the same role twice fails startup.
When to use. Use this for request/reply or one-way messaging between independent services that are not RouteMesh members. Between nodes in the same RouteMesh, use RouteMesh Channel registration instead.
addFanoutChannel (configuration time)¶
Registers a channel dedicated to classic fanout. It is the target ZLinkFanoutClient.publish
(messaging-execution category) publishes to.
options.addFanoutChannel("lobby.events")
.enablePublisher(7001)
.addHandlerGroup("events");
// automatic subscriber — automatically discovers publishers of the same ChannelName from the
// location store.
options.addFanoutChannel("lobby.events")
.enableSubscriber();
// manual subscriber — uses only the specified endpoints. Combining it with enableSubscriber()
// fails startup.
options.addFanoutChannel("lobby.events")
.connect("lobby-1:7001");
Options. Commonly used modifiers are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.enablePublisher(endpoint) / .enablePublisher() / .enablePublisher(port) |
None | Registers this channel's publisher role and receiving endpoint |
.setBindHost(host) / .setAdvertiseHost(host) / .setRoutingId(rid) / .setRoutingIdPrefix(prefix) |
Root default, or issued by the Framework | The bind/advertise host and RID that apply only to the publisher |
.enableSubscriber() |
— | automatic subscriber. Finds every valid publisher of the same ChannelName from the Location Store |
.connect(endpoint) |
— | manual subscriber. Uses only the specified endpoint |
.subscriberConnections() |
— | Returns a runtime handle (ZLinkEndpointConnections: connect/disconnect/listConnections) over the set of manual subscriber endpoints |
.addHandlerGroup(groupName) |
None | Links a typed event handler group |
Completion result. Registers synchronously with no return value. Configuring both automatic subscriber and manual subscriber on the same fanout channel surfaces as a startup failure.
When to use. Use this when creating a new observation/notification channel where the publisher need not know its subscribers. If a reply is needed, use RouteMesh Channel or ClientServer Channel registration instead.
addStreamNode (configuration time)¶
Registers a listener that accepts external STREAM connections.
options.addStreamNode("public-gateway")
.bind(9001)
.enableActorDispatch()
.registerSession(GameSession.class);
Options. Commonly used modifiers are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.bind(endpoint) / .bind() / .bind(port) |
Automatic bind | This STREAM listener's receiving port |
.setBindHost(host) / .setAdvertiseHost(host) |
Root default | The bind/advertise host that applies only to this listener |
.configureSocket() |
ZLinkStreamSocketConfig default |
Fine-tunes this listener socket's maxMessageSize and more |
.setTlsServer(certPath, keyPath) / .setTlsServer(certPath, keyPath, requireClientCertificate) |
No TLS | TLS server certificate/key, and whether to require mutual authentication |
.enableActorDispatch() |
Disabled | Dispatches an incoming message to a bound Actor via global ActorId lookup |
.registerSession(sessionClass) |
None | Registers a Session type implementing ZLinkSession |
.addSessionPacketHandler(handlerType) |
None | Adds a typed packet handler for the Session to process |
Completion result. Registers synchronously with no return value. A TLS configuration error
surfaces as a ZLinkConfigurationException in startup validation.
When to use. Use this to open a gateway that external clients connect to directly over the STREAM protocol. See the stream-session category for the exact Session/Actor wiring rules.
Manual peer connections (configuration time and runtime)¶
Connects to a specific endpoint manually, without automatic discovery. Called via
ZLinkMeshNodeBuilder.peerConnections().
play.peerConnections().connect("play-node-2:5501");
List<ZLinkMeshPeerConnection> connections = play.peerConnections().listConnections();
Options. This call carries the following modifiers.
| Modifier | Default | Meaning |
|---|---|---|
.connect(endpoint) |
No expected RID | The admission handshake determines the remote identity |
.connect(expectedRoutingId, endpoint) |
— | Does not admit if the handshake identity differs |
.disconnect(endpoint) |
— | Releases a registered connection |
.listConnections() |
— | Queries the currently registered connection list |
Completion result. Registers/releases synchronously with no return value. If both MeshNodes
are Object Clients and neither has RouteMesh Channel Server membership, this connection intent
stays in the list but never becomes a ready peer. If either side has any Channel Server
membership, ordinary peer admission/liveness rules apply even if the weight is 0.
The endpoint and optional expected RID supplied by the caller are connection intent. The application doesn't supply a lifecycle generation or security identity. When the runtime matches this endpoint to a Location Store descriptor to complete an object peer, it passes the descriptor's RID, positive lifecycle generation, and security identity as the internal handshake expected values. The caller therefore does not assemble this fence or call raw transport APIs. If the descriptor is not available yet, the runtime keeps the endpoint-only intent; when the descriptor appears later, it replaces the intent with the descriptor values only after liveness closes the previous intent. While no descriptor is available, the peer is not treated as a placement owner.
When to use. Use this to configure RouteMesh with a fixed peer list, without automatic discovery (a Location Store).
useFilter (configuration time)¶
Inserts common logic (authentication, logging, etc.) in front of every handler dispatch.
options.useFilter(AuthenticationFilter.class);
public class AuthenticationFilter implements ZLinkHandlerFilter {
@Override
public <T> CompletionStage<T> invoke(
ZLinkHandlerFilterContext context, ZLinkHandlerFilterNext<T> next) {
if (!isAuthenticated(context)) {
return CompletableFuture.failedFuture(
new IllegalStateException("rejected"));
}
return next.invoke();
}
}
Options. This call carries the following modifiers.
| Modifier | Default | Meaning |
|---|---|---|
.useFilter(filterType) |
None (runs in registration order) | Adds a ZLinkHandlerFilter implementation to the dispatch chain |
Completion result. Registers synchronously with no return value. Calling next.invoke() runs
the remaining filters and the handler. Not calling next in a request ends it as REJECTED, and
calling it twice rejects with IllegalStateException without re-running the handler.
context.dispatchKind() distinguishes NODE_DIRECT_SEND/NODE_DIRECT_REQUEST/CHANNEL_SEND/
CHANNEL_REQUEST/CLASSIC_FANOUT.
When to use. Use this when common preprocessing/validation must repeat across individual handlers. A filter does not construct the business reply itself — it only expresses rejection, and the handler does the rest. Does not apply to Spot/Actor/Logical Multicast/STREAM handlers.
Other host-wide options (configuration time)¶
Configuration that ends with a single simple value, which ZLinkFrameworkOptions provides.
options.addHandlersFromPackageOf(GameEntrySpot.class); // finds and registers annotation-marked handlers from the package
options.configureNetwork().setBindHost("0.0.0.0");
ZLinkDispatchOptions dispatch = options.configureDispatch();
dispatch.setCoreHwmProfile(ZLinkCoreHwmProfile.LOW_LATENCY);
dispatch.setApplicationJobQueueProfile(
ZLinkApplicationJobQueueProfile.LOW_LATENCY);
options.configureMetadata()
.allowSessionToActor("trace-id")
.allowActorToSession("server-region");
options.configureStreamCompression().useLz4();
options.setApplicationVersion(2);
options.useVirtualThreadHandlers();
Options. Commonly used entries are as follows.
| Modifier | Default | Meaning |
|---|---|---|
.addHandlersFromPackageOf(markerType) |
implicit auto-registration active | Specifies the target for annotation-based handler package scanning |
.configureMetadata().allowSessionToActor(key) / .allowActorToSession(key) |
Keys not specified are not forwarded | Adds a metadata key to forward across the STREAM session↔Actor relay to a direction-specific allowlist |
.configureNetwork() |
bindHost() is 127.0.0.1 |
The default bind/advertise host used unless an individual listen call overrides it |
.configureWorkers() |
ZLinkWorkerOptions default |
The bounded worker pool's minimum/maximum thread count, idle timeout, and queue cap |
.configureDispatch() |
Framework dispatch/diagnostics defaults; both profiles are BALANCED; manual values are empty |
Configures dispatch/diagnostics and the Core HWM memory/budget/profile inputs plus the host-wide Application Job Queue profile or exact manual permit limit on ZLinkDispatchOptions |
.configureStreamCompression() |
No compression | The STREAM default compression codec (useDefault()/useLz4()/use(codec)/disable()) |
.setApplicationVersion(version) / .setMaintenanceWave(wave) |
0 / null (no exclusion) |
The deployment version and maintenance wave every local MeshNode publishes |
.setDefaultRequestTimeout(timeout) |
Framework default | The host-wide default request timeout |
.useVirtualThreadHandlers() / .useHandlerExecutor(executor) |
Implementation default executor | Selects the execution model for handler dispatch (virtual thread or a specified Executor). Mutually exclusive |
.codecs() |
Only JSON registered | options.codecs().use(extension). See the Codec registration entry in messaging-execution category |
Completion result. Most execute synchronously with no return value; .configureNetwork()/
.configureWorkers()/.configureDispatch()/.configureMetadata()
return the corresponding builder or options object to continue further configuration on.
Exceeding a value's range surfaces as a configuration error in startup validation.
Core owns its byte-budget calculation. The Framework does not divide that budget by connection
count. The Application Job Queue is a separate job-count limit; setMaxQueuedApplicationJobs
accepts 1..2,147,483,647, while omission selects the Auto profile. See the
Core/Framework API contract.
When to use. Use this to adjust host-wide settings that end with a single simple value and do not belong to a dedicated category above (host lifecycle, topology registration, diagnostics).
Topology status query/observation¶
Checks the operational status of each of RouteMesh/ClientServer/Fanout. The three runtimes provide
the same shape (one snapshot query, streaming observation with observe), and all are injected
as Spring beans.
ZLinkMeshNodeSnapshot status = routeMeshRuntime.snapshot("play");
boolean canPlaceNewObjects = status.isReady() && status.placement().isAvailable();
routeMeshRuntime.observe("play", /*capacity=*/64)
.subscribe(new Flow.Subscriber<>() { /* check observed.status() in onNext(observed) */ });
Options. The correspondence among the three runtimes is as follows.
| Runtime | Target | Returned snapshot |
|---|---|---|
ZLinkRouteMeshRuntime |
MeshName | ZLinkMeshNodeSnapshot (includes channels, peers, placement) |
ZLinkClientServerRuntime |
ChannelName | ZLinkClientServerStatus (includes targets) |
ZLinkFanoutRuntime |
ChannelName | ZLinkFanoutStatus (includes publishers) |
Completion result. snapshot(...) is a synchronous call that returns a value immediately.
observe(...) returns Flow.Publisher<ZLinkObservedStatus<TStatus>>, and the loss() field
tells you whether observations were lost. Values only flow through Flow.Publisher after
Subscription.request(n) signals demand following subscribe(...).
When to use. Use this to judge a specific MeshName/ChannelName's availability, or to narrow
the scope of a failure. If host-wide status is needed, use status/observe in the
host-lifecycle category.
See the Java configuration and host exact interface and the Java channel messaging exact interface (Korean-only) for the full rationale.