TypeBox로 관리하는 프로토콜의 단일 진실 공급원 (Single Source of Truth)
서버와 클라이언트 사이의 통신 규약을 관리하다 보면 금세 머리가 아파지곤 해요. 서버의 데이터 구조를 바꿨는데 클라이언트 타입을 업데이트하는 걸 깜빡하거나, 문서와 실제 API 응답이 달라서 디버깅에 시간을 허비하는 일은 개발자라면 누구나 겪는 고통이죠.
이런 문제를 해결하려면 프로토콜을 정의하는 단 한 곳의 ‘단일 진실 공급원(Source of Truth)‘이 필요합니다. 우리는 TypeBox를 사용해 Gateway WebSocket 프로토콜을 정의하고, 이를 기반으로 런타임 검증, JSON Schema 추출, Swift 코드 생성까지 한 번에 처리하고 있어요.
필요한 것
섹션 제목: “필요한 것”- Node.js 및 pnpm 환경
- TypeScript 및 TypeBox에 대한 기본적인 이해
- Gateway 아키텍처에 대한 배경 지식
빠른 시작
섹션 제목: “빠른 시작”Gateway WebSocket 메시지는 세 가지 프레임 중 하나로 구성됩니다. 5분 안에 구조를 파악해 보세요.
- Request:
{ type: "req", id, method, params } - Response:
{ type: "res", id, ok, payload | error } - Event:
{ type: "event", event, payload, seq?, stateVersion? }
가장 중요한 규칙은 첫 번째 프레임이 반드시 connect 요청이어야 한다는 거예요. 그 이후에는 자유롭게 메서드를 호출하거나 이벤트를 구독할 수 있습니다.
Node.js 최소 클라이언트 예시
섹션 제목: “Node.js 최소 클라이언트 예시”연결 후 health 체크를 수행하는 가장 간단한 흐름입니다.
import { WebSocket } from "ws";
const ws = new WebSocket("ws://127.0.0.1:18789");
ws.on("open", () => { ws.send( JSON.stringify({ type: "req", id: "c1", method: "connect", params: { minProtocol: 3, maxProtocol: 3, client: { id: "cli", displayName: "example", version: "dev", platform: "node", mode: "cli", }, }, }), );});
ws.on("message", (data) => { const msg = JSON.parse(String(data)); if (msg.type === "res" && msg.id === "c1" && msg.ok) { ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" })); } if (msg.type === "res" && msg.id === "h1") { console.log("health:", msg.payload); ws.close(); }});스키마 관리 및 실행 파이프라인
섹션 제목: “스키마 관리 및 실행 파이프라인”모든 스키마 정의는 src/gateway/protocol/schema.ts에서 시작됩니다. 여기서 정의된 스키마는 다음과 같은 파이프라인을 거쳐 활용돼요.
pnpm protocol:gen: JSON Schema(draft-07)를 생성하여dist/protocol.schema.json에 저장합니다.pnpm protocol:gen:swift: macOS 앱에서 사용할 Swift 모델을 생성합니다.pnpm protocol:check: 두 생성기를 모두 실행하고 결과물이 최신 상태로 커밋되었는지 확인합니다.
런타임에서는 AJV를 통해 모든 인바운드 프레임을 검증합니다. 특히 서버 핸드셰이크 시 connect 요청의 파라미터가 ConnectParams와 일치하는지 엄격하게 확인하죠.
실습: 새로운 메서드 추가하기
섹션 제목: “실습: 새로운 메서드 추가하기”system.echo라는 새로운 요청 메서드를 추가하는 과정을 단계별로 살펴볼게요.
1. Schema 정의
섹션 제목: “1. Schema 정의”src/gateway/protocol/schema.ts 파일에 요청 파라미터와 결과 스키마를 추가합니다.
export const SystemEchoParamsSchema = Type.Object( { text: NonEmptyString }, { additionalProperties: false },);
export const SystemEchoResultSchema = Type.Object( { ok: Type.Boolean(), text: NonEmptyString }, { additionalProperties: false },);
// ProtocolSchemas에 등록하고 타입을 export 하세요2. Validation 설정
섹션 제목: “2. Validation 설정”src/gateway/protocol/index.ts에서 AJV 검증기를 export 합니다.
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);3. Server 핸들러 구현
섹션 제목: “3. Server 핸들러 구현”src/gateway/server-methods/system.ts에 실제 동작을 작성합니다.
export const systemHandlers: GatewayRequestHandlers = { "system.echo": ({ params, respond }) => { const text = String(params.text ?? ""); respond(true, { ok: true, text }); },};마지막으로 pnpm protocol:check를 실행해 변경 사항을 반영하고 Swift 모델을 재생성하면 끝입니다!
문제 해결
섹션 제목: “문제 해결”- 프로토콜 버전 불일치: 클라이언트가 보낸
minProtocol과maxProtocol범위 안에 서버 버전이 없으면 연결이 거부됩니다. - 알 수 없는 프레임: Swift 모델은 하위 호환성을 위해 알 수 없는 프레임 타입을
unknown케이스로 유지하며 raw payload를 보존합니다.
다음 단계
섹션 제목: “다음 단계”더 자세한 설정이나 구현 방법이 궁금하다면 AI Setup Assistant에게 질문해 보세요!
OpenClaw Expert
아직 막혀 있나요?
이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.