μ΅κ·Ό μ€μκ° νμ μλΉμ€λ₯Ό κ°λ°νλ κ²½νμ ν λλ‘ κΈ°μ‘΄ REST API κΈ°λ° μν€ν μ²λ‘λ λͺ κ°μ§ μ¬κ°ν νκ³μ λν΄ μΈμ§νκ² λμμ΅λλ€.
μ€μκ° λ°μ΄ν° λκΈ°νκ° νμμ μΈ νμ λꡬμμ μ΄λ¬ν νκ³λ μ¬μ©μ κ²½νμ μ§μ μ μΈ μν₯μ μ£Όμκ³ , μ΄λ₯Ό ν΄κ²°νκΈ° μν λμμ μ°ΎμμΌ νμ΅λλ€.
REST APIλ μΉ μλΉμ€ κ°λ°μ λ리 μ¬μ©λλ μν€ν μ²μ΄μ§λ§, μ€μκ° νμ μλΉμ€μμλ λ€μκ³Ό κ°μ νκ³μ μ λλ¬λμ΅λλ€.
μ΄λ¬ν λ¬Έμ λ€μ λ¨μν μ΅μ νλ‘λ ν΄κ²°νκΈ° μ΄λ €μ κ³ , λ³΄λ€ κ·Όλ³Έμ μΈ μν€ν μ² λ³κ²½μ΄ νμνμ΅λλ€.
ν΄κ²°μ± μ μ°ΎκΈ° μν΄ νμ λκ΅¬μΈ μ¬λ(Slack)μ ν΅μ λ°©μμ λΆμν΄λ³΄μμ΅λλ€. μ¬λμ μΉμμΌμ κΈ°λ°μΌλ‘ λ°μ΄λ리 λ°μ΄ν°λ₯Ό μ£Όκ³ λ°λ λ°©μμ μ¬μ©νκ³ μμμ΅λλ€.
μ¬λμ ν΅μ λ°©μμ λ€μκ³Ό κ°μ νΉμ§μ 보μμ΅λλ€
λ°μ΄λ리 λ°μ΄ν° μ μ‘ λ°©μμ΄ JSON κ°μ ν μ€νΈ κΈ°λ° νμλ³΄λ€ ν¨μ¨μ μ΄λΌλ μ μ΄ ν₯λ―Έλ‘μ μ΅λλ€.
μ¬λμ μ κ·Ό λ°©μμμ μκ°μ λ°μ, μΉμμΌμ ν΅μ μ±λλ‘ μ¬μ©νκ³ Protocol Buffersλ₯Ό λ°μ΄ν° μ§λ ¬ν νμμΌλ‘ νμ©νλ κ²μ κ³ λ―Όν΄λ³΄κ³ ν μ€νΈ ν΄λ³΄κΈ°λ‘ νμ΅λλ€.
ν μ€νΈ νλ‘μ νΈμ κ΅¬μ± μμλ λ€μκ³Ό κ°μ΅λλ€
μ¬κΈ°μ μ€μν μ μ ν΅μ μ±λκ³Ό λ°μ΄ν° νμμ λΆλ¦¬μ λλ€:
μ΄ κ΅¬μ‘°λ λΈλΌμ°μ μμ μ§μ gRPCλ₯Ό μ¬μ©ν μ μλ μ μ½μ μΉμμΌμΌλ‘ μ°ννλ©΄μλ, Protocol Buffersμ ν¨μ¨μ±μ νμ©ν μ μκ² ν΄μ€λλ€.
Protocol Buffersλ ꡬκΈμμ κ°λ°ν μΈμ΄ μ€λ¦½μ μ΄κ³ νλ«νΌ μ€λ¦½μ μΈ μ§λ ¬ν λ©μ»€λμ¦μΌλ‘, λ€μκ³Ό κ°μ νΉμ§μ κ°μ§λλ€
.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 νμΌμ ν΄λΌμ΄μΈνΈμ μλ² μμͺ½μμ 곡μ λμ΄, μΌκ΄λ λ©μμ§ κ΅¬μ‘°λ₯Ό 보μ₯ν©λλ€.
ν΄λΌμ΄μΈνΈμμλ μΉμμΌ μ°κ²°μ μ€μ νκ³ , Protocol Buffersλ₯Ό μ¬μ©νμ¬ λ°μ΄ν°λ₯Ό μ§λ ¬ν/μμ§λ ¬ννλ μ½λλ₯Ό ꡬννμ΅λλ€
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);
}
}
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λ₯Ό ν΅ν΄ λ°±μλ μλΉμ€μ ν΅μ ν©λλ€.
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;
}
μ΄ μν€ν μ²μμ λ°μ΄ν°κ° μ΄λ»κ² νλ₯΄λμ§ μ΄ν΄λ³΄κ² μ΅λλ€.
- Protocol Buffersλ‘ λ©μμ§λ₯Ό μΈμ½λ©
- λ©μμ§ νμ
μλ³μλ₯Ό μΆκ°
- μΉμμΌμ ν΅ν΄ μλ²λ‘ μ μ‘
- λ©μμ§ νμ
μ μλ³
- Protocol Buffersλ‘ λ©μμ§λ₯Ό λμ½λ©
- gRPC ν΄λΌμ΄μΈνΈλ₯Ό ν΅ν΄ λ°±μλ μλΉμ€μ μμ²
- μΉμμΌμ ν΅ν΄ ν΄λΌμ΄μΈνΈμ μ μ‘
μ΄ νλ¦μμ μ€μν μ μ
μ΄ μν€ν μ²κ° κΈ°μ‘΄ REST API λλΉ μ΄λ€ μ΄μ μ κ°μ§λμ§ μ΄ν΄λ³΄μμ΅λλ€.
REST APIλ κ° μμ²λ§λ€ μλ‘μ΄ TCP μ°κ²°μ μ€μ νκ±°λ, keep-aliveλ₯Ό μ¬μ©νλλΌλ μμ²/μλ΅ ν¨ν΄μΌλ‘ μΈν μ€λ²ν€λκ° μμ΅λλ€.
λ¬Όλ‘ κΈ°μ‘΄ HTTP/1.1 νλ‘ν μ½μμλ μ΄λ¬ν μ€λ²ν€λλ₯Ό HTTP/2.0 νλ‘ν μ½λ‘ κ°μ ν μ μμ΅λλ€.
REST API (HTTP/1.1)μ μ°κ²° ν¨ν΄:
μ΄λ¬ν ν¨ν΄μ κ° μμ²λ§λ€ μ΅μ 1λ²μ μ볡 μκ°(RTT)μ΄ νμνλ©°, ν€λ μ€λ³΅κ³Ό κ°μ μ€λ²ν€λκ° λ°μν©λλ€.
WebSocketμ μ°κ²° ν¨ν΄:
λμΌν μ 보λ₯Ό ννν λ, Protocol Buffersλ JSONλ³΄λ€ ν¨μ¨μ μ λλ€.
Protocol Buffersκ° ν¬κΈ°λ₯Ό μ€μ΄λ ν΅μ¬ μ리λ λ€μκ³Ό κ°μ΅λλ€
Protocol Buffersλ λ¨μν λ°μ΄λ리 μ§λ ¬ν(μ: MessagePack, BSON)μ λΉκ΅ν΄λ μ¬λ¬ κ°μ§ μ€μν μ₯μ μ μ 곡ν©λλ€.
Protocol Buffersλ λͺ ννκ² μ μλ μ€ν€λ§λ₯Ό μ¬μ©ν©λλ€.
message Person {
string name = 1;
int32 age = 2;
repeated string hobbies = 3;
}
μ΄ μ€ν€λ§λ
μΌλ°μ μΈ λ°μ΄λ리 μ§λ ¬νλ μ€ν€λ§κ° μκ±°λ λ λͺ μμ μ΄μ΄μ νμ μ€λ₯μ λ²μ νΈνμ± λ¬Έμ κ° λ°μνκΈ° μ½μ΅λλ€.
Protocol Buffersλ μ΅μ νλ μΈμ½λ© μ λ΅μ μ¬μ©ν©λλ€
Protocol Buffersλ μ²μλΆν° νμ νΈνμ±μ κ³ λ €ν μ€κ³λ₯Ό κ°μ§κ³ μμ΅λλ€:
μ΄λ¬ν νΉμ±μ API λ²μ κ΄λ¦¬κ° 볡μ‘ν λκ·λͺ¨ μμ€ν μμ νΉν μ€μν©λλ€.
REST APIμ νκ³λ₯Ό 극볡νκΈ° μν΄ μΉμμΌμ ν΅μ μ±λλ‘, Protocol Buffersλ₯Ό λ°μ΄ν° νμμΌλ‘ μ¬μ©νλ μλ‘μ΄ μν€ν μ²λ₯Ό ν μ€νΈν΄λ³΄μμ΅λλ€.
μ¬λκ³Ό κ°μ΄ λ°μ΄ν°κ° μμ£Ό λ³κ²½λλ νκ²½μμλ μ΄ μν€ν μ²κ° λ λμ μ±λ₯μ 보μ¬μ€ μ μλ κ²μ΄ κ²μ¦λμμΌλ―λ‘ μν©μ λ°λΌ μ’μ μ λ΅μ΄ λλ¦¬λΌ μκ°ν©λλ€.
ν μ€νΈ νλ‘μ νΈλ κΉνλΈμμ νμΈν μ μμ΅λλ€.