입력 (.proto 스키마)

출력 (GraphQL SDL)

이 도구가 하는 일

Protocol Buffers 스키마가 있고 그 앞단에 GraphQL 게이트웨이가 있다고 합시다 (혹은 곧 만들 예정이라거나). Google rejoiner 같은 도구는 런타임에 proto 메시지를 GraphQL 엔드포인트에 연결하지만, 스키마 초안을 잡거나 코드 리뷰를 하거나 손으로 쓴 리졸버 레이어의 출발점을 만드는 단계에서는 보통 SDL이 어떻게 생겼는지만 보고 싶을 때가 많습니다. 이 변환기는 그걸 브라우저 안에서 끝냅니다 — 붙여넣고, 출력 복사하고, schema.graphql에 떨궈 넣으세요.

message는 GraphQL type이 되고, 각 enum은 GraphQL enum이 됩니다. 필드 이름은 snake_case(proto 관례)에서 camelCase(GraphQL 명세에 명시되고 모든 린터가 강제하는 관례)로 변환됩니다. 스칼라와 enum은 non-null(String!, OrderStatus!)로 출력됩니다 — proto3 필드는 와이어 위에서 항상 값을 가지며, 설정되지 않은 경우에도 기본값이 들어가기 때문입니다. 단일 중첩 메시지는 nullable로 출력되며, 이는 proto3의 has-value 의미와 맞아떨어집니다.

repeated 필드는 [T!]! — non-null 요소의 non-null 리스트 — 로 렌더링됩니다. proto3의 repeated 필드는 절대 null이 아니며 null 항목을 담지 않기 때문입니다. map은 우회가 필요합니다: GraphQL에는 네이티브 map 타입이 없어서, metadata 필드의 map<string, string>는 합성 type OrderMetadataEntry { key: String! value: String! }와 함께 [OrderMetadataEntry!]!가 됩니다 — Apollo와 대부분의 운영용 proto-to-GraphQL 게이트웨이가 쓰는 동일한 패턴입니다. 변환은 전부 클라이언트에서 돌고, 스키마는 페이지 밖으로 나가지 않습니다.

사용 방법

세 단계입니다. 출력은 SDL을 받는 어떤 GraphQL 서버에든 바로 붙여 쓸 수 있습니다.

1

.proto 스키마 붙여넣기

왼쪽 에디터에 스키마를 떨어뜨리세요. 맨 위의 syntax = "proto3";는 선택입니다 — 파서는 중첩 message 블록, enum 선언, oneof, map<K, V>, 필드 옵션을 처리합니다. import는 인식하되 건너뛰므로, 의존하는 타입이 있으면 인라인으로 함께 붙여넣어 주세요.

필드명 변환은 자동입니다: .protoorder_id는 GraphQL에서 orderId가 됩니다. 타입과 enum 이름은 PascalCase 그대로입니다. map 필드에는 <부모><필드>Entry라는 합성 entry 타입이 붙습니다.

2

출력 살펴보기

오른쪽: enum을 먼저, 그다음 map entry 타입, 그리고 선언 순서대로의 message가 들어 있는 GraphQL SDL입니다. 중첩 타입이 부모보다 먼저 나오므로 파일은 위에서 아래로 자연스럽게 읽힙니다. 서버가 graphql-tools 또는 buildSchema로 로드하는 .graphql 파일에 떨궈 넣으세요.

3

리졸버 연결

스키마는 모양을 잡아 줄 뿐, 리졸버는 직접 작성해야 합니다 (또는 gRPC 서비스에서 생성). 빠른 길로 가려면 이 SDL과 스텁 리졸버를 Apollo Server에 물리고, 각 스텁을 gRPC 백엔드 호출로 차례차례 바꿔 가세요. 런타임 계약이 proto3 기본과 다르면 nullable 설정을 조정하면 됩니다.

실제로 시간을 아껴 주는 순간

gRPC 백엔드 위에 GraphQL 게이트웨이를 띄우기

팀에는 gRPC 서비스가 있고, 프로덕트는 웹 클라이언트용 GraphQL 엔드포인트를 원합니다. 리졸버를 쓰기 전에 프런트엔드와 논의할 출발점 스키마가 필요하죠. proto 붙여넣고, SDL 복사해서 문서에 떨어뜨리면 끝.

Protobuf API 변경 리뷰

백엔드 동료가 message에 필드를 추가했습니다. 코드 생성 파이프라인 전체를 다시 돌리지 않고도 공개 GraphQL 표면에 어떤 영향이 있는지 보고 싶다면, 새 .proto를 붙여넣고 SDL 출력을 현재 스키마와 diff한 뒤 핵심을 짚는 리뷰 코멘트만 남기면 됩니다.

문서화와 설계 토론

나중에 GraphQL이 앞단에 붙을 새 gRPC 서비스에 대한 RFC를 쓰는 중. 두 모양을 문서에 같이 넣으면 — 한쪽엔 proto, 다른 쪽엔 SDL — 논의가 구체적으로 굴러갑니다. 빌드를 띄우지 않고도 SDL 쪽을 받아낼 수 있습니다.

REST나 gRPC에서 GraphQL로 마이그레이션

Protobuf로 정의된 서비스를 물려받았는데 새 프로덕트 브리프가 GraphQL API를 원한다면, 여기서 초안 스키마를 만들어 마이그레이션의 출발점으로 쓰고, 필드명·nullable·페이지네이션을 손으로 다듬어 가면 됩니다.

자주 묻는 질문

제 스키마가 어딘가로 전송되나요?

아니요. .proto 파서와 SDL 이미터 모두 브라우저 안의 JavaScript로 돕니다. DevTools를 열고 붙여넣는 동안 Network 탭을 보세요 — 요청은 0건입니다. 스키마에 내부 타입 이름, 패키지 경로, 외부 서비스에 보내고 싶지 않은 정보가 들어 있을 때 유용합니다.

64비트 정수가 왜 String으로 나오나요?

GraphQL의 내장 스칼라 IntGraphQL 명세에 따라 부호 있는 32비트라서 proto의 int64uint64를 담을 수 없습니다. 일반적인 우회는 커스텀 스칼라(BigInt, Long, Int64 등으로 부르는)를 정의하고 값을 문자열로 직렬화하는 것입니다. 본 변환기는 스키마가 곧바로 유효하도록 모든 64비트 정수형을 String으로 내보냅니다. 서버에 커스텀 스칼라를 정의하고 나서 해당 자리들을 그 이름으로 바꿔 주세요.

map 필드는 어떻게 처리되나요?

GraphQL에는 네이티브 map 타입이 없습니다 — { String: String } 형태가 존재하지 않죠. 표준 우회는 map을 { key, value } 쌍의 리스트로 풀어내는 것입니다. 그래서 message Ordermap<string, string> metadata = 8;는 합성 type OrderMetadataEntry { key: String! value: String! }와 함께 metadata: [OrderMetadataEntry!]!가 됩니다. entry 타입 이름은 부모 message + 필드명의 PascalCase + Entry로 만드는데, 이건 rejoiner를 포함한 대부분의 proto-to-GraphQL 게이트웨이가 쓰는 관례입니다.

oneof는 어떻게 처리되나요?

oneof의 각 필드는 그룹을 표시하는 주석과 함께 일반적인 nullable 필드로 출력됩니다. GraphQL에는 proto의 oneof에 깔끔하게 대응되는 네이티브 분리 합집합 개념이 없습니다 — 가장 비슷한 건 커스텀 union 타입이나 (최근 input용으로 명세에 추가된) @oneOf 디렉티브입니다. 출력 타입에 대해서는 대부분의 스키마가 oneof 각 케이스를 nullable로 내고 제약은 문서로 보충하는 방식이며, 여기서도 그렇게 합니다. 엄격한 union 타입을 원한다면 출력을 손으로 편집하세요.

왜 스칼라는 non-null인데 message는 nullable인가요?

proto3 의미에 맞춘 것입니다. proto3의 스칼라는 와이어 위에서 항상 값을 갖습니다 — 설정되지 않은 string 필드는 기본값 ""로, int320으로 떨어집니다. optional이나 래퍼 타입을 쓰지 않는 한, 스칼라에서 "기본값으로 설정됨"과 "설정되지 않음"을 구분할 방법이 없습니다. 반면 단일 중첩 메시지는 has-value 의미가 있어 — 필드 태그 자체가 빠질 수도 있어 — 자연스럽게 nullable GraphQL 필드로 매핑됩니다. repeated 필드는 항상 non-null 요소의 non-null 리스트이며, 이것도 proto3와 일치합니다.

google.protobuf.Timestamp 등 well-known 타입은요?

GraphQL에는 내장 DateTime 스칼라가 없으므로 well-known 타입은 기본적으로 String으로 출력됩니다. 운영용 스키마 대부분은 커스텀 DateTime(또는 ISO8601String) 스칼라를 정의하고 생성 후에 String 자리들을 바꿔 줍니다. google.protobuf.Duration, Any, Value도 마찬가지입니다 — 처음엔 String으로 내고, 커스텀 스칼라가 정의되면 갈아 끼우세요.

bytes 필드 — 왜 String인가요?

GraphQL에는 네이티브 바이너리 스칼라가 없습니다. 일반적인 인코딩은 String 필드 안의 base64이고, proto3 JSON 매핑bytes에 같은 방식을 씁니다. 더 엄격한 타입을 원한다면 서버에 Base64String 커스텀 스칼라를 정의하고 해당 필드의 String을 바꿔 주세요.

관련 도구

Protobuf, GraphQL, JSON을 함께 다룬다면 이런 도구들이 잘 어울립니다: