[번역] Node.js에서 gRPC 사용하기

Jisu Yuk
12 min readJan 8, 2023

--

gRPC에 대해서 알아보고 Node.js와 TypeScript를 사용하여 gRPC 통신을 사용하는 강타입 기반의 애플리케이션 만들어보기

원문: https://medium.com/better-programming/how-to-use-grpc-with-node-14e073aa1c84

이 글은 gRPC를 사용하여 통신하는 두 개의 Node.js 프로그램을 구성하고 실행하는 흐름으로 진행됩니다. 이 글을 끝까지 읽으면 gRPC의 기본 개념, 사용 방법 및 REST와 다른 점을 이해하게 될 것입니다. 또한, 이 글에서는 강타입을 보장하기 위해 TypeScript를 사용할 것입니다.

가장 중요한 질문에 답하는 것부터 시작하겠습니다. gRPC란 무엇입니까?

(g)RPC 소개

먼저 RPC에 대해서 이해해 보겠습니다.

RPCRemote Procedure Call의 약자이며 한 프로그램이 다른 프로그램의 함수를 실행하는 기능입니다. 이러한 프로그램들은 다른 컴퓨터에 있거나 다른 언어로 작성되었을 수 있습니다.

gRPCGoogle에서 유지보수하는 오픈 소스 RPC 프레임워크이며 여러가지의 가장 인기 있는 언어들과 함께 사용할 수 있습니다.

gRPC는 REST의 대안으로 사용할 수 있습니다. 아래는 세 가지 주요 차이점입니다.

  1. REST 서비스에서는 JSON/XML 형식을 사용하여 통신합니다. gRPC에서는 프로토콜 버퍼 형식이 사용됩니다. 프로토콜 버퍼는 바이너리 형식이며 JSON 또는 XML보다 훨씬 작은 크기를 가질 수 있습니다.
  2. REST API 서버는 클라이언트와 엔드포인트를 공유합니다. gRPC에서 서버는 클라이언트와 함수를 공유합니다.
  3. REST API 오류는 HTTP 상태 코드로 표시됩니다. gRPC는 에러 코드를 사용합니다.

기본적인 이해를 바탕으로 애플리케이션을 구축하여 gRPC가 작동하는 것을 살펴보겠습니다.

예제

기본 인증 서비스를 구축하여 Node.js 애플리케이션에서 TypeScript와 함께 gRPC를 사용하는 방법을 배워봅시다.

서버에는 사용자 로그인에 사용되는 login이라는 메서드가 있습니다. 성공하면 상태가 변경되고 인증 토큰이 생성됩니다.

클라이언트는 이 함수를 호출하고 서버로부터 응답을 받을 수 있습니다.

애플리케이션 초기화

코드 작성을 시작하기 전에 몇 가지 준비가 필요합니다.

  1. 프로토콜 버퍼 컴파일러 설치(Windows / Linux / Mac)
  2. npm init -y를 사용하여 Node.js 애플리케이션을 생성합니다.
  3. npm install typescript — save-dev를 사용한 다음 npx tsc — init을 사용하여 애플리케이션에 TypeScript를 추가합니다.
  4. @grpc/grpc-js, ts-proto 라이브러리를 설치합니다.

규약(Contract)

서버와 클라이언트 간에 통신하려면 몇 가지 규약이 필요합니다. REST를 사용할 때는 강력한 규약이 필요하지 않습니다. 서버와 클라이언트가 이해하는 한 JSON으로 원하는 모든 것을 보낼 수 있습니다.

gRPC를 사용하면 REST와 비교했을 때 그렇게 자유롭지 않습니다. 프로토콜 버퍼를 사용하여 강력한 형식의 규약을 정의해야 합니다. 규약은 특수 언어로 작성되며 .proto 파일에 저장됩니다.

protos라는 규약용 폴더를 만들고 거기에 규약을 넣겠습니다. 우리는 규약을 auth.proto라고 부를 것입니다.

현재 프로젝트의 구조

VS Code로 작성하는 경우 vscode-proto3라는 proto 파일 구문을 강조 표시하기 위한 애드온을 설치하는 것이 유용합니다.

코드

이 신비로운 언어는 어떻게 생겼을까요? 입력된 언어를 알고 있다면 빠르게 이해할 수 있습니다.

처음에는 구문과 패키지 이름을 제공해야 합니다.

syntax = "proto3";
package authPackage;

글을 작성할 당시에는 proto3이 최신 버전의 구문이었습니다. 패키지 이름은 원하는 대로 지정할 수 있습니다.

이제 보낼 수 있는 메시지(규약 타입)를 정의합니다. 로그인을 요청과 결과를 얻어오는 메시지를 별도로 분리해야 합니다. 또한 로그인 시도가 성공했는지 여부를 나타내는 코드도 정의해야 합니다.

enum LoginCode {
SUCCESS = 0;
FAIL = 1;
};

message LoginResult {
LoginCode loginCode = 1;
optional string token = 2;
}

message LoginRequest {
string username = 1;
string password = 2;
}

보시다시피 메시지의 각 필드에는 고유 번호가 있습니다. 인코딩 중에 사용되는 식별자이며 메시지 형식이 사용되고서는 변경하면 안 됩니다.

이제 해당 메시지를 사용할 서비스를 정의할 수 있습니다. 서비스를 정의하는 것은 TypeScript에서 인터페이스를 정의하는 것과 같습니다. “인터페이스”는 .proto 파일에 있고 Node.js 애플리케이션에서 구현됩니다. 우리의 서비스는 매우 간단하며 로그인 기능만 포함합니다.

service AuthService {
rpc login(LoginRequest) returns (LoginResult);
}

auth.proto의 전체 코드는 아래와 같습니다.

syntax = "proto3";
package authPackage;

service AuthService {
rpc login(LoginRequest) returns (LoginResult);
}

enum LoginCode {
SUCCESS = 0;
FAIL = 1;
};

message LoginResult {
LoginCode loginCode = 1;
optional string token = 2;
}

message LoginRequest {
string username = 1;
string password = 2;
}

TypeScript 타입 생성

이제 proto 규약 정의를 끝마쳤기 때문에 이를 TypeScript로 변환해야 Node.js 애플리케이션에서 타입을 사용할 수 있습니다. 이 동작을 자동으로 수행해 주는 ts-proto 라이브러리가 있습니다. ts-proto 플러그인의 protoc 명령을 사용합니다.

>> protoc - plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd - ts_proto_out=. ./protos/auth.proto - ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true

결과적으로 TypeScript로 작성된 메시지 및 서비스 코드가 포함된 auth.ts 파일을 얻게 됩니다.

이제 서버를 만들 준비가 되었습니다!

서버

server.ts 파일을 만들어봅시다. gRPC 서버 정의 코드는 다음과 같습니다.

import { Server, ServerCredentials } from '@grpc/grpc-js';
import { AuthServiceService } from './protos/auth';

const server = new Server();
server.addService(AuthServiceService, { login: login }); // login 은 이후에 정의할 예정입니다.
server.bindAsync('localhost:8080', ServerCredentials.createInsecure(), () => {
server.start();
});

생성된 Server 인스턴스에 추가하려는 서비스를 정의합니다. AuthServiceServicets-proto로 자동 생성되었으며 여기에 추가합니다. 두 번째 인수로는 서비스에서 사용하는 함수에 대한 정의를 전달합니다. 우리는 잠시 후에 login 기능을 정의할 것입니다.

서버를 실행하는 데 필요한 마지막 작업은 서버를 주소에 바인딩하는 것입니다. 예를 들어 localhost에서 포트 8080을 사용한다고 합시다. bindAsync 함수에는 자격 증명도 정의되어 있습니다. HTTPS 연결을 보호하는 데 사용됩니다. 테스트 목적으로 “안전하지 않은” 자격 증명을 사용할 수 있지만 프로덕션 애플리케이션의 경우 실제 인증서를 사용하는 것이 좋습니다.

로그인 구현을 살펴보겠습니다.

import { LoginCode, LoginRequest, LoginResult } from './protos/auth';

const users = [{ id: 0, username: 'admin', password: 'qwerty' }];

const login = (
call: ServerUnaryCall<LoginRequest, LoginResult>,
callback: sendUnaryData<LoginResult>
) => {
const user = users.find(
(user) =>
user.username === call.request.username &&
user.password === call.request.password
);

if (user) {
const result: LoginResult = {
loginCode: LoginCode.SUCCESS,
token: 'RandomSecretToken',
};
callback(null, result);
} else {
const result: LoginResult = {
loginCode: LoginCode.FAIL,
};
callback(null, result);
}
};

우리는 데이터베이스를 사용하지 않고 시스템의 모든 사용자가 포함된 단순한 배열을 사용합니다. 이 예제를 가능한 한 간단하게 만들기 위한 것입니다.

login 함수는 callcallback의 두 가지 인수를 사용합니다. Call은 요청이 정의된 곳이며 클라이언트가 보낸 데이터를 가져올 수 있습니다. 데이터를 다시 클라이언트로 반환하기 위해 Callback이 호출됩니다. callback의 첫 번째 인수는 서비스 오류입니다. 이 예제에서는 오류가 없으므로 null입니다. 두 번째는 실제 결과입니다.

모든 코드가 강력한 타입을 기반으로 작성된 것을 확인할 수 있습니다.

클라이언트

이제 서버가 생겼으니 클라이언트를 구현해 보겠습니다. ts-proto로 자동 생성된 AuthServiceClient를 사용하겠습니다. 클라이언트는 서버와 동일한 주소 및 자격 증명이 필요합니다. 클라이언트를 생성한 후 클라이언트에서 login을 호출할 수 있습니다. 성공하면 서버 측에서 login 함수를 호출합니다!

import { ServiceError, credentials } from '@grpc/grpc-js';
import { AuthServiceClient, LoginRequest, LoginResult } from './protos/auth';

const loginRequest: LoginRequest = {
username: 'admin',
password: 'qwerty',
};

const client = new AuthServiceClient(
'localhost:8080',
credentials.createInsecure()
);
client.login(
loginRequest,
(err: ServiceError | null, response: LoginResult) => {
console.log(JSON.stringify(response));
}
);

npx ts-node file 명령어를 사용하여 server.tsclient.ts를 모두 실행해 봅시다.

유효한 자격 증명으로 예제를 실행하면 다음과 같은 결과가 나타납니다.

그러나 유효하지 않은 것으로 변경하면 다음과 같은 결과를 얻습니다.

클라이언트와 서버가 gRPC와 통신하도록 만들었습니다!

마무리

이 글을 읽은 후에는 gRPC에 익숙해지고 그 강력함을 실감해 보세요. gRPC를 사용하면 강력한 타입의 스키마로 서로 다른 서비스 간에 쉽게 통신할 수 있습니다. 데이터 형식은 JSON보다 효과적으로 압축할 수 있으므로 통신이 더 빨라집니다.

안타깝게도 이 글을 작성하는 시점에는 현재 브라우저에 gRPC가 구현되어 있지 않으므로 내부 서비스 통신에만 사용할 수 있습니다.

gRPC에 대해 탐구해볼 것이 훨씬 더 많습니다. 사용해보고 적용해볼 수 있는지 확인해 보세요. 전체 코드는 아래의 GitHub에서 확인할 수 있습니다.

🚀 한글로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 
Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!

--

--