서버와 서버가 통신할 때 가장 많이 사용하는 방식은 HTTP 기반 REST API이다. 하지만 서비스가 늘어나고 내부 통신이 복잡해질수록 단순한 JSON 요청·응답 방식은 여러 한계를 드러낸다. 명세 관리의 어려움, 직렬화 비용, 언어 간 인터페이스 불일치 등이 대표적이다. 이러한 문제를 해결하기 위해 등장한 방식이 gRPC다.
gRPC는 RPC(Remote Procedure Call) 프레임워크로, 원격 서버의 함수를 로컬 함수처럼 호출할 수 있도록 추상화한다. HTTP/2 기반 통신, 바이너리 직렬화, 코드 생성 방식을 통해 서버 간 통신을 보다 명확하고 일관되게 만든다. 특히 MSA 환경에서 다수의 서비스가 서로 호출하는 구조라면 gRPC는 충분히 고려해볼 만한 선택지다.
gRPC Proto 정의 방식
gRPC의 출발점은 .proto 파일이다. .proto는 단순한 설정 파일이 아니라 데이터 스키마와 서비스 인터페이스를 동시에 정의하는 명세서다. 메시지는 타입이 명확한 필드들의 집합이며, 각 필드는 고유한 태그 번호를 가진다. 이 태그 번호는 직렬화 시 필드를 식별하는 기준이 되며, 순서가 아닌 번호로 데이터를 매핑한다.
.proto 파일에 정의된 메시지와 서비스는 protoc 컴파일러를 통해 각 언어에 맞는 코드로 생성된다. Java나 Kotlin 기준으로 보면 메시지는 클래스가 되고, 서비스는 Stub 인터페이스가 생성된다. 이 과정에서 개발자는 직렬화나 네트워크 코드를 직접 다루지 않는다. 정의된 인터페이스를 호출하면 내부적으로 바이너리 인코딩과 전송이 처리된다.
Proto3 문법은 간결하고 언어 중립적이다. 기본값 처리, 필드 제거, 확장성 측면에서 장기간 유지보수에 유리하다. 특히 여러 언어를 사용하는 조직이라면 공통 .proto 계약을 중심으로 클라이언트와 서버의 구현을 분리할 수 있다는 점이 가장 큰 장점이다.
gRPC Streaming 구조
gRPC는 HTTP/2 기반으로 동작한다. 이 덕분에 단순한 요청-응답 모델을 넘어 스트리밍 통신을 자연스럽게 지원한다. gRPC 스트리밍은 단일 연결 위에서 여러 메시지를 주고받는 방식이며, 클라이언트 스트리밍, 서버 스트리밍, 양방향 스트리밍을 모두 제공한다.
서버 스트리밍의 경우 클라이언트가 한 번 요청하면 서버는 여러 응답을 순차적으로 전송할 수 있다. 대용량 데이터 처리나 진행 상황 전달에 적합하다. 반대로 클라이언트 스트리밍은 여러 요청을 모아 서버로 전달하고 하나의 응답을 받는 구조다.
중요한 점은 스트리밍 종료를 명확히 처리해야 한다는 것이다. 서버 측에서는 onNext 이후 반드시 onCompleted를 호출해야 하며, 이를 누락하면 클라이언트는 응답이 끝났다고 판단하지 못한다. 이러한 구조는 네트워크 효율을 높이는 대신 명시적인 생명주기 관리가 필요하다는 점을 보여준다.
gRPC Stub 종류와 선택
gRPC Java 클라이언트는 세 가지 Stub 방식을 제공한다. Blocking Stub은 동기 방식으로 호출되며 응답이 올 때까지 현재 스레드를 블로킹한다. 구현이 단순하고 기존 MVC 구조와 잘 어울리지만, 대량 호출 시 스레드 점유에 주의해야 한다.
Async Stub은 콜백 기반 비동기 방식이다. StreamObserver를 통해 응답을 처리하며, gRPC 내부 Executor에서 실행된다. 비동기 처리와 스트리밍에 적합하지만 콜백 구조로 인해 흐름을 추적하기 어려울 수 있다.
Future Stub은 ListenableFuture를 반환한다. 비동기이면서도 then 체이닝과 조합이 가능해 비교적 선언적인 코드 구성이 가능하다. 필요하다면 get 호출로 동기 처리도 가능하다. 어떤 Stub을 선택할지는 호출 빈도, 응답 시간, 스레드 모델에 따라 신중하게 결정해야 한다.
정리하면, gRPC는 단순히 빠른 통신 수단이 아니라 서버 간 계약을 명확히 정의하고 일관된 호출 방식을 제공하는 프레임워크다. .proto 기반 명세, HTTP/2 스트리밍, Stub 추상화라는 특징을 이해한다면 REST가 아닌 또 하나의 선택지로서 충분한 가치를 가진다.