Le discussioni sul versionamento delle API cominciano quasi sempre dalla parte sbagliata, cioè da dove mettere il numero di versione. È la decisione meno importante dell’intero argomento. Quello che conta è capire quali modifiche richiedono davvero una nuova versione, e la maggior parte dei team sbaglia nella direzione della leggerezza: rilascia qualcosa che ritiene puramente additivo, e un client si rompe.
Il modello mentale utile è questo: la vostra API è una promessa su ciò su cui un chiamante può contare. Una modifica rompe la compatibilità se invalida qualcosa su cui un chiamante ragionevole faceva affidamento, e i chiamanti fanno affidamento su molto più di quanto la vostra documentazione conceda loro.
La modifica che frega tutti: aggiungere un campo a una risposta. È additiva, non può rompere un client scritto bene, e rompe client reali con regolarità, perché alcuni di essi validano le risposte in modo rigido e rifiutano i campi sconosciuti. Se la colpa sia vostra o loro non ha più alcuna importanza nel momento in cui la loro integrazione è ferma e il telefono squilla.
Che cosa rompe davvero la compatibilità
Rompe senza alcun dubbio: rimuovere o rinominare un campo, cambiare il tipo di un campo, aggiungere un parametro obbligatorio alla richiesta, irrigidire la validazione, cambiare il significato di un valore esistente, oppure cambiare un codice di stato su cui un chiamante fa un ramo di logica.
Rompe nella pratica, anche se sembra sicuro: aggiungere un campo a una risposta quando i client validano in modo rigido. Cambiare l’ordine di un array che non era mai stato garantito ma che i chiamanti hanno dato per scontato. Cambiare un messaggio di errore il cui testo qualcuno confronta come stringa. Rendere asincrona un’operazione sincrona.
Davvero sicuro: aggiungere un nuovo parametro di richiesta facoltativo, aggiungere un nuovo endpoint, allentare la validazione, e aggiungere un nuovo valore a un’enumerazione, ma solo se ai client era stato detto fin dall’inizio di aspettarsi valori sconosciuti e potete verificare che li gestiscano.
Lo schema di fondo è che la sicurezza dipende da ciò che i chiamanti fanno davvero, non da ciò che la specifica permette. Se controllate ogni client, potete verificarlo. Se non lo fate, date per scontato che qualcuno faccia affidamento proprio su ciò che ritenevate accessorio, perché è così.
Strategie di versionamento delle API e quanto costano
Versione nell’URL. L’approccio più diffuso, e il suo pregio è di essere evidente: /v1/orders e /v2/orders sono risorse visibilmente diverse. Il costo è che incoraggia salti di versione dell’intera API, per cui una modifica che tocca un solo endpoint si trascina dietro tutto il resto e i client devono migrare in blocco.
Versione in un header. Mantiene stabili gli URL e permette una granularità più fine, al prezzo dell’invisibilità. Nessuno vede la versione in un browser o in una riga di log senza andarla a cercare, e un chiamante che omette l’header riceve il vostro valore predefinito, che è una decisione da prendere in modo consapevole.
Versionamento per data. Un chiamante si fissa su una data e riceve l’API così come si comportava allora. Stripe documenta questo approccio, in cui ogni account ha una versione predefinita che le singole richieste possono sovrascrivere. Offre i passi di migrazione più piccoli possibili e sposta il peso della compatibilità nella vostra base di codice, che ora mantiene trasformazioni tra le versioni.
Nessun versionamento, solo modifiche additive. Praticabile e sottovalutato quando potete impegnarvi seriamente a non rimuovere mai nulla. Il costo sono campi che non potete più cancellare e comportamenti che non potete più correggere, cioè una tassa lenta invece di un conto improvviso.
Non esiste una risposta giusta, solo uno scambio tra il vostro carico di migrazione e quello dei vostri clienti. Il versionamento per data è il più gentile verso i chiamanti e il più costoso da gestire. Il versionamento nell’URL è l’esatto contrario.
Rendere sopportabili le rotture
Prima espandere, poi contrarre. Aggiungete il nuovo campo accanto a quello vecchio. Popolate entrambi. Date ai client il tempo di spostarsi. Rimuovete il vecchio in una versione successiva. Così una modifica che rompe diventa due modifiche sicure, e il passaggio in più vale la pena quasi sempre.
Misurate chi usa che cosa. Non potete ritirare una versione in sicurezza senza sapere chi ci sta ancora sopra. Registrate a ogni richiesta la versione e l’identità del client, in modo che la deprecazione diventi una conversazione basata su prove invece di un annuncio lanciato nel buio.
Annunciate con un meccanismo, non solo con una email. Gli header di risposta Deprecation e Sunset permettono a un client di scoprire la data di ritiro in modo automatico, ed è più probabile che questo venga notato rispetto a un messaggio inviato a un indirizzo che nessuno legge più.
Date una finestra realistica. Le integrazioni sono mantenute da persone che hanno altre priorità, e una scadenza più breve del loro ciclo di rilascio verrà semplicemente mancata. Sei mesi sono la norma per un’API pubblica; meno è ragionevole per un piccolo gruppo di partner noti con cui avete parlato.
Ritirare una vecchia versione
È la misurazione descritta sopra a rendere possibile questo passaggio. Annunciate la data, osservate il traffico calare e contattate per nome i chiamanti che non si sono mossi.
Aspettatevi una coda lunga. Ci saranno integrazioni di cui nessuno nell’azienda cliente ricorda di essere proprietario, e verranno scoperte nel momento in cui spegnete la versione. I brownout aiutano: disattivate brevemente la vecchia versione in orari annunciati prima della data finale, così il guasto avviene mentre qualcuno se lo aspetta e non nel bel mezzo di un periodo intenso.
La posizione onesta è che alcuni chiamanti si muoveranno solo quando la vecchia versione smetterà di funzionare. Mettetelo in conto invece di lasciarvi sorprendere, e assicuratevi che il modo in cui fallisce sia un errore chiaro che spiega che cosa è successo, non un timeout.
La versione di cui non avete bisogno
La maggior parte delle API interne non ha alcun bisogno di versionamento, perché controllate ogni chiamante e potete cambiare entrambi i lati insieme. Aggiungere una negoziazione di versione a un’interfaccia usata da due dei vostri stessi servizi è meccanica che costa qualcosa e non protegge da nulla.
Il punto in cui diventa necessaria è quello in cui non riuscite più a rilasciare tutti i consumatori nello stesso momento, che appartengano a un altro team, a un altro ciclo di rilascio o a un’altra azienda. Quello è il vero innesco, e non ha nulla a che fare con quanto sia pubblica l’API. Le considerazioni di progetto della nostra guida alle API serverless valgono allo stesso modo anche sull’edge.
Mecanik progetta e mantiene API di questo tipo come parte del nostro lavoro di sviluppo software. Lo schema di versione è raramente la decisione interessante; sapere quale dei vostri chiamanti è ancora sulla vecchia lo è sempre.
Domande frequenti
Che cosa conta come breaking change di un’API? Rimuovere o rinominare un campo, cambiare il tipo di un campo, aggiungere un parametro obbligatorio, irrigidire la validazione, cambiare il significato di un valore oppure cambiare un codice di stato su cui i chiamanti fanno un ramo di logica. Rompono anche nella pratica: aggiungere un campo di risposta quando i client validano in modo rigido, cambiare l’ordine di un array che i chiamanti davano per scontato e modificare il testo di un messaggio di errore che qualcuno confronta.
Meglio mettere la versione dell’API nell’URL o in un header? Il versionamento nell’URL è visibile e semplice, ma incoraggia salti dell’intera API e obbliga i client a migrare tutto in una volta. Il versionamento tramite header mantiene stabili gli URL e permette una granularità più fine, ma è invisibile nei log e nei browser e richiede un valore predefinito scelto con cura per i chiamanti che lo omettono. Nessuno dei due è sbagliato; scambiano il vostro carico di migrazione con quello dei vostri clienti.
Che cos’è il versionamento delle API per data? Un chiamante si fissa su una data e riceve l’API così come si comportava allora, come fa Stripe con una versione predefinita per account che le singole richieste possono sovrascrivere. Offre ai client i passi di migrazione più piccoli possibili e sposta il peso della compatibilità nella vostra base di codice, che poi mantiene le trasformazioni tra le versioni.
Quanto deve durare una finestra di deprecazione di un’API? Sei mesi sono la norma per un’API pubblica, e meno è ragionevole per un piccolo gruppo di partner noti con cui avete parlato direttamente. Le integrazioni sono mantenute da persone che hanno altre priorità, quindi una finestra più breve del loro ciclo di rilascio verrà mancata per quanto chiaramente sia stata annunciata.
Le API interne hanno bisogno di versionamento? Di solito no, perché controllate ogni chiamante e potete cambiare entrambi i lati insieme. Il versionamento diventa necessario nel momento in cui non riuscite più a rilasciare tutti i consumatori nello stesso momento, che appartengano a un altro team, ciclo di rilascio o azienda. È quello l’innesco, non il fatto che l’API sia pubblica.
Commenti