"REST는 여러 아키텍처 양식 가운데 하나다. 대부분의 상황에서 충분히 유용하고 기존 인프라와 잘 맞기 때문에 널리 사용된다. 그러나 일부 작업에는 더 적합한 대안이 있으며, 통신 형태를 무시하고 REST만 고집하면 불필요한 복잡성이 생긴다."
REST보다 다른 방식이 적합한 경우
1. 타입 계약이 중요한 내부 서비스 간 트래픽 → gRPC. 직접 관리하는 두 서비스가 많은 트래픽을 주고받는다면 명확한 타입 계약, 자동 생성되는 호출 코드, 효율적인 이진 직렬화, HTTP/2 다중화가 유리하다. gRPC와 Protobuf는 이러한 요구를 일관되게 해결한다. Kubernetes와 주요 클라우드 환경의 내부 서비스 메시, 대규모 마이크로서비스 환경에서 gRPC가 자주 사용되는 이유다.
2. 다양한 클라이언트가 깊은 스키마에서 서로 다른 필드를 선택하는 경우 → GraphQL. 웹, 모바일, 파트너 앱처럼 여러 클라이언트가 제품, 리뷰, 작성자, 관련 제품 가운데 서로 다른 조합을 요구하면 REST 엔드포인트가 지나치게 늘거나 필요 이상의 데이터를 반환하기 쉽다. GraphQL은 각 클라이언트가 필요한 데이터 형태를 쿼리로 직접 지정하게 한다.
3. 절차나 명령으로 표현하는 편이 자연스러운 작업 → RPC over HTTP. 일부 작업은 리소스에 자연스럽게 대응하지 않는다. 코드 컴파일, 비디오 변환, 프롬프트 완성은 명사형 리소스보다 동사형 작업에 가깝다. 풍부한 페이로드를 받는 POST 동작 엔드포인트인 OpenAI의 /v1/chat/completions도 RPC의 성격을 띤다. 이를 억지로 REST로 설명할 필요는 없다.
gRPC — 타입 계약, 이진 형식, HTTP/2
gRPC에서는 서비스를 Protobuf의 .proto 파일로 정의한다. 도구가 이 정의를 바탕으로 다양한 언어의 타입 안전 클라이언트와 서버 코드를 생성하며, 전송에는 HTTP/2 위의 이진 Protobuf 형식을 사용한다. 주요 장점은 다음과 같다.
- 명확한 타입 계약. 필드가 문자열인지 숫자인지 추측할 필요가 없으며 .proto 파일이 계약을 정의한다.
- 자동 생성되는 코드. Go, Python, Java, TypeScript 클라이언트가 같은 .proto에서 생성되고 동일한 타입 검사를 적용받는다.
- HTTP/2 다중화. 하나의 연결에서 여러 호출을 동시에 처리할 수 있다.
- 양방향 스트리밍. 양쪽에서 스트림을 전송할 수 있으며 WebSocket 프레이밍을 직접 설계하는 것보다 단순하다.
비용도 있다. 이진 형식은 브라우저 개발자 도구에서 바로 읽기 어렵고, CDN 지원이 제한적이어서 gRPC-Web 같은 별도 계층이 필요할 수 있다. .proto 스키마를 발전시키는 과정에도 규율이 필요하다. 내부 서비스 메시에서는 이러한 비용을 감수할 가치가 있지만, 소비자가 다양한 공개 API에는 적합하지 않을 수 있다.
GraphQL — 클라이언트가 선택하는 응답 형태
GraphQL은 클라이언트가 필요한 필드를 정확히 서술한 쿼리를 보내는 단일 엔드포인트를 노출한다. 보통 POST /graphql을 사용한다.
{
user(id: "u_42") {
name
email
orders(limit: 3) {
id
total
items { name }
}
}
}
서버는 각 필드를 독립적으로 해석하고, 클라이언트는 요청한 데이터만 받는다. 주요 장점은 다음과 같다.
- 과다 조회와 부족 조회를 줄인다. 같은 엔드포인트에서 모바일은 작은 페이로드를, 웹 관리자 화면은 더 풍부한 페이로드를 요청할 수 있다.
- 관련 데이터를 한 번의 왕복으로 가져온다. 클라이언트에서 여러 REST 요청을 연쇄적으로 보내는 문제를 줄일 수 있다.
- 강한 타입 계약을 제공한다. GraphQL 스키마가 필드와 관계의 타입을 정의한다.
GraphQL 역시 비용이 작지 않다. 요청마다 쿼리 형태가 달라 캐시하기 어렵고, resolver를 주의 깊게 구현하지 않으면 복잡한 쿼리 하나가 DB에 과도한 부하를 줄 수 있다. 호출마다 요청 형태가 달라지므로 CDN이나 proxy 같은 중간 장치가 최적화할 여지도 줄어든다.
RPC — 작업에 자연스러운 명사가 없을 때
절차 중심의 작업에는 JSON 페이로드를 받는 동작 엔드포인트에 POST를 사용하는 RPC over HTTP가 더 정직한 표현일 수 있다. 예를 들면 POST /complete, POST /transcode, POST /sign이 있다. OpenAI Chat Completions API도 이와 같은 형태다. 이를 억지로 REST라고 부를 이유는 없다.
현대적인 RPC 패턴에는 TypeScript 전 구간에 타입을 연결하는 tRPC, JSON-RPC 2.0, 일반적인 POST with JSON 방식이 있다. 적합한 지점에 RPC를 선택하는 것은 REST를 포기한다는 의미가 아니다. 프로토콜을 트래픽 형태에 맞추는 설계 판단이다.
선택을 위한 기준
- 양쪽의 타입 계약이 중요한 내부 서비스 메시 → gRPC.
- 서로 다른 필드 조합을 요구하는 다양한 클라이언트 → GraphQL.
- 명사형 리소스로 표현하기 어려운 작업 중심 트래픽 → RPC over HTTP.
- 공개 CRUD, 외부 통합, 브라우저 앱 → REST.
- 실시간 양방향 통신 → WebSocket 또는 내부 시스템의 gRPC 스트리밍.
- 서버에서 클라이언트로 보내는 단방향 이벤트 → SSE.
대부분의 운영 시스템은 이 가운데 두세 가지를 함께 사용한다. cwkPippa는 REST + SSE + WebSocket을 조합한다. 일반적인 SaaS는 공개 API에 REST, 풍부한 웹 앱에 GraphQL, 내부 통신에 gRPC를 사용할 수 있다. 여러 방식을 조합하는 것이 자연스러우며, 하나의 방식만 사용하는 시스템이 오히려 드물다.
cwkPippa의 선택 다시 보기
POST /api/council/{id}/finalize)를 둔다. 하나의 방식으로 모든 문제를 해결하려 하지 않고 요구에 맞는 방식을 의도적으로 조합한 설계다.