Controllo delle versioni di un'API REST

1. Il problema

L'evoluzione di un'API REST è un problema difficile, per il quale sono disponibili molte opzioni. Questo articolo discute alcune di queste opzioni.

2. Cosa c'è nel contratto?

Prima di ogni altra cosa, dobbiamo rispondere a una semplice domanda: qual è il contratto tra l'API e il cliente?

2.1. Gli URI fanno parte del contratto?

Consideriamo prima la struttura URI dell'API REST : fa parte del contratto? I client dovrebbero aggiungere segnalibri, hardcode e generalmente fare affidamento sugli URI dell'API?

In tal caso, l'interazione del Cliente con il Servizio REST non sarebbe più guidata dal Servizio stesso, ma da ciò che Roy Fielding chiama informazioni fuori banda :

Un'API REST deve essere inserita senza alcuna conoscenza preliminare oltre l'URI iniziale (segnalibro) e un insieme di tipi di media standardizzati appropriati per il pubblico previsto ... Un errore qui implica che le informazioni fuori banda guidano l'interazione anziché l'ipertesto.

Quindi chiaramente gli URI non fanno parte del contratto ! Il client dovrebbe conoscere solo un singolo URI, il punto di ingresso all'API. Tutti gli altri URI devono essere rilevati durante l'utilizzo dell'API.

2.2. I tipi di media fanno parte del contratto?

E le informazioni sul tipo di supporto utilizzate per le rappresentazioni delle risorse: queste fanno parte del contratto tra il cliente e il servizio?

Per utilizzare correttamente l'API, il cliente deve avere una conoscenza preliminare di questi tipi di media . In effetti, la definizione di questi tipi di media rappresenta l'intero contratto.

Pertanto, è qui che il servizio REST dovrebbe concentrarsi maggiormente:

Un'API REST dovrebbe dedicare quasi tutto il suo sforzo descrittivo alla definizione dei tipi di media utilizzati per rappresentare le risorse e guidare lo stato dell'applicazione, o nella definizione di nomi di relazione estesa e / o markup abilitato per ipertesto per i tipi di media standard esistenti.

Quindi le definizioni del tipo di supporto fanno parte del contratto e dovrebbero essere a conoscenza del cliente che utilizza l'API. È qui che entra in gioco la standardizzazione.

Ora abbiamo una buona idea di cosa sia il contratto, passiamo a come affrontare effettivamente il problema del controllo delle versioni.

3. Opzioni di alto livello

Discutiamo ora gli approcci di alto livello al controllo delle versioni dell'API REST:

  • Controllo delle versioni URI : versione dello spazio URI utilizzando gli indicatori di versione
  • Controllo delle versioni del tipo di supporto : versione della rappresentazione della risorsa

Quando introduciamo la versione nello spazio URI, le rappresentazioni delle risorse sono considerate immutabili. Pertanto, quando è necessario introdurre modifiche nell'API, è necessario creare un nuovo spazio URI.

Ad esempio, supponiamo che un'API pubblichi le seguenti risorse: utenti e privilegi:

//host/v1/users //host/v1/privileges

Consideriamo ora che un cambiamento radicale nell'API degli utenti richiede l'introduzione di una seconda versione:

//host/v2/users //host/v2/privileges

Quando eseguiamo la versione del tipo di supporto ed estendiamo la lingua, eseguiamo la negoziazione del contenuto in base a questa intestazione. L'API REST utilizza i tipi di media MIME del fornitore personalizzato invece di tipi di media generici come application / json . Eseguiremo la versione di questi tipi di media invece degli URI.

Per esempio:

===> GET /users/3 HTTP/1.1 Accept: application/vnd.myname.v1+json <=== HTTP/1.1 200 OK Content-Type: application/vnd.myname.v1+json { "user": { "name": "John Smith" } }

Possiamo consultare questo articolo "Tipi di supporti personalizzati per API di riposo" per ulteriori informazioni ed esempi su questo argomento.

Ciò che è importante capire qui è che il client non fa supposizioni sulla struttura della risposta oltre a quanto definito nel tipo di supporto.

Questo è il motivo per cui i tipi di media generici non sono ideali. Questi non forniscono informazioni semantiche sufficienti e costringono il client a utilizzare i suggerimenti aggiuntivi per elaborare la rappresentazione effettiva della risorsa.

Un'eccezione a questo è l'utilizzo di un altro modo per identificare in modo univoco la semantica del contenuto, come uno schema XML.

4. Vantaggi e svantaggi

Ora che abbiamo un concetto chiaro di ciò che fa parte del contratto tra il cliente e il servizio, nonché una panoramica di alto livello delle opzioni per la versione dell'API, discutiamo i vantaggi e gli svantaggi di ciascun approccio.

Innanzitutto, l' introduzione di identificatori di versione nell'URI porta a un'impronta URI molto ampia. Ciò è dovuto al fatto che qualsiasi modifica sostanziale in una qualsiasi delle API pubblicate introdurrà un albero di rappresentazioni completamente nuovo per l'intera API. Nel tempo, questo diventa un peso da mantenere e un problema per il cliente, che ora ha più opzioni tra cui scegliere.

Anche gli identificatori di versione nell'URI SONO estremamente rigidi . Non è possibile semplicemente evolvere l'API di una singola risorsa o un piccolo sottoinsieme dell'API complessiva.

Come accennato prima, questo è un approccio tutto o niente. Se una parte dell'API passa alla nuova versione, l'intera API deve spostarsi con essa. Ciò rende anche l'aggiornamento dei client dalla v1 alla v2 un'impresa importante, che porta a aggiornamenti più lenti e periodi di tramonto molto più lunghi per le vecchie versioni.

Anche la memorizzazione nella cache HTTP è una delle principali preoccupazioni quando si tratta di controllo delle versioni.

Dal punto di vista delle cache proxy nel mezzo, ogni approccio presenta vantaggi e svantaggi. Se l'URI è dotato di versione, la cache dovrà conservare più copie di ciascuna risorsa, una per ogni versione dell'API. Ciò carica la cache e riduce la percentuale di riscontri della cache poiché client diversi utilizzeranno versioni diverse.

Inoltre, alcuni meccanismi di invalidazione della cache non funzioneranno più. Se il tipo di supporto è quello con versione, sia il client che il servizio devono supportare l'intestazione HTTP Vary per indicare che sono presenti più versioni memorizzate nella cache.

Dal punto di vista della memorizzazione nella cache del client , tuttavia, la soluzione per la versione del tipo di supporto richiede un po 'più di lavoro rispetto a quella in cui gli URI contengono l'identificatore della versione. Questo perché è semplicemente più facile memorizzare nella cache qualcosa quando la sua chiave è un URL piuttosto che un tipo di supporto.

Terminiamo questa sezione con la definizione di alcuni obiettivi (direttamente da API Evolution):

  • mantenere le modifiche compatibili fuori dai nomi
  • evitare nuove versioni principali
  • apporta modifiche compatibili con le versioni precedenti
  • pensa alla compatibilità in avanti

5. Possibili modifiche all'API

Next, let's consider the types of changes to the REST API – these are introduced here:

  • representation format changes
  • resource changes

5.1. Adding to the Representation of a Resource

The format documentation of the media type should be designed with forward compatibility in mind. Specifically, a client should ignore information that it doesn't understand (which JSON does better than XML).

Now, adding information in the Representation of a resource will not break existing clients if these are correctly implemented.

To continue our earlier example, adding the amount in the representation of the user will not be a breaking change:

{ "user": { "name": "John Smith", "amount": "300" } }

5.2. Removing or Changing an Existing Representation

Removing, renaming or generally restructuring information in the design of existing representations is a breaking change for clients. This is because they already understand and rely on the old format.

This is where Content Negotiation comes in. For such changes, we can add a new vendor MIME media type.

Let's continue with the previous example. Say we want to break the name of the user into firstname and lastname:

===> GET /users/3 HTTP/1.1 Accept: application/vnd.myname.v2+json <=== HTTP/1.1 200 OK Content-Type: application/vnd.myname.v2+json { "user": { "firstname": "John", "lastname": "Smith", "amount": "300" } }

As such, this does represent an incompatible change for the Client – which will have to request the new Representation and understand the new semantics. However, the URI space will remain stable and will not be affected.

5.3. Major Semantic Changes

These are changes in the meaning of the Resources, the relations between them or what the map to in the backend. This kind of changes may require a new media type, or they may require publishing a new, sibling Resource next to the old one and making use of linking to point to it.

While this sounds like using version identifiers in the URI all over again, the important distinction is that the new Resource is published independently of any other Resources in the API and will not fork the entire API at the root.

The REST API should adhere to the HATEOAS constraint. According to this, most of the URIs should be DISCOVERED by Clients, not hardcoded. Changing such an URI should not be considered an incompatible change. The new URI can replace the old one and Clients will be able to re-discover the URI and still function.

It's worth noting however that, while using version identifiers in the URI is problematic for all of these reasons, it is not un-RESTful in any way.

6. Conclusion

This article tried to provide an overview of the very diverse and difficult problem of evolving a REST Service. We discussed the two common solutions, advantages and disadvantages of each one, and ways to reason about these approaches in the context of REST.

L'articolo si conclude sostenendo la seconda soluzione: il controllo delle versioni dei tipi di media mentre si esaminano le possibili modifiche a un'API RESTful.

L'implementazione completa di questo tutorial può essere trovata nel progetto GitHub.

7. Ulteriori letture

Di solito, queste risorse di lettura sono collegate in tutto l'articolo, ma in questi casi ce ne sono semplicemente troppe buone:

    • Le API REST devono essere basate su ipertesto
    • Evoluzione delle API
    • Collegamento per un'API HTTP
    • Strategie di compatibilità