웹 클라이언트에서 gRPC 통신을 할 수 있을까?
브라우저 환경에서 gRPC 클라이언트를 구현하고 백오피스 생산성 높이기
gRPC란?
gRPC(Google Remote Procedure Call)는 네트워크를 통해 다른 서버에 있는 함수를 호출할 수 있게 해주는 기술입니다. 마치 로컬에서 함수를 호출하는 것처럼 원격 서버의 함수를 실행할 수 있기 때문에, 포트원에서는 서비스 간 통신을 위해 gRPC를 적극 활용 중입니다.
- Protobuf 활용: 데이터 교환 형식으로 Protobuf를 사용하며, 데이터를 바이너리 형태로 직렬화하여 XML이나 JSON보다 빠른 성능 제공
- 다양한 언어 지원: Go, Java, Python, Node.js 등 다양한 프로그래밍 언어 지원
클라이언트에서 gRPC를 선택한 이유
백엔드에서 이미 gRPC를 사용하고 있어 기존 인프라를 최대한 활용할 수 있었고, 클라이언트에서 서버 메서드를 로컬 객체처럼 직접 호출할 수 있어 개발 생산성을 크게 향상시킬 수 있다는 점이 매력적이었습니다. 특히 REST API를 추가로 만들거나 수정할 필요 없이 타입 안정성까지 보장받을 수 있다는 것이 큰 장점으로 다가왔어요.
하지만 웹에서 gRPC를 지원하기 위해 일반적으로 사용하는 gRPC-Web 라이브러리는 proxy 서버 구성이 필요하며, 추가적인 인프라 설정이 복잡하다는 단점이 있었습니다. 백오피스에서 간단하게 구현하고자 했기에 이는 큰 장벽처럼 느껴졌는데요.
다행히 우리 회사에는 Pbkit이라는 Protobuf 툴킷을 개발한 동료가 있었습니다. Pbkit은 웹 프론트엔드의 gRPC/Protobuf 사용 문제를 해결하기 위해 만들어진 도구인데요, 이 동료로부터 웹에서 gRPC를 효과적으로 활용할 수 있는 방법에 대한 조언을 얻을 수 있었고, 우리가 gRPC 도입을 결정하는 데 큰 도움이 되었습니다.
백오피스에서 gRPC 요청을 구현하는 방식
1. Pollapo를 통한 스키마 생성
먼저 백엔드 팀이 정의한 gRPC 서비스 스키마를 가져와야 해요. Pollapo는 GitHub 저장소에서 필요한 protobuf(interface) 파일들을 컴파일해주는 패키지 매니저 역할을 합니다.
2. Pbkit으로 Typescript 코드 생성
Pollapo로 가져온 스키마를 Pbkit이 TypeScript 코드로 변환해줍니다. 이때 gRPC 클라이언트를 관리하는 ClientManager도 함께 생성돼요.
workflow에서도 동작할 수 있도록 다음을 개선했어요:
- SSH Key 지원:
--git옵션을 통해 Personal Access Token 대신 SSH Key 사용 가능하도록 변경 - 특정 커밋/브랜치 지원: 특정 레포의 특정 커밋이나 브랜치를 불러올 수 있도록 기능 확장
클라이언트 gRPC 함수 정의 예시:
// 클라이언트에서 간편하게 백엔드 함수를 호출할 수 있다!
export async function getPayments({
request,
appEnvironment,
}: {
request: GetPaymentsRequest
appEnvironment: AppEnvironment
}) {
const serviceClient = clientManager.get(appEnvironment)
const response = await serviceClient.getPayments(request)
return unwrapResponse(response)
}
3. 그런데 문제가 생겼어요 - 서버 함수가 필요했거든요
Pbkit으로 gRPC 클라이언트 코드는 생성했지만, 이걸 웹에서 직접 사용하기에는 여전히 복잡했어요. 프론트엔드에서 gRPC 클라이언트를 직접 다루는 것보다는, 서버 사이드에서 gRPC 호출을 처리하고 프론트엔드에서는 간단한 함수만 호출하는 방식이 더 깔끔할 것 같았거든요.
그래서 Server Functions 기능이 필요했는데, 당시 사용하던 Vite는 이런 기능을 내장하고 있지 않았어요.
빌드 도구 선택의 여정
Vite에 Server Functions 기능을 추가하기 위해 Telefunc 라이브러리를 검토했어요. 하지만 shield() 함수(타입체크 기능)를 사용할 때 재귀 호출 제한을 초과하는 문제가 발생했습니다.
그래서 선택한 라이브러리가 Vinxi였어요. 많은 사람들이 사용하거나 유명한 도구는 아니었지만, Vite와 Nitro를 기반으로 하면서도 Server Functions 기능을 네이티브로 지원하는 빌드 툴이었거든요.
Vinxi의 장점:
- Vite의 장점은 그대로 유지 (Vite와 Nitro를 기반으로 한 빌드 툴)
- Server Functions 기능 내장
- 복잡한 설정 없이 바로 사용 가능
- AWS Lambda 배포를 지원 (백오피스 배포 flow 개선하기 →)
마무리
덕분에 백오피스 개발 생산성이 크게 올라갔습니다. 더 이상 백오피스를 위한 별도 API를 만들지 않아도 되고, 프론트엔드 개발자는 protobuf 스키마를 직접 다루면서 백엔드 로직을 더 잘 이해하게 되었어요.
처음 접하는 라이브러리를 조합해가며 구현하는 것이 복잡했지만, 여기저기 질문해가며 결국 결과물을 얻은게 큰 성과라고 생각합니다. 도움을 청하는 것이 얼마나 중요한지 다시 한번 깨달은 계기가 되었네요.