17. Where ZLink Fits — Internal Service Communication and Real-Time State Server Patterns¶
Guide Home | Previous: ../../../java/guide/server/16-options.en.md
View in another language — C#/.NET · C++ · Java · Kotlin · Node/TypeScript
This chapter has no spec document that owns a contract. It's an introduction to help readers decide what to choose.
If you're building internal service communication or a real-time state server and weighing gRPC or Akka/Orleans, ZLink is a candidate to take their place.
ZLink isn't a plain RPC library — it's a server-to-server and real-time messaging layer that bundles a logical channel, connection lifecycle, a dynamic state unit (SPOT), pub/sub, and location-based auto-connect into one framework on the backend. It pays off especially when "where the service is," "where the client is connected," and "how to serialize a state unit like a room/zone/symbol" keep coming up as recurring problems.
If the three situations in
01. Overview§2 (a real-time game server, adding real-time features to a web service, simplifying event-driven business processing) were "why you'd need it," this chapter brings that reasoning down to the level of a technology choice. The sample chapter covers the runnable business flow, and chapters 05–12 cover per-feature usage.
1. Where It's Used, at a Glance¶
Draw the boundary first. If a monolith or modular monolith is enough, don't reach for ZLink first. A call between modules in the same process is just a function call — it doesn't need server-to-server transport. ZLink is a tool for when you already have a reason to split across several processes/servers, and you want to cut the complexity of the communication, connection, routing, and state dispatch between them.
| Situation | Why ZLink helps | Feature used |
|---|---|---|
| Internal services calling each other often | Call by channel name instead of host/port/stub | channel + location store |
| Broadcasting an event to several services in real time | Transport fan-out without a separate broker | fanout pub/sub |
| A dynamic state unit like a game room, chat room, or ride zone | Lock-free serial state processing via a single execution queue | SPOT |
| A long-lived connection to a mobile/game client | The framework owns connection lifecycle, framing, and the reconnect flow | STREAM |
| Separating the connection server from the logic server | Reconnect portability through ActorId-based binding | session actor dispatch |
| Services implemented in different languages calling each other | Interoperable calls over the same channel contract, on top of a language-neutral wire protocol + codec | cross-language binding |
| Ultra-low-latency HFT, a durable queue, a public external API | Not ZLink's core territory | keep gRPC/REST/Kafka/FIX |
2. What You Stop Having to Worry About — the Development Model¶
ZLink's practical benefit isn't that infrastructure components disappear — it's that developers have less to worry about. The application deals only with domain units (channel/SPOT/session), and the framework handles the rest.
- You make calls knowing only the channel name — you don't know the target host/port/stub.
- Service location and peer distribution are handled by location-store-based auto-connect (10-location).
- Request correlation and waiting for a reply are handled by the framework.
- Client connection lifecycle and packet framing are handled by STREAM.
- Serial state for a room/zone/symbol is handled by the SPOT execution queue.
- Actor/session binding after a reconnect is carried forward by the framework.
- The handler/filter/DI model matches how existing web frameworks work, so it feels familiar.
ZLink doesn't eliminate these problems — it pushes them out of the caller's way. The framework handles location, connection, correlation, and dispatch serialization, so application code reads like business flow, not transport configuration.
2.1 Several Languages on One Channel (Cross-Language)¶
ZLink isn't tied to one language. Because the call contract is a language-neutral wire protocol (ZMP) + codec (protobuf/json/messagepack) + a logical channel/packet name, services implemented in different languages call each other over the same channel. For example, in a game system you could put the room server in C++ and the API/matchmaking server in .NET or Java, and message over the same channel/SPOT contract.
- The cross-language contract is a packet name + a codec-encoded DTO (protobuf is recommended across languages, or an agreed JSON/MessagePack schema). Unlike gRPC, it doesn't force service-stub code generation or HTTP/2 — only the payload schema is shared.
- Each language binding lays a handler/SPOT/STREAM surface on top of the same core (C ABI, ZMP). So even when the handler is written in a different language, on the wire it's the same channel and packet.
Different-language bindings. The same channel/packet contract is implemented by each language's binding in its own language. This guide's examples are split into language tabs, and whichever tab you look at describes the same contract. Cross-language is a design goal of ZLink — the call contract doesn't depend on the binding's implementation language.
3. When These Problems Keep Recurring, ZLink Is a Candidate¶
Judge by symptoms, not by technology names. If the following keep recurring, ZLink is a candidate.
- The gRPC stub, channel factory, deadline, and service-location lookup setup repeat for every service.
- gRPC load isn't spreading evenly under a Kubernetes L4 LB, so you're considering a mesh.
- You're protecting a state unit like a game room, chat room, or ride zone with a lock.
- You separately track, in Redis, which server a client was connected to before a reconnect.
- You're using Kafka for real-time event fan-out, but you don't actually need replay.
- External client connections, internal service calls, and room-state processing are spread across different frameworks.
4. What ZLink Doesn't Do — the Boundary¶
For the benefits to be clear, the boundary has to be clear too. The following are best left as-is.
| Requirement | ZLink guidance |
|---|---|
| A public-facing external HTTP API | Keep REST/gRPC |
| A durable queue, replay, consumer offsets | Keep Kafka/NATS |
| DB queries, geo-index, audit trail | Keep DB/Redis/event store |
| An HFT microsecond matching loop | Keep Disruptor/Aeron/FIX |
| Internal service communication + real-time state dispatch | ZLink fits |
The point: ZLink is a transport/dispatch layer, not a datastore, a durable log, or an HFT bus. Hard domain problems like distributed data consistency (saga, outbox, idempotency) and persistence/duplicate control remain the application's and infrastructure's responsibility.
5. Reference — Comparison with the gRPC/Service-Mesh Stack¶
To see why "internal services calling each other often" in §1 makes ZLink a candidate, compare it with the gRPC stack.
5.1 The Limits of gRPC Alone¶
gRPC's own performance is excellent. The problem is that the official best practices for making this kind of service "production grade" immediately call for additional infrastructure.
- Reusing channels/stubs is mandatory. "Always re-use stubs and channels when possible" — creating a channel per call inflates latency significantly, so you manage the lifecycle yourself with a channel factory/pool. (grpc.io performance)
- A deadline on every call. You attach a deadline so one slow RPC doesn't block an upstream service. (Microsoft Learn)
- The default load balancer (L4, per-connection distribution) doesn't spread gRPC load evenly. Because gRPC keeps one connection open for a long time over HTTP/2 and multiplexes many requests over it, an L4 load balancer sees only one connection, and requests pile onto whichever server that connection first connected to. Since it's built on HTTP/2, per-request (L7) distribution is effectively required, so you typically add one of the following on top.
- Client-side LB: the client holds the server list and calls them in rotation itself.
- A headless service (Kubernetes): exposes the service not as one virtual IP but as the IP list of each backing pod, so the client distributes evenly on its own.
- An Envoy/Istio service-mesh sidecar: a proxy auto-deployed alongside each service handles per-request (L7) distribution and encryption (mTLS) on its behalf. (Kubernetes blog)
- On top of that, service-location lookup (Eureka/Consul/xDS), retry/hedging, the
.protopipeline, mTLS, and yet another separate broker for event fan-out (Kafka/NATS).
L7 distribution splits work by looking at each individual request, not the connection — a mesh sidecar or client-side LB plays this role.
In other words, "using gRPC" really means running gRPC + an L7 LB (usually a mesh) + service-location lookup + an event broker + a proto pipeline together.
5.2 Deployment Shape Comparison¶
The Envoy sidecar and mesh control plane (service-location lookup, L7 LB, mTLS) give way to one layer: the framework plus the location store. The broker and the WS edge can be absorbed into the fanout channel and STREAM if the requirements are limited to real-time propagation and connection admission; if you need a durable queue with replay, or HTTP-edge policy, you keep them as-is.
5.3 The Path One Call Takes¶
5.4 Summary of What Collapses¶
| gRPC best practice/required infrastructure | In ZLink | Note |
|---|---|---|
| "Reuse stubs/channels" | The route client is a DI singleton and the framework manages the MeshNode connection lifecycle | Nothing to create per call |
| RPC deadline | RequestToChannel(...).Timeout(...) |
The reply-wait duration |
| L7 load balancing (Envoy/Istio) | Channel name + store auto-connect distributes traffic across peers | No sidecar needed |
| Interceptor | Handler filter | 5 §5 |
| Event broker (Kafka/NATS) | fanout channel pub/sub | Real-time fan-out only. A broker stays for persistence/replay |
| Unified observability (mesh telemetry) | The status stream and standard diagnostics | 11. Monitoring |
| Bidirectional streaming | STREAM session | Admits external clients. HTTP-edge policy is separate |
This comparison isn't trying to generalize which is better. gRPC is still a good choice when a public external API, a standard RPC contract, or organization-standard tooling matters. Performance also varies with payload size, codec, network, peer count, and deployment shape, so no numeric claim is made here. The gain described here is that the call path and the number of operational components shrink — a setup that used to go through an HTTP/2 proxy, a stub, and a separate broker collapses into one layer: the framework plus the location store. If your organization's security policy or external ingress still needs it, keep the existing mesh/LB alongside it.
6. Reference — Comparison with Distributed Actor Frameworks (Orleans/Akka)¶
Microsoft Orleans and Akka are representative frameworks used for the ④ stateful-actor
pattern in 01. Overview §2. Because ZLink's SPOT/actor offers the same
primitives (mailbox serialization + location transparency), the candidates overlap for this
workload.
6.1 The Limits of Orleans/Akka Alone¶
Orleans and Akka specialize in a single actor primitive. But to build "one real-time state server," the subject of this guide, you still have to assemble the pieces outside the actor yourself.
- No external client connection. Neither one bundles a protocol for a client to call a grain/actor directly. A web client usually connects through SignalR or a separate WebSocket server, which then calls into the actor.
- Not polyglot. Orleans is
.NET-only, Akka is JVM-only (Akka.NET is a separate port). Combining a C++ room server with a.NETAPI server under the same contract is outside their design scope. - Service-to-service messaging is separate from actor calls. Grain-to-grain calls exist, but there's no general service-messaging surface such as channel-name-based request/response or fanout — if you need one, you add gRPC or a message broker separately.
6.2 Deployment Shape Comparison¶
Client connections, service messaging, and actor state — three separate layers — collapse into one. But this diagram doesn't imply that the auxiliary tooling Orleans/Akka built up over a long time, like persistence connectors and reminder schedulers, also collapse into one. The table below separates raw feature differences from differences in the availability of this kind of pre-built tooling.
6.3 Feature Comparison — Advantages and Disadvantages¶
| Item | Orleans / Akka | ZLink |
|---|---|---|
| Actor primitive (mailbox serialization + location transparency) | ✅ | ✅ (SPOT/actor) |
| Built-in external client connection | ❌ Assemble SignalR/WS separately | ✅ STREAM |
| Polyglot | ❌ Single language (.NET or JVM) | ✅ |
| Typed inter-service messaging + declared topology | ❌ Assemble separately (gRPC, etc.) | ✅ channel + location store |
| Actor state persistence | ✅ Mature provider ecosystem | ⚠️ Lifecycle hooks exist; no pre-built storage connector (① below) |
| Restoring a SPOT timer after relocation | ✅ | ✅ Registration and the pending tick are included in the payload and restored automatically |
| Create a missing Actor or use an existing one | ✅ | ✅ getOrCreate coordinates concurrent creation of the same ActorId |
| Waking a dormant actor at a scheduled time (reminder) | ✅ One API call (Orleans Reminder) | ❌ No dedicated API — compose with a distributed scheduler (② below) |
| Distributed transactions | Orleans has experimental support | ❌ None (the app composes a saga) — this is an inherent protocol challenge that can't be worked around with existing primitives |
| License | Orleans MIT / Akka BSL (a paid trigger based on annual revenue) | framework is FSL-1.1-ALv2, core/binding are MPL-2.0 — no revenue-based paid trigger (§7) |
| Time proven in production | 10+ years (Halo, Microsoft 365, Skype) | Short — this project itself is still in progress |
① Actor state persistence — lifecycle hooks like onCreate/onClosing are provided,
but which DB to use and how to persist the state is left to the application to decide. This
means there's no bundle of pre-built storage connectors
(ShoppingMall is an example of this).
② Reminder — the application configures a distributed scheduler, such as Quartz.NET
Clustered or Hangfire, to run an Actor getOrCreate or message at a scheduled time.
Conclusion. For this guide's workload — "build one real-time state server without stitching components together" — ZLink is a viable alternative. The Framework provides Actor/SPOT lifecycle and relocation-timer restoration. Persistent-state providers and scheduled-time reminders must be composed by the application with its own storage and scheduler. Distributed transactions aren't provided either. Whether to migrate an existing Orleans/Akka system should be decided by weighing these differences together with your operational experience.
7. License — the Cost of Using It¶
A technology choice comes bundled with its license terms. Akka is BSL, which requires a commercial contract once annual revenue crosses a threshold; Orleans is MIT. ZLink's license differs by layer.
| Layer | License |
|---|---|
core, bindings — the messaging engine and per-language native bindings |
Mozilla Public License 2.0 |
framework — the SPOT/actor, channel messaging, STREAM, and drain this guide covers |
Functional Source License 1.1, ALv2 Future License |
Each language's http-client package |
Apache License 2.0 |
FSL-1.1-ALv2 in one line: it only blocks selling a product that competes with ZLink; everything else is allowed, and each release becomes Apache-2.0 two years after publication.
| Allowed | Building and shipping/selling your own product or service, internal company systems, education/research |
| Not allowed | A commercial product or service that replaces ZLink itself or provides substantially the same functionality |
| Cost | None. No usage fee, and no paid-conversion threshold like annual revenue |
| After two years | That release automatically converts to Apache-2.0 |
Conclusion. Whether it's a game server or a business server, there's no cost or restriction on building it, running it as a service, and selling it. There's no trigger like Akka's BSL that flips to paid once revenue grows large enough.
The reason core and bindings are MPL-2.0 is that core started from
libzmq v4.3.5, which is MPL-2.0. http-client is a thin
wrapper around each platform's conventional HTTP client library, so it's Apache-2.0.
The exact terms are governed by framework/LICENSE; the policy background is documented in doc/license/README.md.
8. Related Documents¶
- Common business scenarios: Framework Common Sample Scenarios
- How to use it: Channel Messaging
- Surface mapping: 05-channel-messaging §0, 13. Interface Catalog §1.6
- Samples as runnable code: 14-samples
References¶
- gRPC Performance Best Practices
- Performance best practices with gRPC (.NET)
- gRPC Load Balancing on Kubernetes without Tears
- System Design Study: Netflix's adoption of Service Mesh
- Scaling Microservices: Lessons from Netflix, Uber, Amazon, and Spotify
- Orleans overview (Microsoft Learn)
- The impact of the Akka License Change (Coralogix)