Back to blog

Protobuf와 gRPC Transcoding으로 REST API·OpenAPI 생성하기

September 16, 2026

Charlie Zhang
2025년 9월 3일

Protobuf로 gRPC·REST API·OpenAPI 통합하기

이번 블로그 시리즈에서는 유용한 툴을 제공하는 MCP(Model Context Protocol) 서버를 구축하는 방법을 살펴봅니다. 처음부터 새로 만드는 대신, 기존 Protocol Buffers(Protobuf)Google의 gRPC Transcoding을 활용합니다.

커스텀 protoc(Protocol Buffer 컴파일러) 플러그인을 만들면 MCP 서버를 자동으로 생성할 수 있습니다. 이를 통해 gRPC 서비스, OpenAPI 스펙, REST API, MCP 서버를 하나의 소스에서 통합적으로 생성할 수 있습니다.

이 시리즈는 총 4편으로 구성됩니다:

  • Part 1 (이번 글): Protobuf에서 gRPC Transcoding을 활용한 REST API 생성
  • Part 2: Protoc 플러그인으로 MCP 서버 자동 생성
  • Part 3: Proto 주석에서 툴 설명 자동 생성 (예정)
  • Part 4: 실제 MCP 툴 운영 경험에서 얻은 인사이트 (예정)

Protobuf 기반 gRPC·REST API·OpenAPI 생성 결과

이 글을 끝까지 따라가면 다음을 얻게 됩니다:

  • 하나의 Protocol Buffer 정의로 gRPC와 REST API를 동시에 생성
  • 자동 OpenAPI 스펙 생성
  • MCP 서버 생성을 위한 기초 (Part 2에서 다룸)

👉 모든 코드는 GitHub 저장소 zhangcz828/proto-to-mcp-tutorial 에서 확인할 수 있습니다.


gRPC Transcoding 실습을 위한 사전 준비 사항

시작하기 전 준비물:

  • Go 1.19+
  • Protocol Buffer 컴파일러(protoc)
  • gRPC와 REST API에 대한 기본 이해

gRPC·REST API·OpenAPI 분리 관리의 문제

초기에는 단순했습니다. 내부 서비스는 gRPC, 외부 클라이언트는 REST API, 그리고 OpenAPI 스펙은 별도 저장소에서 수동 관리했습니다.

하지만 시스템이 커지면서 문제들이 생겼습니다:

  • 코드, 게이트웨이, 문서를 각각 업데이트해야 했고
  • 일관성이 깨지고, 누락이 생기며, 중복 작업이 늘어났습니다.

gRPC Transcoding이란?

gRPC Transcoding은 gRPC 서비스를 REST API로 노출하는 기능입니다. 즉, 클라이언트는 익숙한 HTTP 호출을 사용할 수 있고, 백엔드는 gRPC 로직을 그대로 유지합니다.

이는 Google API annotations라는 특별한 HTTP 어노테이션을 Protobuf 정의에 추가함으로써 가능합니다.

  • 경로, Path 변수, Request Body 등을 지정
  • 동일한 주석을 gRPC Gateway, OpenAPI 생성기, MCP 플러그인이 모두 이해할 수 있음

Protobuf 프로젝트 구조 설정

mkdir proto-to-mcp-tutorial
cd proto-to-mcp-tutorial
mkdir -p {proto,googleapis,generated/{go,openapi},cmd}

구조는 다음과 같습니다:

proto-to-mcp-tutorial/
├── proto/
├── googleapis/
├── generated/
│   ├── go/
│   └── openapi/
└── cmd/

gRPC Gateway와 OpenAPI 생성 의존성 설치

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.24.0

그리고 Google API 어노테이션 파일 다운로드:

mkdir -p googleapis/google/api

curl -o googleapis/google/api/annotations.proto \
https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto

curl -o googleapis/google/api/http.proto \
https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto

Protobuf 정의 작성

proto/bookstore.proto 파일:

syntax = "proto3";

package bookstore.v1;

import "google/api/annotations.proto";

option go_package = "generated/go/bookstore/v1";

service BookstoreService {
    // Get a book by ID
    rpc GetBook(GetBookRequest) returns (Book) {
        option (google.api.http) = {
            get: "/v1/books/{book_id}"
        };
    }
 
    // Create a new book
    rpc CreateBook(CreateBookRequest) returns (Book) {
        option (google.api.http) = {
            post: "/v1/books"
            body: "book"
        };
    }
}

message Book {
    string book_id = 1;
    string title = 2;
    string author = 3;
    int32 pages = 4;
}

message GetBookRequest {
    string book_id = 1;
}

message CreateBookRequest {
    Book book = 1;
}

여기서 google.api.http 어노테이션이 핵심입니다:

  • get: "/v1/books/{book_id}" → GET 요청에서 book_id 경로 변수를 자동 매핑
  • body: "book" → CreateBook 엔드포인트의 HTTP body에 Book 메시지 전체 매핑

Protobuf에서 gRPC·REST API·OpenAPI 코드 생성

export GOOGLEAPIS_DIR=./googleapis

# gRPC 코드
protoc -I${GOOGLEAPIS_DIR} --proto_path=proto \
--go_out=./generated/go  --go_opt paths=source_relative \
--go-grpc_out=./generated/go --go-grpc_opt paths=source_relative \
bookstore.proto

# REST 엔드포인트 (gRPC Gateway)
protoc -I${GOOGLEAPIS_DIR} --proto_path=proto \
--grpc-gateway_out=./generated/go --grpc-gateway_opt paths=source_relative \
bookstore.proto

# OpenAPI 스펙
protoc -I${GOOGLEAPIS_DIR} -I./proto \
--openapi_out=./generated/openapi \
--openapi_opt=fq_schema_naming=true \
--openapi_opt=version="1.0.0" \
--openapi_opt=title="Bookstore API" \
bookstore.proto

👉 하나의 .proto 파일에서 gRPC, REST, OpenAPI를 모두 생성


gRPC 서버 구현

이제 실제 gRPC 서버를 구현해봅시다. cmd/server/main.go 파일을 생성하고 아래와 같이 작성합니다:

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "net/http"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/reflection"
     pb "proto-to-mcp-tutorial/generated/go"
)

type server struct {
    pb.UnimplementedBookstoreServiceServer
    books map[string]*pb.Book
}

func (s *server) GetBook(ctx context.Context, req *pb.GetBookRequest) (*pb.Book, error) {
    book, exists := s.books[req.BookId]
    if !exists {
        return nil, fmt.Errorf("book not found")
    }
    return book, nil
}

func (s *server) CreateBook(ctx context.Context, req *pb.CreateBookRequest) (*pb.Book, error) {
    book := req.Book
    if book.BookId == "" {
        book.BookId = fmt.Sprintf("book-%d", len(s.books)+1)
    }
    s.books[book.BookId] = book
    return book, nil
}

func main() {
    // Initialize server with some sample data
    srv := &server{
        books: map[string]*pb.Book{
            "book-1": {
                BookId: "book-1",
                Title:  "The Go Programming Language",
                Author: "Alan Donovan",
                Pages:  380,
            },
        },
    }
    
    // Start gRPC server
    go func() {
        lis, err := net.Listen("tcp", ":9090")
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterBookstoreServiceServer(s, srv)
        
        reflection.Register(s)
        
        log.Println("gRPC server starting on :9090")
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }()
    
    // Start REST gateway
    ctx := context.Background()
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
    err := pb.RegisterBookstoreServiceHandlerFromEndpoint(ctx, mux, "localhost:9090", opts)
    if err != nil {
        log.Fatalf("failed to register gateway: %v", err)
    }
    
    log.Println("REST server starting on :8080")
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatalf("failed to serve REST: %v", err)
    }
}

gRPC 서버와 REST API 실행 및 테스트

먼저 Go 모듈을 초기화하고 의존성을 정리합니다:

go mod init proto-to-mcp-tutorial && go mod tidy
go run cmd/server/main.go

정상적으로 실행되면 다음과 같은 출력이 보입니다:

gRPC server starting on :9090
REST server starting on :8080

이제 새로운 터미널을 열고(서버는 계속 실행 중이어야 합니다) REST API를 호출해봅니다.

curl -X GET http://localhost:8080/v1/books/book-1

결과는 예상대로 다음과 같습니다:

{
 "bookId": "book-1",
 "title": "The Go Programming Language",
 "author": "Alan Donovan",
 "pages": 380
}

👉 동일한 서비스 로직을 gRPC 또는 HTTP REST API 중 원하는 방식으로 호출할 수 있고, 데이터 모델과 동작은 완전히 동일합니다.


OpenAPI 스펙 확인

생성된 OpenAPI 명세도 확인해봅시다:

cat generated/openapi/openapi.yaml

여기에는 다음이 포함되어 있어야 합니다:

  • /v1/books/{bookId}/v1/books 경로 정의
  • bookstore.v1.Book 스키마 정의
  • GET, POST 메서드 명세

Protobuf 기반 API 통합 결과

축하합니다! 🎉 이제 통합된 API 시스템을 완성했습니다.

  • 단일 소스 오브 트루스(Single Source of Truth): 하나의 proto 파일로 전체 API 정의
  • 이중 인터페이스(Dual Interface): 동일한 서비스를 gRPC와 REST 양쪽에서 접근 가능
  • 자동 문서화(Automatic Documentation): OpenAPI 스펙이 자동으로 생성
  • 싱크 문제 없음(No Sync Issues): 모든 결과물이 같은 소스에서 생성되므로 항상 일관성 유지

최종 아키텍처는 다음과 같은 형태를 띱니다:

Protobuf (.proto)
   ├── gRPC 서비스
   ├── REST API (gRPC Gateway)
   └── OpenAPI 스펙

다음 단계: MCP 서버 자동 생성

이제 Part 2로 넘어가면, 같은 패턴을 확장하여 AI 통합을 위한 MCP 서버 자동 생성을 다룰 예정입니다.
이번에 추가한 HTTP 어노테이션은 MCP 플러그인이 툴의 입력과 출력을 어떻게 구조화해야 하는지 이해하는 데 중요한 역할을 하게 됩니다.


참고 자료

protoc Plugin Development

Google API HTTP Annotations Documentation

Protocol Buffers Documentation

gRPC Gateway GitHub


gRPC Transcoding과 Protobuf FAQ

gRPC Transcoding이란 무엇인가요?

gRPC Transcoding은 gRPC 서비스를 HTTP 기반 REST API로 노출하는 방식입니다. 클라이언트는 익숙한 HTTP 호출을 사용하면서 백엔드는 기존 gRPC 서비스 로직을 유지할 수 있습니다.

하나의 Protobuf 정의로 무엇을 생성할 수 있나요?

이 튜토리얼에서는 하나의 .proto 파일을 기준으로 gRPC 코드, gRPC Gateway 기반 REST 엔드포인트, OpenAPI 스펙을 생성합니다. 각 결과물이 같은 소스를 사용하므로 API 정의의 일관성을 유지할 수 있습니다.

google.api.http 어노테이션은 어떤 역할을 하나요?

google.api.http 어노테이션은 gRPC 메서드를 HTTP 경로와 메서드에 매핑합니다. 경로 변수와 요청 본문을 지정하면 gRPC Gateway와 OpenAPI 생성기가 동일한 매핑 정보를 해석할 수 있습니다.

이 실습을 위해 무엇을 준비해야 하나요?

Go 1.19 이상, Protocol Buffer 컴파일러인 protoc, gRPC와 REST API에 대한 기본 이해가 필요합니다. 본문에 안내된 Go 플러그인과 Google API 어노테이션 파일도 설치하거나 내려받아야 합니다.

생성한 REST API는 어떻게 테스트하나요?

Go 모듈을 초기화하고 서버를 실행한 뒤, 별도의 터미널에서 curl -X GET http://localhost:8080/v1/books/book-1을 호출합니다. 서버가 정상적으로 실행되면 본문에 제시된 JSON 형식의 도서 정보가 반환됩니다.


메일: salesinquiry@enterprisedb.com

Share this