์ต๊ทผ ์ค์๊ฐ ํ์ ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๋ ๊ฒฝํ์ ํ ๋๋ก ๊ธฐ์กด REST API ๊ธฐ๋ฐ ์ํคํ ์ฒ๋ก๋ ๋ช ๊ฐ์ง ์ฌ๊ฐํ ํ๊ณ์ ๋ํด ์ธ์งํ๊ฒ ๋์์ต๋๋ค.
์ต๊ทผ ์ค์๊ฐ ํ์ ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๋ ๊ฒฝํ์ ํ ๋๋ก ๊ธฐ์กด REST API ๊ธฐ๋ฐ ์ํคํ ์ฒ๋ก๋ ๋ช ๊ฐ์ง ์ฌ๊ฐํ ํ๊ณ์ ๋ํด ์ธ์งํ๊ฒ ๋์์ต๋๋ค.
์ค์๊ฐ ๋ฐ์ดํฐ ๋๊ธฐํ๊ฐ ํ์์ ์ธ ํ์ ๋๊ตฌ์์ ์ด๋ฌํ ํ๊ณ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ง์ ์ ์ธ ์ํฅ์ ์ฃผ์๊ณ , ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋์์ ์ฐพ์์ผ ํ์ต๋๋ค.
REST API๋ ์น ์๋น์ค ๊ฐ๋ฐ์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์ํคํ ์ฒ์ด์ง๋ง, ์ค์๊ฐ ํ์ ์๋น์ค์์๋ ๋ค์๊ณผ ๊ฐ์ ํ๊ณ์ ์ ๋๋ฌ๋์ต๋๋ค.
๋์ ๋คํธ์ํฌ ๋น์ฉ: ๋งค ์์ฒญ๋ง๋ค HTTP ํค๋์ ๊ฐ์ ๋ถ๊ฐ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์ค์ ๋ฐ์ดํฐ ์ธ์๋ ๋ง์ ๋คํธ์ํฌ ํธ๋ํฝ์ด ๋ฐ์ํ์ต๋๋ค. ํนํ ์์ ํฌ๊ธฐ์ ์ค์๊ฐ ์ ๋ฐ์ดํธ๊ฐ ์์ฃผ ๋ฐ์ํ๋ ํ๊ฒฝ์์ ์ด ์ค๋ฒํค๋๋ ๋ฌด์ํ ์ ์์์ต๋๋ค.
์ฐ๊ฒฐ ์ค๋ฒํค๋: REST API๋ ๋งค ์์ฒญ๋ง๋ค ์๋ก์ด ์ฐ๊ฒฐ์ ๋งบ๊ณ ๋๋ ๊ณผ์ ์ด ํ์ํฉ๋๋ค. ์ด๋ฌํ ์ฐ๊ฒฐ ์ค์ ๋น์ฉ์ ์ค์๊ฐ์ฑ์ด ์ค์ํ ํ์ ์๋น์ค์์ ์ฌ๊ฐํ ์ง์ฐ์ ์ด๋ํ์ต๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ค์ ๋จ์ํ ์ต์ ํ๋ก๋ ํด๊ฒฐํ๊ธฐ ์ด๋ ค์ ๊ณ , ๋ณด๋ค ๊ทผ๋ณธ์ ์ธ ์ํคํ ์ฒ ๋ณ๊ฒฝ์ด ํ์ํ์ต๋๋ค.
ํด๊ฒฐ์ฑ ์ ์ฐพ๊ธฐ ์ํด ํ์ ๋๊ตฌ์ธ ์ฌ๋(Slack)์ ํต์ ๋ฐฉ์์ ๋ถ์ํด๋ณด์์ต๋๋ค. ์ฌ๋์ ์น์์ผ์ ๊ธฐ๋ฐ์ผ๋ก JSON ํ ์คํธ ํ๋ ์์ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค.
์ฌ๋์ ํต์ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๋ณด์์ต๋๋ค
์ฌ๋์ ์น์์ผ ํ์ฉ์์ ์๊ฐ์ ๋ฐ๋, ๋ฐ์ดํฐ ์ ์ก ํจ์จ์ ๋ ๋์ด๊ธฐ ์ํด JSON ๋์ ๋ฐ์ด๋๋ฆฌ ํ์์ธ Protocol Buffers๋ฅผ ์ ์ฉํ๊ธฐ๋ก ํ์ต๋๋ค.
์ฌ๋์ ์ ๊ทผ ๋ฐฉ์์์ ์๊ฐ์ ๋ฐ์, ์น์์ผ์ ํต์ ์ฑ๋๋ก ์ฌ์ฉํ๊ณ Protocol Buffers๋ฅผ ๋ฐ์ดํฐ ์ง๋ ฌํ ํ์์ผ๋ก ํ์ฉํ๋ ๊ฒ์ ๊ณ ๋ฏผํด๋ณด๊ณ ํ ์คํธ ํด๋ณด๊ธฐ๋ก ํ์ต๋๋ค.
ํ ์คํธ ํ๋ก์ ํธ์ ๊ตฌ์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค
์ฌ๊ธฐ์ ์ค์ํ ์ ์ ํต์ ์ฑ๋๊ณผ ๋ฐ์ดํฐ ํ์์ ๋ถ๋ฆฌ์ ๋๋ค:
ํต์ ์ฑ๋: ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์๋ ์น์์ผ ์ฐ๊ฒฐ์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ง์์ ์ธ ์ฐ๊ฒฐ์ ์ ์งํ๊ณ ์๋ฐฉํฅ ํต์ ์ด ๊ฐ๋ฅํด์ง๋๋ค.
๋ฐ์ดํฐ ํ์: ์น์์ผ์ ํตํด ์ ์ก๋๋ ๋ฐ์ดํฐ๋ Protocol Buffers๋ก ์ง๋ ฌํ๋ ๋ฐ์ด๋๋ฆฌ ํ์์ ๋๋ค. ์ด๋ JSON๋ณด๋ค ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ ์ก์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
๋ด๋ถ ์๋น์ค ํต์ : ์น์์ผ ์๋ฒ์ ๋ค๋ฅธ ๋ฐฑ์๋ ์๋น์ค ๊ฐ์๋ gRPC๋ฅผ ์ฌ์ฉํ์ฌ ํจ์จ์ ์ผ๋ก ํต์ ํฉ๋๋ค.
์ด ๊ตฌ์กฐ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์ 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;
}์ด ์ํคํ ์ฒ์์ ๋ฐ์ดํฐ๊ฐ ์ด๋ป๊ฒ ํ๋ฅด๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
ํด๋ผ์ด์ธํธ๊ฐ ๋ฉ์์ง๋ฅผ ์ ์กํ๋ ค๋ฉด
์น์์ผ ์๋ฒ๋
์ด ํ๋ฆ์์ ์ค์ํ ์ ์
์ด ์ํคํ ์ฒ๊ฐ ๊ธฐ์กด 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๋ฅผ ๋ฐ์ดํฐ ํ์์ผ๋ก ์ฌ์ฉํ๋ ์๋ก์ด ์ํคํ ์ฒ๋ฅผ ํ ์คํธํด๋ณด์์ต๋๋ค.
์ฌ๋๊ณผ ๊ฐ์ด ๋ฐ์ดํฐ๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋๋ ํ๊ฒฝ์์๋ ์ด ์ํคํ ์ฒ๊ฐ ๋ ๋์ ์ฑ๋ฅ์ ๋ณด์ฌ์ค ์ ์๋ ๊ฒ์ด ๊ฒ์ฆ๋์์ผ๋ฏ๋ก ์ํฉ์ ๋ฐ๋ผ ์ข์ ์ ๋ต์ด ๋๋ฆฌ๋ผ ์๊ฐํฉ๋๋ค.
ํ ์คํธ ํ๋ก์ ํธ๋ ๊นํ๋ธ์์ ํ์ธํ ์ ์์ต๋๋ค.