Konwerter Protobuf do GraphQL
Wklej schemat .proto. Otrzymaj GraphQL SDL z type, enum, listami non-null i syntetycznymi typami entry dla pól map — gotowy, by posłużyć za bazę schematu gateway’a.
Wejście (schemat .proto)
Wyjście (GraphQL SDL)
Co robi to narzędzie
Masz schemat Protocol Buffers i przed nim gateway GraphQL (albo zaraz go zbudujesz). Narzędzia takie jak rejoiner od Google w czasie wykonania podpinają wiadomości proto pod żywy endpoint GraphQL, ale do szkicowania schematu, code review albo zasiania ręcznie pisanej warstwy resolverów zwykle chcesz po prostu zobaczyć, jak wygląda SDL. Ten konwerter robi to w przeglądarce — wklej, skopiuj wyjście, wrzuć do schema.graphql.
Każdy message staje się type’em GraphQL; każdy enum staje się enum’em GraphQL. Nazwy pól są konwertowane ze snake_case (konwencja proto) na camelCase (konwencja zapisana w specyfikacji GraphQL i wymuszana przez każdy linter). Skalary i enumy emitowane są jako non-null (String!, OrderStatus!), bo pola proto3 zawsze mają wartość na drucie — nawet nieustawione spadają do wartości zerowej. Pojedyncze, zagnieżdżone wiadomości emitowane są jako nullable, co odpowiada semantyce has-value w proto3.
Pola repeated renderują się jako [T!]! — non-null lista non-null elementów — bo pola repeated w proto3 nigdy nie są nullem i nie zawierają nullowych wpisów. Mapy wymagają obejścia: GraphQL nie ma natywnego typu map, więc map<string, string> w polu metadata staje się [OrderMetadataEntry!]! z syntetycznym type OrderMetadataEntry { key: String! value: String! } — to ten sam wzorzec, którego używa Apollo i większość produkcyjnych gateway’y proto-do-GraphQL. Cała konwersja działa po stronie klienta; nic z twojego schematu nie opuszcza strony.
Jak tego użyć
Trzy kroki. Wyjście jest gotowe do wklejenia w dowolnym serwerze GraphQL akceptującym SDL.
Wklej swój schemat .proto
Wrzuć schemat do edytora po lewej. syntax = "proto3"; na górze jest opcjonalne — parser radzi sobie z zagnieżdżonymi blokami message, deklaracjami enum, oneof, map<K, V> i opcjami pól. Importy są rozpoznawane, ale pomijane, więc jeśli zależysz od jakichś typów, wklej je inline.
Konwersja nazw pól odbywa się automatycznie: order_id w .proto staje się orderId w GraphQL. Nazwy typów i enumów zostają w PascalCase. Pola map dostają syntetyczny typ entry o nazwie <Rodzic><Pole>Entry.
Przeczytaj wyjście
Po prawej: SDL GraphQL — najpierw enumy, potem typy entry dla map, a następnie message w kolejności deklaracji. Typy zagnieżdżone idą przed swoimi rodzicami, więc plik czyta się od góry do dołu. Wrzuć go do pliku .graphql, który twój serwer ładuje przez graphql-tools albo buildSchema.
Podłącz resolvery
Schemat daje ci kształt; resolvery i tak musisz napisać (albo wygenerować z usługi gRPC). Na szybki sposób wskaż Apollo Server na ten SDL z resolverami-zaślepkami, a potem zamieniaj kolejne zaślepki na wywołania backendu gRPC. Skoryguj nullowalność, jeśli kontrakt w runtime różni się od domyślnych ustawień proto3.
Kiedy to naprawdę oszczędza czas
Stawianie gateway’a GraphQL nad backendem gRPC
Twój zespół ma usługę gRPC, a produkt chce endpointu GraphQL dla klienta webowego. Potrzebujesz wyjściowego schematu, żeby pogadać z frontendem zanim zaczniesz pisać resolvery. Wklejasz proto, kopiujesz SDL, wrzucasz do dokumentu — koniec.
Review zmiany API w Protobuf
Backendowy kolega dorzucił pola do message’a. Chcesz zobaczyć, jak to wpłynie na publiczną powierzchnię GraphQL bez przepuszczania od nowa całego pipeline’u codegenu. Wklej nowy .proto, zrób diff wyjścia SDL z aktualnym schematem i zostaw konkretny komentarz w review.
Dokumentacja i dyskusje projektowe
Piszesz RFC o nowej usłudze gRPC, która kiedyś dostanie GraphQL z przodu. Wsadzenie obu form do dokumentu — z jednej strony proto, z drugiej SDL — robi rozmowę konkretną. Tym konwerterem dostajesz stronę SDL bez stawiania builda.
Migracja z REST lub gRPC do GraphQL
Odziedziczyłeś usługę zdefiniowaną w Protobuf, a nowy brief produktowy mówi GraphQL. Wygeneruj tu szkic schematu jako punkt wyjścia migracji, a potem ręcznie iteruj nazwy pól, nullowalność i paginację.
Częste pytania
Czy mój schemat jest gdzieś wysyłany?
Nie. Parser .proto i emiter SDL działają jako JavaScript w twojej przeglądarce. Otwórz DevTools i obserwuj zakładkę Network, kiedy wklejasz — zero zapytań. Przydatne, gdy w schemacie masz wewnętrzne nazwy typów, ścieżki pakietów albo cokolwiek, czego nie chciałbyś wysyłać do zewnętrznego serwisu.
Dlaczego liczby 64-bitowe są typowane jako String?
Wbudowany skalar Int w GraphQL jest 32-bitowy ze znakiem zgodnie z specyfikacją GraphQL i nie pomieści proto-int64 ani uint64. Konwencjonalne obejście to zdefiniowanie własnego skalara (często nazywanego BigInt, Long lub Int64) i serializowanie wartości jako stringa. Ten konwerter wypisuje String dla wszystkich 64-bitowych typów całkowitych, żeby schemat był od razu poprawny; po zdefiniowaniu własnego skalara po stronie serwera podmień te wystąpienia na jego nazwę.
Jak obsługiwane są pola map?
GraphQL nie ma natywnego typu map — nie istnieje forma { String: String }. Standardowe obejście to rozwinięcie mapy w listę par { key, value }. Czyli map<string, string> metadata = 8; w message Order staje się metadata: [OrderMetadataEntry!]! z syntetycznym type OrderMetadataEntry { key: String! value: String! }. Nazwa typu entry jest tworzona z message’a-rodzica + nazwy pola w PascalCase + Entry, i jest to konwencja używana przez większość gateway’y proto-do-GraphQL, w tym rejoiner.
Jak obsługiwany jest oneof?
Każde pole z oneof jest emitowane jako zwykłe pole nullable z komentarzem oznaczającym grupę. GraphQL nie ma natywnego pojęcia unii rozróżnianej, które ładnie odwzorowałoby proto-oneof — najbliższym odpowiednikiem jest własny typ union albo dyrektywa @oneOf dla inputów (świeżo dodana do specyfikacji dla inputów). Dla typów wyjściowych większość schematów po prostu emituje każdy przypadek oneof jako nullable i opisuje ograniczenie w dokumentacji — i my robimy tu to samo. Jeśli chcesz ścisłych typów union, edytuj wyjście ręcznie.
Dlaczego skalary są non-null, a message nullable?
To zgadza się z semantyką proto3. Skalary w proto3 zawsze mają wartość na drucie — nieustawione pole string spada do "", nieustawiony int32 spada do 0. Bez użycia optional czy typów wrapper nie ma sposobu, by dla skalarów odróżnić "ustawione na default" od "nieustawione". Z drugiej strony pojedyncze zagnieżdżone wiadomości mają semantykę has-value — tagu pola może w ogóle nie być — więc naturalnie mapują się na nullable pola GraphQL. Pola repeated zawsze są listami non-null z elementami non-null — i to też jest zgodne z proto3.
A co z google.protobuf.Timestamp i innymi well-known types?
Well-known types są domyślnie emitowane jako String, ponieważ GraphQL nie ma wbudowanego skalara DateTime. Większość produkcyjnych schematów definiuje własny skalar DateTime (lub ISO8601String) i po wygenerowaniu podmienia wystąpienia String. To samo dotyczy google.protobuf.Duration, Any i Value — najpierw wypisz jako String, później podmień na własne skalary, gdy je zdefiniujesz.
Pola bytes — dlaczego String?
GraphQL nie ma natywnego skalara binarnego. Konwencjonalne kodowanie to base64 w polu String, którego używa też JSON-mapping proto3 dla bytes. Jeśli chcesz ściślejszego typowania, zdefiniuj na serwerze własny skalar Base64String i podmień String na tych polach.
Powiązane narzędzia
Jeśli pracujesz z Protobuf, GraphQL i JSON, te narzędzia dobrze się dopełniają: