Modifiche alle API

Deprecazione di GraphQL senza numero di versione

6 min di lettura

Un’API REST può pubblicare /v2/ accanto a /v1/ e lasciare che chi chiama si muova al proprio ritmo. GraphQL ha uno schema a un endpoint, e ogni client, l’app mobile con la build dell’anno scorso e la dashboard interna distribuita stamattina, interroga lo stesso grafo. Non c’è URL da biforcare. Deprecare un campo significa marcarlo come deprecato sul posto, in uno schema da cui tutti già dipendono, il che rende la disciplina diversa da REST anche se il problema di fondo, dire a chi chiama che qualcosa sta per sparire, è lo stesso che deprecazione di API copre in generale.

Come marca GraphQL un campo come deprecato, se non c’è versione da incrementare?

Con la direttiva @deprecated, applicata direttamente al campo:

type Product {
  price: Float @deprecated(reason: "Use priceV2 for multi-currency support.")
  priceV2: Money
}

Il campo resta interrogabile. Non sparisce, non restituisce un 404, non cambia comportamento; porta solo una nota leggibile da macchina che la maggior parte degli strumenti GraphQL, GraphiQL, Apollo Studio, i linter di schema, mostrerà a chiunque navighi lo schema o scriva una query contro di esso. Questo è l’intero meccanismo. Non c’è un endpoint di deprecazione separato, nessun header, nessun documento aggiuntivo richiesto dalla specifica, il che è sia l’attrattiva sia la trappola: la direttiva è facile da aggiungere e facile da ignorare, perché niente costringe un client a guardarla.

Qualcuno vede davvero il motivo della deprecazione?

Solo chi usa lo schema direttamente, tramite introspezione o un editor consapevole dello schema, e questo è un pubblico più piccolo dei lettori abituali di un changelog di API. Un’app mobile costruita contro una query sei mesi fa ha già quella query incorporata nel suo binario; continuerà a chiedere price e continuerà a ricevere una risposta, deprecato o no, finché qualcuno non ricostruisce l’app con il nuovo campo e rilascia un aggiornamento. La direttiva dice a chi scrive codice nuovo di non usare il campo vecchio. Non fa nulla per il client già distribuito e in esecuzione.

MeccanismoChi raggiunge
Direttiva @deprecatedSviluppatrici che navigano lo schema o scrivono nuove query
Fallimenti CI del linter di schemaIl team proprietario del codice client, se ne gestisce uno
Una voce di changelogChiunque la legga, incluso un team client senza linter
Niente (il campo funziona e basta)Un client già costruito che usa il campo vecchio

Un campo deprecato dovrebbe comunque avere una voce di changelog?

Sì, e fa più lavoro della sola direttiva, perché un changelog raggiunge persone che la direttiva non può: un team partner che consuma il grafo senza navigarne lo schema, un client costruito contro una copia cachata dello schema di mesi fa, chiunque se ne accorgerebbe solo leggendo prosa. Changelog di API copre in generale cosa deve una voce a chi chiama; una voce GraphQL deve una cosa che REST raramente deve esplicitare, perché chi chiama REST la deduce dal numero di versione: se il campo vecchio funziona ancora oggi, funziona ancora con un avviso, o ha effettivamente smesso di restituire dati. La direttiva da sola non risponde a nulla di questo per una lettrice che non ha mai aperto lo schema.

Quando è davvero sicuro rimuovere un campo dallo schema?

Solo quando i log delle query mostrano che nessuno lo chiede più, il che è una domanda di utilizzo, non di calendario. Un campo può portare @deprecated per un anno ed essere ancora portante per un client mai ricostruito; rimuoverlo su un calendario fisso, come spesso fa un Sunset REST, rompe quel client senza alcun avviso su cui possa agire, perché GraphQL non gli dà nulla su cui agire oltre alla direttiva che non ha mai letto. Registrate l’utilizzo a livello di campo prima di impegnarvi su una data di rimozione, e trattate qualsiasi conteggio di query diverso da zero come una pausa, non un conto alla rovescia.

Aggiungere un campo comporta lo stesso rischio che in un’API REST?

Meno, per un nuovo campo, perché un client GraphQL riceve solo i campi che chiede esplicitamente. Aggiungere priceV2 accanto a price non può rompere una query esistente nel modo in cui aggiungere un campo a una risposta JSON REST può rompere un deserializzatore rigido, dato che niente costringe il client a richiedere il campo nuovo. Aggiungere un valore a un enum esistente è l’eccezione che vale la pena nominare nello stesso respiro: un client che fa uno switch esaustivo su ogni valore dell’enum, cosa che i linguaggi fortemente tipizzati incoraggiano, si rompe nel momento in cui arriva un valore nuovo, indipendentemente dal fatto che una query lo chiedesse. La sicurezza vale solo per i campi e i membri di union su cui un client sceglie di entrare; non vale per un insieme chiuso che il codice di un client enumera a mano.

Cosa serve a una voce di changelog GraphQL che non serve a una REST?

La forma della query, non solo il nome del campo, perché “il campo price è deprecato” manca del pezzo di cui chi chiama ha davvero bisogno: quali tipi e quali query lo toccano. Una voce utile nomina il tipo, il campo, il campo sostitutivo e, se potete generarlo, le query reali in produzione che ancora richiedono la forma vecchia. Quest’ultimo pezzo, legare l’avviso di deprecazione all’utilizzo reale, è ciò che chi chiama REST ottiene gratis dai log del server su un URL e chi chiama GraphQL no, perché ogni query colpisce lo stesso endpoint indipendentemente da cosa chiede.

Qualcos’altro oltre a un campo può portare la direttiva @deprecated?

I valori enum, usando la stessa direttiva sulla definizione del valore stesso invece che su quella del campo:

enum ShippingMethod {
  STANDARD
  EXPRESS
  OVERNIGHT @deprecated(reason: "Use EXPRESS with priority: true instead.")
}

La specifica definisce @deprecated per esattamente due posizioni, la definizione di un campo o un valore enum, e nient’altro nella release stabile; la deprecazione a livello di argomento e di campo di input esiste solo nel linguaggio delle bozze successive, non in quello che la maggior parte dei server implementa oggi. Un valore enum marcato in questo modo resta un valore legale che un server può ancora restituire o accettare, la stessa promessa di non-rottura che fa un campo deprecato, ed è quello che lo rende sicuro da pubblicare prima di rimuovere davvero il valore.

FAQ

GraphQL supporta qualcosa come un header Sunset per un intero endpoint? No, perché di solito c’è un solo endpoint. Il tempismo della deprecazione vive a livello di campo, nel testo del motivo della direttiva @deprecated e in qualunque changelog o guida alla migrazione un team pubblichi accanto ad esso, non in un header di risposta che un client possa leggere programmaticamente.

Un campo deprecato può essere rimosso e riaggiunto in seguito con un tipo diverso? Solo come nuovo nome di campo. Reintrodurre lo stesso nome di campo con un tipo cambiato è esattamente il breaking change che il ciclo di deprecazione esiste per evitare; date al sostituto un proprio nome, come fa priceV2, e lasciate che il vecchio si estingua completamente prima che il nome sia libero per il riutilizzo.

Il testo del motivo @deprecated dovrebbe linkare alla voce di changelog? Sì, quando gli strumenti dello schema lo supportano. Il campo motivo accetta una stringa semplice, e un URL dentro quella stringa è il percorso più breve da una sviluppatrice che fissa l’output dell’introspezione alla spiegazione più completa che una voce di changelog può dare.

Un cambiamento di schema GraphQL è mai retrocompatibile in un modo in cui REST non lo è? I cambiamenti additivi di campo, sì, per il motivo sopra: i client ottengono solo ciò che chiedono. I nuovi valori enum sono l’eccezione, perché un client che enumera un insieme chiuso può rompersi su un valore che non si aspettava. Le rimozioni e i cambiamenti di tipo sono esattamente rompenti quanto i loro equivalenti REST.


Le affermazioni tecniche di questo articolo non sono state verificate in modo indipendente. Se qualcosa non è corretto, faccelo sapere e lo correggeremo.

Correlati su changeloop: Documentazione per sviluppatori, Strumenti di changelog a confronto

changeloop
Il team che costruisce un changelog che chiude il cerchio. I tuoi utenti chiedono, il tuo team rilascia, chi ha chiesto lo viene a sapere.