gRPC와 REST API 함께 사용하기
문제 상황
내부 마이크로서비스 간 통신을 gRPC로 전환하는 프로젝트를 진행 중이었다. 하지만 외부 클라이언트(웹, 모바일)는 여전히 REST API를 사용해야 했다. 각각 별도로 구현하면 중복 코드가 발생하고 유지보수가 어려워진다.
grpc-gateway 도입
grpc-gateway는 proto 정의에 HTTP 어노테이션을 추가하면 자동으로 REST 프록시를 생성해준다.
syntax = "proto3";
import "google/api/annotations.proto";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {
get: "/v1/users/{user_id}"
};
}
}
message GetUserRequest {
string user_id = 1;
}
구현 과정
- proto 파일에 HTTP 매핑 정의
- protoc로 gRPC 서버 코드와 gateway 코드 생성
- gRPC 서버는 50051 포트, gateway는 8080 포트로 실행
- gateway가 HTTP 요청을 gRPC로 변환해 전달
func main() {
ctx := context.Background()
// gRPC 서버 시작
go startGRPCServer()
// Gateway 시작
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := pb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
if err != nil {
log.Fatal(err)
}
http.ListenAndServe(":8080", mux)
}
결과
하나의 proto 정의로 gRPC와 REST API를 동시에 제공할 수 있게 되었다. 내부 서비스는 gRPC로 빠르게 통신하고, 외부 클라이언트는 익숙한 REST로 접근한다. API 문서도 proto 파일 하나로 관리되어 일관성이 유지된다.
다만 복잡한 HTTP 요청(파일 업로드 등)은 여전히 별도 처리가 필요했다. 모든 경우를 커버하지는 못하지만, 대부분의 CRUD 작업에는 충분히 유용했다.