grpc
gRPC는 고성능 원격 프로시저 호출(RPC)을 위해 .proto 파일을 기반으로 서비스 인터페이스와 메시지 타입을 정의합니다. 이 글에서는 gRPC의 proto 파일 작성법과 서비스 정의, 그리고 기본적인 서버 및 클라이언트 코드 구현 예제를 살펴보고, 단방향 RPC 호출 예시와 함께 gRPC의 다양한 통신 방식을 간략히 소개합니다.
gRPC 서비스는 .proto 파일 내에서 정의됩니다. 이 파일은 서비스 인터페이스와 메시지 구조를 명확히 기술하여, 클라이언트와 서버가 동일한 계약을 공유할 수 있도록 합니다. 아래 예시는 간단한 계산 서비스를 정의한 proto 파일입니다.
syntax = "proto3";
package calculator;
// 요청 메시지 정의: 두 개의 정수를 전달
message CalculationRequest {
int32 a = 1;
int32 b = 2;
}
// 응답 메시지 정의: 계산 결과를 전달
message CalculationResponse {
int32 result = 1;
}
// CalculatorService 서비스 정의
service CalculatorService {
// 단방향 RPC: 클라이언트가 요청하면 서버가 단일 응답을 반환
rpc Add(CalculationRequest) returns (CalculationResponse);
}
위 파일에서는 CalculationRequest와 CalculationResponse라는 메시지 타입을 정의하고, CalculatorService라는 서비스를 통해 Add라는 RPC 메서드를 선언합니다. 이처럼 proto 파일은 데이터 형식을 이진(binary) 형태로 직렬화하는 Protocol Buffers를 사용하여, 효율적이고 타입 안전한 통신을 가능하게 합니다.
gRPC에서는 .proto 파일을 바탕으로 각 언어별로 자동 생성되는 스텁을 사용하여, 클라이언트와 서버 코드를 구현합니다. 예를 들어, Node.js 환경에서 gRPC 서버를 구현하는 기본 구조는 다음과 같습니다.
// server.js
const grpc = require('@grpc/grpc-js')
const protoLoader = require('@grpc/proto-loader')
// proto 파일 로드 및 패키지 정의
const packageDefinition = protoLoader.loadSync('calculator.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
})
const calculatorProto = grpc.loadPackageDefinition(packageDefinition).calculator
// RPC 메서드 구현
function add(call, callback) {
const a = call.request.a
const b = call.request.b
const result = a + b
callback(null, { result })
}
// gRPC 서버 생성 및 서비스 등록
function main() {
const server = new grpc.Server()
server.addService(calculatorProto.CalculatorService.service, { Add: add })
const address = '0.0.0.0:50051'
server.bindAsync(address, grpc.ServerCredentials.createInsecure(), () => {
console.log(`gRPC 서버가 ${address}에서 실행 중입니다.`)
server.start()
})
}
main()
// client.js
const grpc = require('@grpc/grpc-js')
const protoLoader = require('@grpc/proto-loader')
// proto 파일 로드
const packageDefinition = protoLoader.loadSync('calculator.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
})
const calculatorProto = grpc.loadPackageDefinition(packageDefinition).calculator
// 클라이언트 생성
const client = new calculatorProto.CalculatorService(
'localhost:50051',
grpc.credentials.createInsecure(),
)
// 단방향 RPC 호출 예시
client.Add({ a: 10, b: 20 }, (error, response) => {
if (!error) {
console.log(`계산 결과: ${response.result}`) // 출력: 계산 결과: 30
} else {
console.error(error)
}
})
이 예시에서는 서버가 Add 메서드를 구현하여, 클라이언트로부터 두 정수를 받아 합계를 계산한 후 응답으로 반환합니다. 클라이언트는 자동 생성된 스텁을 사용해 마치 로컬 함수를 호출하는 것처럼 RPC를 실행합니다.
gRPC는 단순한 요청-응답 방식 외에도 다양한 스트리밍 통신을 지원합니다.
이러한 다양한 통신 방식은, 예를 들어 실시간 데이터 피드, 채팅 애플리케이션, 또는 대용량 데이터 전송 등 복잡한 분산 시스템에서도 높은 유연성과 성능을 제공합니다.
gRPC는 .proto 파일을 통해 서비스와 메시지 구조를 명확하게 정의하고, 이를 기반으로 자동 생성되는 클라이언트와 서버 코드를 사용하여 안정적이고 효율적인 원격 프로시저 호출을 지원합니다. 위에서 살펴본 예시처럼, 단방향 RPC 호출을 통해 간단한 계산 서비스를 구현할 수 있으며, gRPC는 요청-응답 패턴 외에도 다양한 스트리밍 통신 방식을 제공하여, 현대 분산 시스템에서 필수적인 도구로 자리잡고 있습니다. 중급 개발자라면 gRPC의 서비스 정의와 기본 구현 방법을 숙지하고, 실제 프로젝트에 적용하여 확장 가능하고 고성능의 시스템을 구축할 수 있기를 바랍니다.