Input (schema .proto)

Output (GraphQL SDL)

Cosa fa questo strumento

Hai uno schema Protocol Buffers e davanti un gateway GraphQL (o stai per costruirne uno). Strumenti come il rejoiner di Google collegano i message proto a un endpoint GraphQL live a runtime, ma per abbozzare lo schema, fare code review o seminare uno strato di resolver scritto a mano di solito vuoi solo vedere com'è fatto l'SDL. Questo convertitore lo fa nel browser — incolla, copia l'output, mettilo in schema.graphql.

Ogni message diventa un type di GraphQL; ogni enum diventa un enum di GraphQL. I nomi dei campi vengono convertiti da snake_case (convenzione proto) a camelCase (la convenzione codificata nella specifica GraphQL e imposta da ogni linter). Scalari ed enum vengono emessi come non null (String!, OrderStatus!) perché i campi proto3 sul filo hanno sempre un valore — anche quelli non impostati cadono sul valore zero. I messaggi annidati singoli vengono emessi come nullable, in linea con la semantica has-value di proto3.

I campi repeated vengono renderizzati come [T!]! — lista non nulla di elementi non nulli — perché i campi repeated proto3 non sono mai null e non contengono mai voci null. I map richiedono un escamotage: GraphQL non ha un tipo map nativo, quindi map<string, string> su un campo metadata diventa [OrderMetadataEntry!]! con un type OrderMetadataEntry { key: String! value: String! } sintetico, lo stesso pattern usato da Apollo e dalla maggior parte dei gateway proto-verso-GraphQL in produzione. Tutta la conversione gira lato client; nulla del tuo schema esce dalla pagina.

Come si usa

Tre passi. L'output è pronto da incollare in qualsiasi server GraphQL che accetti SDL.

1

Incolla il tuo schema .proto

Butta lo schema nell'editor di sinistra. syntax = "proto3"; in cima è opzionale — il parser gestisce blocchi message annidati, dichiarazioni enum, oneof, map<K, V> e opzioni di campo. Gli import vengono riconosciuti ma saltati, quindi incolla i tipi importati inline se ne dipendi.

La conversione dei nomi di campo è automatica: order_id in .proto diventa orderId in GraphQL. I nomi di tipi ed enum restano in PascalCase. I campi map ricevono un entry type sintetico chiamato <Genitore><Campo>Entry.

2

Leggi l'output

A destra: SDL GraphQL con prima gli enum, poi gli entry types delle map, poi i message in ordine di dichiarazione. I tipi annidati arrivano prima dei loro genitori, così il file si legge dall'alto verso il basso. Mettilo in un file .graphql che il tuo server carica via graphql-tools o buildSchema.

3

Collega i resolver

Lo schema ti dà la forma; i resolver devi comunque scriverli (o generarli dal tuo servizio gRPC). Per un percorso veloce, punta un Apollo Server a questo SDL con resolver stub, poi sostituisci ogni stub con una chiamata al tuo backend gRPC. Aggiusta la nullability se il contratto a runtime si discosta dai default proto3.

Quando fa davvero risparmiare tempo

Avviare un gateway GraphQL sopra un backend gRPC

Il tuo team ha un servizio gRPC e prodotto vuole un endpoint GraphQL per il client web. Ti serve uno schema di partenza da discutere con i frontend prima di scrivere qualunque resolver. Incolli il proto, copi l'SDL, lo metti in un doc — fatto.

Revisionare un cambio di API Protobuf

Un collega di backend ha aggiunto campi a un message. Vuoi vedere come questo cambia la superficie pubblica di GraphQL senza rifare girare l'intera pipeline di codegen. Incolli il nuovo .proto, fai diff dell'output SDL contro lo schema attuale, lasci un commento di review mirato.

Documentazione e discussioni di design

Stai scrivendo un RFC su un nuovo servizio gRPC che prima o poi avrà bisogno di un GraphQL davanti. Mettere entrambe le forme nel documento — proto da una parte, SDL dall'altra — rende il discorso concreto. Questo convertitore ti dà il lato SDL senza tirare su una build.

Migrare da REST o gRPC a GraphQL

Hai ereditato un servizio definito in Protobuf e il nuovo brief di prodotto chiede un'API GraphQL. Genera qui una bozza di schema come punto di partenza per la migrazione e poi itera a mano su nomi di campo, nullability e paginazione.

Domande frequenti

Il mio schema viene mandato da qualche parte?

No. Il parser .proto e l'emettitore SDL girano entrambi come JavaScript nel tuo browser. Apri i DevTools e guarda la scheda Network mentre incolli — zero richieste. Utile quando lo schema include nomi di tipi interni, percorsi di package o roba che non vorresti spedire a un servizio terzo.

Perché gli interi a 64 bit vengono tipizzati come String?

Lo scalare integrato Int di GraphQL è a 32 bit con segno secondo la specifica GraphQL, e non riesce a contenere un int64 o uint64 proto. La via convenzionale è definire uno scalare custom (spesso chiamato BigInt, Long o Int64) e serializzare il valore come stringa. Questo convertitore emette String per tutti i tipi interi a 64 bit così che lo schema sia subito valido; sostituisci quelle occorrenze con il nome del tuo scalare custom una volta definito sul server.

Come vengono gestiti i campi map?

GraphQL non ha un tipo map nativo — non esiste la forma { String: String }. La via standard è srotolare la map in una lista di coppie { key, value }. Quindi map<string, string> metadata = 8; in un message Order diventa metadata: [OrderMetadataEntry!]! con un type OrderMetadataEntry { key: String! value: String! } sintetico. Il nome dell'entry type deriva dal message genitore + nome del campo in PascalCase + Entry, ed è la convenzione usata dalla maggior parte dei gateway proto-verso-GraphQL, incluso rejoiner.

Come viene gestito oneof?

Ogni campo di un oneof viene emesso come un normale campo nullable con un commento che marca il gruppo. GraphQL non ha un concetto nativo di unione discriminata che si mappi pulitamente sull'oneof proto — l'analogo più vicino è un union type custom o una direttiva @oneOf di input (aggiunta di recente alla spec per gli input). Per i tipi di output la maggior parte degli schemi emette ogni caso del oneof come nullable e documenta il vincolo, ed è quello che facciamo qui. Modifica l'output a mano se vuoi union type stretti.

Perché gli scalari sono non null ma i message sono nullable?

Combacia con la semantica proto3. Gli scalari in proto3 sul filo hanno sempre un valore — un campo string non impostato cade su "", un int32 non impostato cade su 0. Non c'è modo di distinguere "impostato al default" da "non impostato" per gli scalari senza usare optional o tipi wrapper. I messaggi annidati singoli, invece, hanno semantica has-value — il field tag può proprio non esserci — quindi mappano naturalmente su campi GraphQL nullable. I campi repeated sono sempre liste non nulle con elementi non nulli, di nuovo coerenti con proto3.

E google.protobuf.Timestamp e gli altri well-known types?

I well-known types vengono emessi come String per default, dato che GraphQL non ha uno scalare DateTime integrato. Gli schemi di produzione di solito definiscono uno scalare custom DateTime (o ISO8601String) e sostituiscono le occorrenze di String dopo la generazione. Stessa storia per google.protobuf.Duration, Any e Value — emetti prima come String, poi sostituisci con scalari custom una volta definiti.

Campi bytes — perché String?

GraphQL non ha uno scalare binario nativo. La codifica convenzionale è base64 in un campo String, ed è quello che usa anche il mapping JSON di proto3 per bytes. Se vuoi una tipizzazione più stretta, definisci uno scalare custom Base64String sul server e sostituisci String su quei campi.

Strumenti correlati

Se lavori con Protobuf, GraphQL e JSON, questi si sposano bene: