โ† ๋ชจ๋“  ๊ธ€

websocketprotobufgrpc

WebSocket with Protocol Buffers

์ตœ๊ทผ ์‹ค์‹œ๊ฐ„ ํ˜‘์—… ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ–ˆ๋˜ ๊ฒฝํ—˜์„ ํ† ๋Œ€๋กœ ๊ธฐ์กด REST API ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋กœ๋Š” ๋ช‡ ๊ฐ€์ง€ ์‹ฌ๊ฐํ•œ ํ•œ๊ณ„์— ๋Œ€ํ•ด ์ธ์ง€ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2025.03.216 min read

์ตœ๊ทผ ์‹ค์‹œ๊ฐ„ ํ˜‘์—… ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ–ˆ๋˜ ๊ฒฝํ—˜์„ ํ† ๋Œ€๋กœ ๊ธฐ์กด REST API ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋กœ๋Š” ๋ช‡ ๊ฐ€์ง€ ์‹ฌ๊ฐํ•œ ํ•œ๊ณ„์— ๋Œ€ํ•ด ์ธ์ง€ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”๊ฐ€ ํ•„์ˆ˜์ ์ธ ํ˜‘์—… ๋„๊ตฌ์—์„œ ์ด๋Ÿฌํ•œ ํ•œ๊ณ„๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ์ฃผ์—ˆ๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋Œ€์•ˆ์„ ์ฐพ์•„์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

REST API์˜ ๊ทผ๋ณธ์ ์ธ ํ•œ๊ณ„

REST API๋Š” ์›น ์„œ๋น„์Šค ๊ฐœ๋ฐœ์— ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ์•„ํ‚คํ…์ฒ˜์ด์ง€๋งŒ, ์‹ค์‹œ๊ฐ„ ํ˜‘์—… ์„œ๋น„์Šค์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•œ๊ณ„์ ์„ ๋“œ๋Ÿฌ๋ƒˆ์Šต๋‹ˆ๋‹ค.

  • ๋†’์€ ๋„คํŠธ์›Œํฌ ๋น„์šฉ: ๋งค ์š”์ฒญ๋งˆ๋‹ค HTTP ํ—ค๋”์™€ ๊ฐ™์€ ๋ถ€๊ฐ€ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์‹ค์ œ ๋ฐ์ดํ„ฐ ์™ธ์—๋„ ๋งŽ์€ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ž‘์€ ํฌ๊ธฐ์˜ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ ์ด ์˜ค๋ฒ„ํ—ค๋“œ๋Š” ๋ฌด์‹œํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์—ฐ๊ฒฐ ์˜ค๋ฒ„ํ—ค๋“œ: REST API๋Š” ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ์„ ๋งบ๊ณ  ๋Š๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์—ฐ๊ฒฐ ์„ค์ • ๋น„์šฉ์€ ์‹ค์‹œ๊ฐ„์„ฑ์ด ์ค‘์š”ํ•œ ํ˜‘์—… ์„œ๋น„์Šค์—์„œ ์‹ฌ๊ฐํ•œ ์ง€์—ฐ์„ ์ดˆ๋ž˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋“ค์€ ๋‹จ์ˆœํ•œ ์ตœ์ ํ™”๋กœ๋Š” ํ•ด๊ฒฐํ•˜๊ธฐ ์–ด๋ ค์› ๊ณ , ๋ณด๋‹ค ๊ทผ๋ณธ์ ์ธ ์•„ํ‚คํ…์ฒ˜ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

์Šฌ๋ž™์—๊ฒŒ ๋ฐฐ์šฐ๊ธฐ

ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ๊ธฐ ์œ„ํ•ด ํ˜‘์—… ๋„๊ตฌ์ธ ์Šฌ๋ž™(Slack)์˜ ํ†ต์‹  ๋ฐฉ์‹์„ ๋ถ„์„ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์Šฌ๋ž™์€ ์›น์†Œ์ผ“์„ ๊ธฐ๋ฐ˜์œผ๋กœ JSON ํ…์ŠคํŠธ ํ”„๋ ˆ์ž„์„ ์ฃผ๊ณ ๋ฐ›๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์Šฌ๋ž™์˜ ํ†ต์‹  ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๋ณด์˜€์Šต๋‹ˆ๋‹ค

  • ์›น์†Œ์ผ“์„ ํ†ตํ•œ ์ง€์†์ ์ธ ์—ฐ๊ฒฐ ์œ ์ง€
  • JSON ๊ธฐ๋ฐ˜์˜ ๊ตฌ์กฐํ™”๋œ ๋ฉ”์‹œ์ง€ ์ „์†ก
  • ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹  ์ง€์›

์Šฌ๋ž™์˜ ์›น์†Œ์ผ“ ํ™œ์šฉ์—์„œ ์˜๊ฐ์„ ๋ฐ›๋˜, ๋ฐ์ดํ„ฐ ์ „์†ก ํšจ์œจ์„ ๋” ๋†’์ด๊ธฐ ์œ„ํ•ด JSON ๋Œ€์‹  ๋ฐ”์ด๋„ˆ๋ฆฌ ํ˜•์‹์ธ Protocol Buffers๋ฅผ ์ ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์‹œ๋„ํ•ด๋ณด๊ธฐ

์Šฌ๋ž™์˜ ์ ‘๊ทผ ๋ฐฉ์‹์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„, ์›น์†Œ์ผ“์„ ํ†ต์‹  ์ฑ„๋„๋กœ ์‚ฌ์šฉํ•˜๊ณ  Protocol Buffers๋ฅผ ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” ํ˜•์‹์œผ๋กœ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ฏผํ•ด๋ณด๊ณ  ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ํ”„๋กœ์ ํŠธ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค

  • ํด๋ผ์ด์–ธํŠธ: React ๊ธฐ๋ฐ˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
  • ์›น์†Œ์ผ“ ์„œ๋ฒ„: ํด๋ผ์ด์–ธํŠธ์™€ ์ง€์†์ ์ธ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๋Š” Node.js ์„œ๋ฒ„
  • gRPC ์„œ๋ฒ„: ๋‚ด๋ถ€ ์„œ๋น„์Šค ๋ฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋ฒ„

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์ ์€ ํ†ต์‹  ์ฑ„๋„๊ณผ ๋ฐ์ดํ„ฐ ํ˜•์‹์˜ ๋ถ„๋ฆฌ์ž…๋‹ˆ๋‹ค:

  1. ํ†ต์‹  ์ฑ„๋„: ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์—๋Š” ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ง€์†์ ์ธ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๊ณ  ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

  2. ๋ฐ์ดํ„ฐ ํ˜•์‹: ์›น์†Œ์ผ“์„ ํ†ตํ•ด ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” Protocol Buffers๋กœ ์ง๋ ฌํ™”๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์ด๋Š” JSON๋ณด๋‹ค ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ์ „์†ก์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

  3. ๋‚ด๋ถ€ ์„œ๋น„์Šค ํ†ต์‹ : ์›น์†Œ์ผ“ ์„œ๋ฒ„์™€ ๋‹ค๋ฅธ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค ๊ฐ„์—๋Š” gRPC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํšจ์œจ์ ์œผ๋กœ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ตฌ์กฐ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ gRPC๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ์ œ์•ฝ์„ ์›น์†Œ์ผ“์œผ๋กœ ์šฐํšŒํ•˜๋ฉด์„œ๋„, Protocol Buffers์˜ ํšจ์œจ์„ฑ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

Protocol Buffers

Protocol Buffers๋Š” ๊ตฌ๊ธ€์—์„œ ๊ฐœ๋ฐœํ•œ ์–ธ์–ด ์ค‘๋ฆฝ์ ์ด๊ณ  ํ”Œ๋žซํผ ์ค‘๋ฆฝ์ ์ธ ์ง๋ ฌํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค

  1. ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹: .proto ํŒŒ์ผ์— ๋ช…ํ™•ํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  2. ํšจ์œจ์ ์ธ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ธ์ฝ”๋”ฉ: ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ํ˜•์‹๋ณด๋‹ค ํฌ๊ธฐ๊ฐ€ ์ž‘๊ณ  ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋น ๋ฆ…๋‹ˆ๋‹ค.
  3. ์–ธ์–ด ์ค‘๋ฆฝ์„ฑ: ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” ํ…Œ์ŠคํŠธ ํ”„๋กœ์ ํŠธ์— ์‚ฌ์šฉ๋˜๋Š” ๋ฉ”์‹œ์ง€ ํƒ€์ž…์„ ์ •์˜ํ•œ .proto ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

proto
syntax = "proto3";

package user;

service UserService {
  rpc GetUser (UserRequest) returns (User) {}
  rpc ListUsers (Empty) returns (UserList) {}
}

message Empty {}

message UserRequest {
  string id = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  string role = 5;
}

message UserList {
  repeated User users = 1;
}

์ด .proto ํŒŒ์ผ์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์–‘์ชฝ์—์„œ ๊ณต์œ ๋˜์–ด, ์ผ๊ด€๋œ ๋ฉ”์‹œ์ง€ ๊ตฌ์กฐ๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ

ํด๋ผ์ด์–ธํŠธ ์ธก ๊ตฌํ˜„ (React)

ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ณ , Protocol Buffers๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค

typescript
import * as protobuf from "protobufjs";

// ์‚ฌ์šฉ์ž ํƒ€์ž… ์ •์˜
export interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  role: string;
}

// ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ ์—ญ์ง๋ ฌํ™” ํ•จ์ˆ˜
private deserializeMessage(buffer: Uint8Array, messageType: string): any {
  try {
    // Protobuf ๋””์ฝ”๋”ฉ
    const decodedMessage = MessageType.decode(buffer);
    // ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
    return MessageType.toObject(decodedMessage, {
      longs: String,
      enums: String,
      bytes: String,
    });
  } catch (error) {
    console.error(`๋ฉ”์‹œ์ง€ ์—ญ์ง๋ ฌํ™” ์ค‘ ์˜ค๋ฅ˜(${messageType}):`, error);
  }
}
typescript
export class WebSocketClient {
  // getUserById ์š”์ฒญ ์˜ˆ์‹œ
  public getUserById(userId: string): Promise<User> {
    return new Promise((resolve, reject) => {
      if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
        reject(new Error("WebSocket์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค"));
        return;
      }

      const requestType = new Uint8Array([1]); // ์š”์ฒญ ํƒ€์ž… 1 (์œ ์ € ์ •๋ณด ์š”์ฒญ)
      const userIdBytes = new TextEncoder().encode(userId);
      const requestData = new Uint8Array(
        requestType.length + userIdBytes.length
      );
      requestData.set(requestType);
      requestData.set(userIdBytes, requestType.length);

      this.messageCallbacks.set(1, (data) => {
        resolve(data as User);
      });

      this.socket.send(requestData);
    });
  }
}

์„œ๋ฒ„ ๊ตฌํ˜„

์›น์†Œ์ผ“ ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์™€์˜ ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๊ณ , Protocol Buffers๋กœ ์ง๋ ฌํ™”๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ํ•„์š”์— ๋”ฐ๋ผ gRPC๋ฅผ ํ†ตํ•ด ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

typescript
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import * as path from "path";
import * as protobuf from "protobufjs";
import * as WebSocket from "ws";

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  role: string;
}

const PROTO_PATH = path.resolve(__dirname, "../../proto/user.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDefinition);
let protoRoot: protobuf.Root | null = null;

try {
  protoRoot = protobuf.loadSync(PROTO_PATH);
  console.log("Protobuf ์ •์˜๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
} catch (error) {
  console.error("Protobuf ์ •์˜ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜:", error);
}

function serializeMessage(message: any, messageType: string): Buffer {
  try {
    if (!protoRoot) {
      console.warn("Protobuf ์ •์˜๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. JSON์œผ๋กœ ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค.");
      return Buffer.from(JSON.stringify(message), "utf8");
    }

    const MessageType = protoRoot.lookupType("user." + messageType);

    const verificationError = MessageType.verify(message);
    if (verificationError) {
      console.warn(
        `๋ฉ”์‹œ์ง€ ๊ฒ€์ฆ ์˜ค๋ฅ˜ (${messageType}): ${verificationError}. JSON์œผ๋กœ ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค.`
      );
      return Buffer.from(JSON.stringify(message), "utf8");
    }

    const protoMessage = MessageType.create(message);
    const encodedMessage = MessageType.encode(protoMessage).finish();

    return Buffer.from(encodedMessage);
  } catch (error) {
    console.error(`๋ฉ”์‹œ์ง€ ์ง๋ ฌํ™” ์ค‘ ์˜ค๋ฅ˜ (${messageType}):`, error);
    return Buffer.from(JSON.stringify(message), "utf8");
  }
}

function startWebSocketServer() {
  const wss = new WebSocket.Server({ port: 8080 });

  wss.on("connection", (ws) => {
    console.log("์ƒˆ๋กœ์šด WebSocket ์—ฐ๊ฒฐ ์„ค์ •๋จ");

    ws.on("message", async (message) => {
      try {
        const requestBuffer = Buffer.from(message as Buffer);
        const requestType = requestBuffer.readUInt8(0);
        const requestData = requestBuffer.subarray(1); // ๋‚˜๋จธ์ง€๋Š” ์š”์ฒญ ๋ฐ์ดํ„ฐ

        const client = new (proto as any).user.UserService(
          "localhost:50051",
          grpc.credentials.createInsecure()
        );

        let response: Buffer;

        switch (requestType) {
          case 1: // ์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ
            const userId = requestData.toString("utf8");
            console.log(`์‚ฌ์šฉ์ž ID(${userId}) ์ •๋ณด ์š”์ฒญ ์ˆ˜์‹ `);

            const user = await getUserById(client, userId);
            const userBinary = serializeMessage(user, "User");
            response = Buffer.concat([Buffer.from([1]), userBinary]);

            break;

          case 2: // ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์š”์ฒญ
            console.log("์‚ฌ์šฉ์ž ๋ชฉ๋ก ์š”์ฒญ ์ˆ˜์‹ ");

            const userList = await listAllUsers(client);
            const userListBinary = serializeMessage(userList, "UserList");
            response = Buffer.concat([Buffer.from([2]), userListBinary]);

            break;

          default:
            console.warn(`์•Œ ์ˆ˜ ์—†๋Š” ์š”์ฒญ ํƒ€์ž…: ${requestType}`);
            response = Buffer.from([0, 0]);
            break;
        }

        ws.send(response);
      } catch (error) {
        console.error("๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:", error);
        const errorResponse = Buffer.concat([
          Buffer.from([255]), // 255๋Š” ์—๋Ÿฌ ์‘๋‹ต ํƒ€์ž…
          Buffer.from(JSON.stringify({ error: "์š”์ฒญ ์ฒ˜๋ฆฌ ์‹คํŒจ" })),
        ]);
        ws.send(errorResponse);
      }
    });
  });

  console.log("WebSocket ์„œ๋ฒ„๊ฐ€ ํฌํŠธ 8080์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค");
  return wss;
}

๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ดํ•ดํ•˜๊ธฐ

์ด ์•„ํ‚คํ…์ฒ˜์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ๋ฅด๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๋ ค๋ฉด

    • Protocol Buffers๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ธ์ฝ”๋”ฉ
    • ๋ฉ”์‹œ์ง€ ํƒ€์ž… ์‹๋ณ„์ž๋ฅผ ์ถ”๊ฐ€
    • ์›น์†Œ์ผ“์„ ํ†ตํ•ด ์„œ๋ฒ„๋กœ ์ „์†ก
  2. ์›น์†Œ์ผ“ ์„œ๋ฒ„๋Š”

    • ๋ฉ”์‹œ์ง€ ํƒ€์ž…์„ ์‹๋ณ„
    • Protocol Buffers๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋””์ฝ”๋”ฉ
    • gRPC ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ†ตํ•ด ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์— ์š”์ฒญ
    • ์›น์†Œ์ผ“์„ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์— ์ „์†ก

์ด ํ๋ฆ„์—์„œ ์ค‘์š”ํ•œ ์ ์€

  • ํ†ต์‹  ์ฑ„๋„์€ ์›น์†Œ์ผ“์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ง€์†์ ์ธ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๋ฉฐ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ํ˜•์‹์€ Protocol Buffers์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํšจ์œจ์ ์ธ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ง๋ ฌํ™”๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋‚ด๋ถ€ ์„œ๋น„์Šค ํ†ต์‹ ์€ gRPC๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์— ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํŒจํ‚ท ์ˆ˜์ค€ ์ตœ์ ํ™”

์ด ์•„ํ‚คํ…์ฒ˜๊ฐ€ ๊ธฐ์กด REST API ๋Œ€๋น„ ์–ด๋–ค ์ด์ ์„ ๊ฐ€์ง€๋Š”์ง€ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

REST API vs WebSocket

REST API๋Š” ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ์šด TCP ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, keep-alive๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์š”์ฒญ/์‘๋‹ต ํŒจํ„ด์œผ๋กœ ์ธํ•œ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฌผ๋ก  ๊ธฐ์กด HTTP/1.1 ํ”„๋กœํ† ์ฝœ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ HTTP/2.0 ํ”„๋กœํ† ์ฝœ๋กœ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

REST API (HTTP/1.1)์˜ ์—ฐ๊ฒฐ ํŒจํ„ด:

  1. TCP ์—ฐ๊ฒฐ ์„ค์ • (3-way handshake)
  2. HTTP ์š”์ฒญ ์ „์†ก
  3. HTTP ์‘๋‹ต ์ˆ˜์‹ 
  4. ์—ฐ๊ฒฐ ์ข…๋ฃŒ ๋˜๋Š” ์œ ์ง€

์ด๋Ÿฌํ•œ ํŒจํ„ด์€ ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ตœ์†Œ 1๋ฒˆ์˜ ์™•๋ณต ์‹œ๊ฐ„(RTT)์ด ํ•„์š”ํ•˜๋ฉฐ, ํ—ค๋” ์ค‘๋ณต๊ณผ ๊ฐ™์€ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

WebSocket์˜ ์—ฐ๊ฒฐ ํŒจํ„ด:

  1. ์ดˆ๊ธฐ TCP ์—ฐ๊ฒฐ ๋ฐ WebSocket ํ•ธ๋“œ์…ฐ์ดํฌ (ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰)
  2. ์–‘๋ฐฉํ–ฅ ๋ฉ”์‹œ์ง€ ๊ตํ™˜ (๋ณ„๋„์˜ ์—ฐ๊ฒฐ ์„ค์ • ์—†์Œ)
  3. ์„ธ์…˜ ์ข…๋ฃŒ ์‹œ ์—ฐ๊ฒฐ ์ข…๋ฃŒ

JSON vs Protocol Buffers

๋™์ผํ•œ ์ •๋ณด๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ, Protocol Buffers๋Š” JSON๋ณด๋‹ค ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

Protocol Buffers๊ฐ€ ํฌ๊ธฐ๋ฅผ ์ค„์ด๋Š” ํ•ต์‹ฌ ์›๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค

  1. ํ•„๋“œ ํƒœ๊ทธ ์‚ฌ์šฉ: JSON์˜ ๋ฌธ์ž์—ด ํ‚ค ๋Œ€์‹  ์ˆซ์ž ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ๋ฅผ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ฐ€๋ณ€ ๊ธธ์ด ์ •์ˆ˜ ์ธ์ฝ”๋”ฉ(Varint): ์ž‘์€ ์ˆซ์ž๋Š” ์ ์€ ๋ฐ”์ดํŠธ๋ฅผ, ํฐ ์ˆซ์ž๋Š” ๋” ๋งŽ์€ ๋ฐ”์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ฐ„์„ ์ ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.
  3. ๋ฌธ์ž์—ด ํšจ์œจ์„ฑ: ๊ธธ์ด ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด ๊ฒฝ๊ณ„๋ฅผ ๋ช…ํ™•ํžˆ ํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ถˆํ•„์š”ํ•œ ๊ตฌ๋ฌธ ์ƒ๋žต: JSON์˜ ๋”ฐ์˜ดํ‘œ, ์ฝœ๋ก , ์ค‘๊ด„ํ˜ธ, ์‰ผํ‘œ ๋“ฑ์˜ ๊ตฌ๋ฌธ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Protocol Buffers์˜ ์žฅ์ 

Protocol Buffers๋Š” ๋‹จ์ˆœํ•œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ง๋ ฌํ™”(์˜ˆ: MessagePack, BSON)์™€ ๋น„๊ตํ•ด๋„ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

1. ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜ ์„ค๊ณ„

Protocol Buffers๋Š” ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜๋œ ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

proto
message Person {
  string name = 1;
  int32 age = 2;
  repeated string hobbies = 3;
}

์ด ์Šคํ‚ค๋งˆ๋Š”

  • ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ฌธ์„œํ™”
  • ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ณด์žฅ
  • ์ฝ”๋“œ ์ƒ์„ฑ์„ ํ†ตํ•œ ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ ์ œ๊ณต
  • ๋ฒ„์ „ ๊ด€๋ฆฌ ์šฉ์ด์„ฑ

์ผ๋ฐ˜์ ์ธ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ง๋ ฌํ™”๋Š” ์Šคํ‚ค๋งˆ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋œ ๋ช…์‹œ์ ์ด์–ด์„œ ํƒ€์ž… ์˜ค๋ฅ˜์™€ ๋ฒ„์ „ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

2. ํšจ์œจ์ ์ธ ์ธ์ฝ”๋”ฉ ์ „๋žต

Protocol Buffers๋Š” ์ตœ์ ํ™”๋œ ์ธ์ฝ”๋”ฉ ์ „๋žต์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค

  • ํ•„๋“œ ๋ฒˆํ˜ธ ์ฒด๊ณ„: ํ•„๋“œ๋ช… ๋Œ€์‹  ์ˆซ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ฐ„์„ ์ ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ€๋ณ€ ๊ธธ์ด ์ •์ˆ˜(Varint): ์ž‘์€ ์ˆซ์ž๋Š” ์ ์€ ๋ฐ”์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ฐ„์„ ์ ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.

3. ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ๋ณด์žฅ

Protocol Buffers๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ๊ณ ๋ คํ•œ ์„ค๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:

  • ์ƒˆ๋กœ์šด ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋„ ๊ธฐ์กด ํด๋ผ์ด์–ธํŠธ๋Š” ํ•ด๋‹น ํ•„๋“œ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๊ณ„์† ์ž‘๋™
  • ํ•„๋“œ ๋ฒˆํ˜ธ๋Š” ํ•œ ๋ฒˆ ํ• ๋‹น๋˜๋ฉด ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” ๊ทœ์น™
  • ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์•”๋ฌต์ ์œผ๋กœ ์„ ํƒ์ (proto3)์ด๋ฏ€๋กœ ํ•„๋“œ ์ถ”๊ฐ€ยท์ œ๊ฑฐ๊ฐ€ ์ž์œ ๋กœ์›€

์ด๋Ÿฌํ•œ ํŠน์„ฑ์€ API ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•œ ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์—์„œ ํŠนํžˆ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์น˜๋ฉฐ

REST API์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•ด ์›น์†Œ์ผ“์„ ํ†ต์‹  ์ฑ„๋„๋กœ, Protocol Buffers๋ฅผ ๋ฐ์ดํ„ฐ ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ƒˆ๋กœ์šด ์•„ํ‚คํ…์ฒ˜๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์Šฌ๋ž™๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ํ™˜๊ฒฝ์—์„œ๋Š” ์ด ์•„ํ‚คํ…์ฒ˜๊ฐ€ ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ๊ฒ€์ฆ๋˜์—ˆ์œผ๋ฏ€๋กœ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ข‹์€ ์ „๋žต์ด ๋˜๋ฆฌ๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ํ”„๋กœ์ ํŠธ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.