Questa pagina fornisce spiegazioni più dettagliate per l'elenco di modifiche che provocano un errore e una struttura non ricorrente nella sezione Controllo delle versioni.
Non è sempre chiaro cosa viene considerato come una modifica che provoca un errore (incompatibile). Le linee guida qui dovrebbero essere trattate come indicative anziché come un elenco completo di ogni possibile modifica.
Le regole qui elencate riguardano solo la compatibilità del client. Si prevede che i producer di API siano consapevoli dei propri requisiti in materia di deployment, comprese le modifiche nei dettagli di implementazione.
L'obiettivo generale è evitare che i client subiscano interruzioni a causa dell'aggiornamento di un servizio a una nuova versione secondaria o patch. I tipi di interruzioni presi in considerazione sono:
- Compatibilità sorgente: il codice scritto contro la versione 1.0 non riesce a effettuare la compilazione rispetto alla versione 1.1
- Compatibilità binaria: il codice compilato sulla versione 1.0 non riesce a collegarsi/eseguirsi su una libreria client 1.1. I dettagli esatti dipendono dalla piattaforma client; esistono varianti in situazioni diverse.
- Compatibilità con i cavi: un'applicazione basata sulla versione 1.0 non riesce a comunicare con un server 1.1
- Compatibilità semantica: tutto funziona ma restituisce risultati imprevisti o sorprendenti
In altre parole, i vecchi client devono essere in grado di lavorare su server più recenti all'interno dello stesso numero di versione principale e, quando vogliono eseguire l'aggiornamento a una nuova versione secondaria (ad esempio per sfruttare una nuova funzionalità), devono essere in grado di farlo facilmente.
Nota: quando facciamo riferimento a numeri di versione come v1.1 e v1.0, ci riferiamo a numeri di versione logici che non sono mai concreti. Il loro scopo è unicamente quello di semplificare la descrizione dei cambiamenti.
Oltre alle considerazioni teoriche basate su protocollo, esistono considerazioni pratiche dovute all'esistenza di librerie client che coinvolgono sia codice generato che codice scritto a mano. Ove possibile, testa le modifiche che stai prendendo in considerazione generando nuove versioni delle librerie client e assicurandoti che i test continuino a essere superati.
La discussione riportata di seguito suddivide i messaggi proto in tre categorie:
- Messaggi di richiesta (ad esempio
GetBookRequest
) - Messaggi di risposta (ad esempio
ListBooksResponse
) - Messaggi delle risorse (come
Book
, inclusi eventuali messaggi utilizzati in altri messaggi delle risorse)
Queste categorie hanno regole diverse, in quanto i messaggi di richiesta vengono inviati solo dal client al server, i messaggi di risposta vengono inviati solo dal server al client, ma in genere i messaggi delle risorse vengono inviati in entrambi i modi. In particolare, le risorse che possono essere aggiornate devono essere considerate come un ciclo di lettura/modifica/scrittura.
Modifiche compatibili con le versioni precedenti (non informative)
Aggiunta di un'interfaccia API a una definizione di servizio API
Dal punto di vista del protocollo, è sempre sicuro. L'unico avvertimento è che le librerie client potrebbero aver già utilizzato il nuovo nome dell'interfaccia dell'API all'interno del codice scritto a mano. È improbabile che la nuova interfaccia sia completamente ortogonale a quelle esistenti; nel caso di una versione semplificata di un'interfaccia esistente, è più probabile che si verifichi un conflitto.
Aggiunta di un metodo a un'interfaccia API
A meno che tu non aggiunga un metodo in conflitto con un metodo già in fase di generazione nelle librerie client, non è un problema.
Esempio in cui potrebbe verificarsi un errore: se hai un metodo GetFoo
, il generatore di codice C# creerà già i metodi GetFoo
e GetFooAsync
. L'aggiunta di un metodo GetFooAsync
nell'interfaccia API rappresenterebbe quindi una modifica che provoca un errore dal punto di vista della libreria client.)
Aggiunta di un'associazione HTTP a un metodo
Supponendo che l'associazione non introduca ambiguità, è sicuro far sì che il server risponda a un URL che in precedenza avrebbe rifiutato. Questa operazione può essere eseguita quando si applica un'operazione esistente a un nuovo pattern del nome della risorsa.
Aggiungere un campo a un messaggio di richiesta
L'aggiunta di campi di richiesta può essere irreversibile, a condizione che i client che non lo specificano, nella nuova versione verranno trattati allo stesso modo della versione precedente.
L'esempio più ovvio di dove questo può essere fatto in modo errato è con l'impaginazione: se la versione 1.0 dell'API non include l'impaginazione per una raccolta, non può essere aggiunta nella v1.1 a meno che il valore predefinito page_size
non venga considerato infinito (che in genere è una cattiva idea). In caso contrario, i client v1.0 che si aspettano di ottenere risultati completi da una singola richiesta potrebbero ricevere risultati troncati, senza consapevolezza che la raccolta contiene più risorse.
Aggiungere un campo a un messaggio di risposta
Un messaggio di risposta che non è una risorsa (ad es. ListBooksResponse
) può essere
espanso senza interrompere i client, purché ciò non modifichi il comportamento
di altri campi di risposta. Tutti i campi compilati in precedenza in una risposta
devono continuare a essere completati con la stessa semantica, anche se questo introduce la
ridondanza.
Ad esempio, una risposta alla query in versione 1.0 potrebbe avere un campo booleano pari a contained_duplicates
per indicare che alcuni risultati sono stati omessi a causa di duplicati. Nella versione 1.1, potremmo fornire informazioni più dettagliate in un campo duplicate_count
. Anche se è ridondante dal punto di vista 1.1, il campo contained_duplicates
deve essere comunque compilato.
Aggiunta di un valore a un'enumerazione
Un'enumerazione utilizzata solo in un messaggio di richiesta può essere espansa liberamente per includere nuovi elementi. Ad esempio, utilizzando il pattern Visualizzazione risorse, è possibile aggiungere una nuova vista in una nuova versione secondaria. I client non devono mai ricevere questa enumerazione, quindi non devono essere a conoscenza di valori che non sono di loro interesse.
Per i messaggi delle risorse e i messaggi di risposta, il presupposto predefinito è che i client debbano gestire valori di enum di cui non sono a conoscenza. Tuttavia, i producer di API devono sapere che scrivere applicazioni per gestire correttamente i nuovi elementi di enum può essere difficile. I proprietari delle API devono documentare il comportamento previsto del client quando incontrano un valore di enumerazione sconosciuto.
Proto3 consente ai client di ricevere un valore di cui non sono a conoscenza e di riutilizzare il messaggio mantenendo lo stesso valore, in modo da non interrompere il ciclo di lettura/modifica/scrittura. Il formato JSON consente l'invio di un valore numerico dove il "nome" del valore è sconosciuto, ma in genere il server non sa se il client è effettivamente a conoscenza di un determinato valore. Pertanto, i client JSON potrebbero sapere di aver ricevuto un valore che in precedenza non conoscevano, ma vedranno solo il nome o il numero e non sapranno entrambi. La restituzione dello stesso valore al server in un ciclo di lettura/modifica/scrittura non deve modificare il campo, in quanto il server dovrebbe comprendere entrambi i moduli.
Aggiunta di un campo di risorse solo di output
I campi di un'entità della risorsa forniti solo dal server possono essere aggiunti. Il server può convalidare qualsiasi valore fornito dal client in una richiesta, ma non deve avere esito negativo se il valore viene omesso.
Modifiche incompatibili con le versioni precedenti
Rimozione o ridenominazione di un servizio, un campo, un metodo o un valore di enumerazione
Fondamentalmente, se il codice client può fare riferimento a qualcosa, la sua rimozione o ridenominazione costituisce una modifica che provoca un errore e deve comportare un aumento sostanziale della versione. Il codice che fa riferimento al nome precedente causerà errori in fase di compilazione per alcuni linguaggi (come C# e Java) e potrebbe causare errori in fase di esecuzione o perdita di dati in altri linguaggi. La compatibilità del formato cavo non è pertinente qui.
Modifica di un'associazione HTTP
"Modifica" qui è effettivamente "elimina e aggiungi". Ad esempio, se decidi di supportare davvero PATCH ma la tua versione pubblicata supporta PUT o hai utilizzato il nome del verbo personalizzato sbagliato, puoi aggiungere la nuova associazione, ma non devi rimuovere la vecchia associazione per gli stessi motivi in cui la rimozione di un metodo di servizio costituisce una modifica che provoca un errore.
Modificare il tipo di campo
Anche se il nuovo tipo è compatibile con il formato cavo, questa operazione potrebbe modificare il codice generato per le librerie client e, di conseguenza, deve comportare un notevole aumento della versione. Per i linguaggi compilati di tipo statico, ciò può introdurre facilmente errori in fase di compilazione.
Modificare il formato del nome di una risorsa
Il nome di una risorsa non deve essere modificato; ciò significa che i nomi delle raccolte non possono essere modificati.
A differenza della maggior parte delle modifiche che provocano un errore, questo riguarda anche le versioni principali: se un client può prevedere di utilizzare la versione 2.0 per accedere a una risorsa creata nella versione 1.0 o viceversa, deve essere utilizzato lo stesso nome di risorsa in entrambe le versioni.
In modo più discreto, anche l'insieme di nomi di risorse validi non dovrebbe cambiare, per i seguenti motivi:
- Se diventa più restrittiva, una richiesta che in precedenza sarebbe andata a buon fine ora avrà esito negativo.
- Se diventa meno restrittiva di quanto documentato in precedenza, i clienti che fanno ipotesi basate sulla documentazione precedente potrebbero non essere conformi. È molto probabile che i client memorizzino i nomi delle risorse altrove, in base al set di caratteri consentiti e alla lunghezza del nome. In alternativa, i client potrebbero eseguire correttamente la convalida del nome delle risorse per seguire la documentazione. Ad esempio, Amazon ha dato molti avvisi ai clienti e ha avuto un periodo di migrazione quando ha iniziato a consentire ID risorsa EC2 più lunghi.
Tieni presente che questa modifica potrebbe essere visibile solo nella documentazione di un proto. Pertanto, quando si esamina un CL per individuare eventuali interruzioni, non è sufficiente esaminare le modifiche che non riguardano i commenti.
Modifica del comportamento visibile delle richieste esistenti
I client spesso dipendono dal comportamento e dalla semantica dell'API, anche quando questo comportamento non è esplicitamente supportato o documentato. Pertanto, nella maggior parte dei casi, la modifica del comportamento o della semantica dei dati delle API viene considerata come un'infrazione da parte dei consumatori. Se il comportamento non è nascosto in modo crittografico, dovresti presumere che gli utenti lo abbiano rilevato e dipenderanno da questo.
È anche una buona idea criptare i token di impaginazione per questo motivo (anche quando i dati non sono interessanti), per evitare agli utenti di creare i propri token e che potenzialmente vengano danneggiati quando cambia il comportamento dei token.
Modifica del formato dell'URL nella definizione HTTP
Oltre alle modifiche ai nomi delle risorse elencate sopra, devi prendere in considerazione due tipi di modifiche:
- Nomi di metodo personalizzati: anche se non fanno parte del nome della risorsa, un nome di metodo personalizzato fa parte dell'URL pubblicato dai client REST. La modifica del nome di un metodo personalizzato non dovrebbe interrompere i client gRPC, ma le API pubbliche devono presupporre che abbiano client REST.
- Nomi dei parametri delle risorse: una modifica da
v1/shelves/{shelf}/books/{book}
av1/shelves/{shelf_id}/books/{book_id}
non influisce sul nome della risorsa sostituito, ma potrebbe influire sulla generazione del codice.
Aggiunta di un campo di lettura/scrittura a un messaggio di risorse
I client spesso eseguono operazioni di lettura/modifica/scrittura. La maggior parte dei client non fornisce valori per i campi di cui non sono a conoscenza e, in particolare, proto3 non supporta questa funzionalità. Potresti specificare che eventuali campi mancanti dei tipi di messaggi (anziché i tipi primitivi) indicano che non viene applicato un aggiornamento a questi campi, ma ciò rende più difficile rimuovere esplicitamente questo valore di campo da un'entità.
Semplicemente, i tipi primitivi (inclusi string
e bytes
) non possono essere gestiti in questo modo, poiché nel proto3 non esiste alcuna differenza tra specificare esplicitamente un campo int32
come 0 e non specificarlo affatto.
Se tutti gli aggiornamenti vengono eseguiti utilizzando una maschera di campo, questo non è un problema, in quanto il client non sovrascriverà implicitamente i campi di cui non è a conoscenza. Tuttavia, si tratterebbe di una decisione API insolita: la maggior parte delle API consente aggiornamenti "dell'intera risorsa".