ZLink Framework Kotlin — 사용 가이드¶
다른 언어로 보기 — C++ · C#/.NET · Java · Kotlin · Node/TypeScript
실시간 메시징이 중요한 서버 시스템을 여러 프로세스로 나눠 만드는 Kotlin 애플리케이션 프레임워크다. Spring Boot 위에 그대로 들어가므로 별도 런타임으로 옮겨갈 필요가 없다.
// 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.
val mesh =
options
.addRouteMesh("game")
.listen("tcp://127.0.0.1:7601")
// The advertised endpoint must match the endpoint passed to
// connect(routingId, endpoint). Both use 127.0.0.1 here.
.setAdvertiseHost("127.0.0.1")
.setRoutingId(RoutingId.from("game-server-1"))
handler 하나를 등록하면 메시지 디코딩·routing·인코딩은 framework가 처리한다.
만드는 시스템¶
여러 서버 프로세스가 역할을 나눠 협력하고, 상태 변화를 실시간으로 client에 전달해야 하는 시스템에 맞게 설계됐다.
| 도메인 | 핵심 시나리오 |
|---|---|
| 실시간 게임 | room 생성 → player 입장 → 게임 상태 갱신 → client push |
| 고객 지원 채팅 | 대화 개설 → 상담원 배정 → 메시지 중계 → 대화 상태 push |
| 주문 워크플로 | 주문 접수 → 단계별 처리 → 상태 변경 → client 알림 |
| 배송·배차 | 배차 요청 → 수행자 배정·수락 → 상태 추적 → 실시간 push |
공통 구조는 역할별 서버 프로세스가 typed 메시지로 통신하고, client는 실시간 연결로 상태 변화를 받는 것이다.
핵심 기능¶
Channel 메시징 — 서버 간 typed 요청-응답¶
channel은 서버 사이의 통신 경로에 붙인 이름이다. 한쪽이 channel 이름으로 요청을 보내면 반대편이 처리해 응답한다. 직렬화(JSON · MessagePack · Protobuf)는 framework가 처리한다.
보내는 쪽이다.
@GetMapping("/players/{playerId}/profile")
suspend fun profile(@PathVariable playerId: String): PlayerProfile {
// The target is a channel name. Which node answers is decided at call time.
// The reply type is a reified type argument, not a Class passed at the end.
val request = GetPlayerProfile(playerId)
return route.requestToChannel<PlayerProfile>("profile", request).await()
}
받는 쪽이다.
// Answers a request addressed to the "profile" channel. Any node that exposes
// this channel may receive it; the caller does not pick one.
//
// ZLinkSuspendingRequestHandler is the Kotlin surface: the body is a suspend fun
// and returns the reply itself. The Java surface, ZLinkRequestHandler, returns a
// CompletionStage, which a suspend fun cannot override. The group annotation is
// what ties this class to the channel -- see HandlerGroups.kt.
@ZLinkHandlerGroup(HandlerGroups.PROFILE)
class GetPlayerProfileHandler : ZLinkSuspendingRequestHandler<GetPlayerProfile, PlayerProfile> {
override suspend fun handle(
request: GetPlayerProfile,
context: ZLinkMessageContext,
): PlayerProfile {
return PlayerProfile(request.playerId, nickname = "rookie", level = 1)
}
}
request-reply 외에 fanout(pub/sub)과 route mesh(주소 라우팅) 패턴도 제공한다. Channel 메시징 →
Spot — 상태 단위를 lock 없이 관리¶
Spot은 게임 room, 지원 대화, 주문 처리 단위처럼 상태 영역 하나와 그 참가자를 묶는 실행 단위다. 한 Spot 안에서 일어나는 일 — 참가자 packet, timer, 입장과 퇴장 — 은 모두 직렬로 처리된다. lock 없이 상태에 접근하며, 비동기 처리를 사용해도 같은 Spot에 두 요청이 겹치지 않는다.
// 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.
//
// ZLinkSuspendingSpot is the Kotlin base: it turns every CompletionStage
// callback into a suspending one. Its type argument is the actor type the room
// can admit; this room admits none, so it names the base type and rejects every
// join. Actors are a later chapter.
class GameRoom(override val context: ZLinkSpotContext) : ZLinkSuspendingSpot<ZLinkActor>() {
private val chat = mutableListOf<String>()
private var title = "untitled"
init {
// Handler classes are named here rather than scanned, the same way the
// channel registrations name their group. Each takes the target room as
// its first argument.
context.handlers().addHandler<PostChatHandler>()
context.handlers().addHandler<GetRoomStateHandler>()
}
// 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 suspend fun onCreateSuspending(request: ZLinkMessage): ZLinkSpotCreateResponse {
val body = request.decode<OpenRoom>()
title = body.title
return ZLinkSpotCreateResponse.accept()
}
// No actor ever joins this room, so the three membership callbacks below say
// so and do nothing else.
override suspend fun onActorJoinSuspending(
actorId: String,
request: ZLinkMessage,
): ZLinkSpotActorJoinResult {
return ZLinkSpotActorJoinResult.reject()
}
override suspend fun onJoinedActorSuspending(actor: ZLinkActor) {}
override suspend fun onLeaveActorSuspending(actor: ZLinkActor) {}
fun append(line: String) {
chat.add(line)
}
fun state(): RoomState {
return RoomState(title, chat.toList())
}
}
배정을 담당하는 entry spot(node마다 하나)과 상태 본체인 room spot(단위마다 하나)으로 나뉜다. 주기 작업은 timer로 등록한다. Spot →
STREAM과 Actor — client 실시간 연결¶
client의 실시간 양방향 연결을 STREAM이라 하고, 연결 하나를 대표하는 서버 쪽 객체가 Actor다. client가 접속하면 session이 Actor를 만들고, Actor는 Spot에 입장해 상태 처리에 참가한다.
// A slotted packet uses its dispatch Actor. An unslotted packet can use
// the connection only while exactly one Actor is bound.
val player =
dispatch.actor()
?: run {
val bound = context.actors().bound()
when (bound.size) {
1 -> bound[0]
0 -> error("Authenticate an Actor before sending player packets.")
else -> error("Select an Actor handle when more than one Actor is bound.")
}
}
player.kotlin().relay(dispatch, payload).await()
client 쪽 접속은 별도 산출물인 stream connector가 담당한다. STREAM → · Session과 Actor 연결 →
Location — endpoint를 코드에 적지 않는다¶
같은 역할의 서버 여러 대가 떠 있을 때 어느 쪽으로 연결할지 주소를 코드에 적지 않는다. 공용 location store가 주소를 관리하고, 각 서버는 id로 지금 있는 node를 조회한다.
// 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.
val locationOptions =
ZLinkRedisLocationOptions()
.setConnectionString("127.0.0.1:6379")
.setKeyPrefix("zlink-tutorial-kotlin:location:")
options.addLocationStore(ZLinkRedisLocationStore(locationOptions))
Spring Boot가 제공하는 것¶
DI container, configuration, HTTP endpoint, logging은 Spring Boot의 것을 그대로 사용한다. ZLink Framework는 그 위에 handler와 mesh를 등록하므로, 같은 프로세스 안에서 REST endpoint와 실시간 연결이 함께 동작한다.
목차¶
| 순서 | 문서 | 내용 |
|---|---|---|
| 1 | 개요 | 핵심 표면, 계층 구조, topology를 빠르게 파악하기 |
| 2 | 둘러보기 | MORPG 하나로 기능별 동작을 직접 눌러 보며 확인하기 |
| 3 | 퀵스타트 | 설치, 두 process가 서로 호출하는 최소 project, 첫 실행 점검 |
| 4 | 핵심 개념 | channel·Spot·Actor·session이 각각 무엇인가 |
| 5 | Channel 메시징 | 이름으로 부르는 경로 — 등록과 호출 |
| 6 | Spot | 여럿이 함께 사용하는 자리를 id로 만들고 부르기 |
| 7 | Actor | 개체 하나를 id로 만들고 부르기 |
| 8 | STREAM | mesh 밖의 client가 연결 하나로 붙기 |
| 9 | Session과 Actor 연결 | 연결 하나를 Actor 하나에 묶기 |
| 10 | Location | id로 지금 있는 node를 조회하기 |
| 11 | 모니터링 | 기능별 가이드 자리 표시 — 본문은 아직 없다 |
| 12 | 실행 모델 | 두 queue, 직렬화 범위, 실행권 |
| 13 | Backpressure | 처리보다 도착이 빠를 때와 영향을 주는 옵션 |
| 14 | 활성화와 수명 | 종류별 생성 시점, lifecycle callback, 주입 수명 |
| 15 | Actor membership | Spot 사이 이동, 예약과 상한 |
| 16 | Timer와 worker | 주기 실행, 줄 밖 실행, 실행권 반납 |
| 17 | Relocation | 옮겨도 남는 것, adapter, 이동 단위 |
| 18 | Channel 동작 원리 | 패턴 차이, 대상 선택, pub/sub, 연결과 discovery |
| 19 | Handler와 메시지 처리 | 등록 변형, filter, codec, handler 종류 |
| 20 | STREAM의 동작 원리 | 등록 검증, 오류 귀속, 응답 token, 실행 방식 |
| 21 | Session 묶음의 동작 원리 | 묶는 개수, 경로 갱신, 끊김 통지, 실패 |
| 22 | 운영과 lifecycle | 런타임 메트릭, relocate, drain, readiness 연결 |
| 23 | Options | 옵션 목록, 기본값과 바꾸는 시점 |
| 24 | 샘플 고르기 | 어떤 샘플을 먼저 볼지 고르고 실행하는 방법 |
| 25 | Bingo 따라 읽기 | 인증·매칭·room·timer·multicast·cleanup을 코드 순서로 |
| 26 | TicTacToe 따라 읽기 | 수동 연결·수동 등록, 다른 node로의 Actor join |
| 27 | SupportChat 따라 읽기 | session 하나에 Actor 여럿, metadata relay, idle timer |
| 28 | DeliveryDispatch 따라 읽기 | one-way send, deadline 기록과 재배정, 고객 push |
| 29 | ShoppingMall 따라 읽기 | owner Instance Spot, replay·다음 단계·expected version |
| 30 | GameQuest 따라 읽기 | player별 owner, best-effort push와 보정 |
| 31 | ZoneWorld 따라 읽기 | capacity placement, 경계 join과 relocation, fanout·관찰 |
| 32 | E2E 테스트 | client library로 시스템 전체를 검증하기 |
| 33 | 주요 타입 사용 색인 | 계약 인터페이스를 검증 코드로 색인 |
| 34 | 모니터링 | 재작성 대기 — 상태 snapshot과 진단 |
파일 번호는 언어에 상관없이 같은 장을 가리키는 식별자다. 읽는 순서는 이 표가 정한다.
Kotlin은 Java 런타임을 그대로 사용한다. zlink-framework-kotlin은 별도 구현이 아니라
coroutine idiom을 추가하는 얇은 레이어다. 그래서 이 가이드는 Java와 다른 지점만 사용하고
나머지는 Java 가이드를 가리킨다.
- 01 — 의존성과 등록 코드 모양이 달라 Kotlin 전용으로 사용한다.
- 11 · 13 · 16 — 표면은 Java와 같고 idiom만 다르다. 차이만 사용하고 나머지는 Java 장을 가리킨다.
같은 내용을 두 벌로 두지 않는 것이 목적이다. Java 문서가 바뀌면 Kotlin 독자도 같은 문서를 본다.
다이어그램 읽는 법¶
이 가이드의 다이어그램은 같은 시각 언어를 사용한다 — 색이 곧 개념이다.
여러 장이 같은 토폴로지를 그리며, 장마다 확대하는 위치만 바뀐다.
관련 문서¶
- 공개 계약: Kotlin 공개 계약
- 언어 중립 의미: 공통 스펙
- client library: HTTP client · Stream connector