Contratti, indirizzamento e API per i microservizi

ID regione

REGION_ID è un codice abbreviato che Google assegna in base alla regione selezionata al momento della creazione dell'app. Il codice non corrisponde a un paese o a una provincia, anche se alcuni ID regione possono sembrare simili ai codici paese e provincia di uso comune. Per le app create dopo febbraio 2020, REGION_ID.r è incluso negli URL di App Engine. Per le app esistenti create prima di questa data, l'ID regione è facoltativo nell'URL.

Scopri di più sugli ID regione.

I microservizi su App Engine in genere si chiamano reciprocamente utilizzando API RESTful basate su HTTP. È inoltre possibile richiamare i microservizi in background utilizzando le code di attività; si applicano i principi di progettazione delle API qui descritti. È importante seguire determinati pattern per garantire che l'applicazione basata su microservizi sia stabile, sicura e funzioni bene.

Adottare contratti solidi

Uno degli aspetti più importanti delle applicazioni basate su microservizi è la capacità di eseguire il deployment dei microservizi in maniera totalmente indipendente l'uno dall'altro. Per ottenere questa indipendenza, ciascun microservizio deve fornire un contratto ben definito e soggetto al controllo delle versioni ai propri client, che sono altri microservizi. I singoli servizi non devono interrompere questi contratti sottoposti al controllo delle versioni finché non viene appurato che nessun altro microservizio fa affidamento su uno di questi contratti. Tieni presente che altri microservizi potrebbero dover eseguire il rollback a una versione precedente del codice che richiede un contratto precedente, quindi è importante tenerne conto nei criteri di ritiro e disattivazione.

Una cultura incentrata su contratti solidi e sottoposti al controllo delle versioni è probabilmente l'aspetto organizzativo più impegnativo di un'applicazione stabile basata su microservizi. I team di sviluppo devono interiorizzare la comprensione di un cambiamento che provoca un cambiamento e di un cambiamento costante. Devono sapere quando è richiesta una nuova release principale. Devono sapere come e quando un vecchio contratto può essere disattivato. I team devono impiegare tecniche di comunicazione appropriate, compresi il ritiro e gli avvisi di disattivazione, per garantire la consapevolezza delle modifiche ai contratti dei microservizi. Per quanto possa sembrare un'impresa ardua, integrare queste pratiche nella tua cultura dello sviluppo produrrà grandi miglioramenti in termini di velocità e qualità nel tempo.

Gestione dei microservizi

I servizi e le versioni del codice possono essere gestiti direttamente. Di conseguenza, puoi eseguire il deployment delle nuove versioni del codice affiancate alle versioni esistenti del codice e puoi testare il nuovo codice prima di renderlo la versione di pubblicazione predefinita.

Ogni progetto App Engine ha un servizio predefinito e ogni servizio ha una versione del codice predefinita. Per gestire il servizio predefinito della versione predefinita di un progetto, utilizza il seguente URL:
https://PROJECT_ID.REGION_ID.r.appspot.com

Se esegui il deployment di un servizio denominato user-service, puoi accedere alla versione di servizio predefinita di quel servizio utilizzando il seguente URL:

https://user-service-dot-my-app.REGION_ID.r.appspot.com

Se esegui il deployment di una seconda versione del codice non predefinita denominata banana nel servizio user-service, puoi accedere direttamente a quella versione del codice utilizzando il seguente URL:

https://banana-dot-user-service-dot-my-app.REGION_ID.r.appspot.com

Tieni presente che se esegui il deployment di una seconda versione del codice non predefinita denominata cherry nel servizio default, puoi accedere a tale versione del codice utilizzando il seguente URL:

https://cherry-dot-my-app.REGION_ID.r.appspot.com

App Engine applica la regola che stabilisce che i nomi delle versioni di codice nel servizio predefinito non possono entrare in conflitto con i nomi dei servizi.

Versioni del codice specifiche con indirizzo diretto devono essere utilizzate solo per i test di fumo e per facilitare i test A/B, il rollback e il rollback. Il codice client deve invece essere rivolto solo alla versione predefinita di pubblicazione del servizio predefinito o di un servizio specifico:


https://PROJECT_ID.REGION_ID.r.appspot.com

https://SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com

Questo stile di indirizzo consente ai microservizi di eseguire il deployment di nuove versioni dei servizi, incluse correzioni di bug, senza richiedere modifiche ai client.

Utilizzo delle versioni API

Ogni API dei microservizi dovrebbe avere una versione principale dell'API nell'URL, ad esempio:

/user-service/v1/

Questa versione principale dell'API identifica chiaramente nei log la versione API del microservizio che viene chiamata. Ma soprattutto, la versione principale dell'API restituisce URL diversi, pertanto le nuove versioni principali dell'API possono essere pubblicate affiancate alle versioni principali dell'API precedenti:

/user-service/v1/
/user-service/v2/

Non è necessario includere la versione secondaria dell'API nell'URL perché, per definizione, le versioni secondarie dell'API non introdurranno modifiche che provocano un errore. Infatti, l'inclusione della versione secondaria dell'API nell'URL causerebbe una proliferazione degli URL e causerebbe incertezze sulla possibilità di un client di passare a una nuova versione secondaria dell'API.

Tieni presente che questo articolo presuppone un ambiente di integrazione e distribuzione continue in cui viene sempre eseguito il deployment del ramo principale in App Engine. In questo articolo vengono descritti due concetti distinti della versione:

  • Versione del codice, che viene mappata direttamente a una versione di servizio App Engine e rappresenta un particolare tag di commit del ramo principale.

  • Versione API, che mappa direttamente a un URL dell'API e rappresenta la forma degli argomenti della richiesta, la forma del documento di risposta e il comportamento dell'API.

Questo articolo presuppone inoltre che il deployment di un singolo codice implementerà sia la vecchia che la nuova versione di un'API in una versione comune del codice. Ad esempio, il ramo principale di cui hai eseguito il deployment potrebbe implementare sia /user-service/v1/ sia /user-service/v2/. Durante l'implementazione di nuove versioni secondarie e patch, questo approccio consente di suddividere il traffico tra due versioni del codice indipendentemente dalle versioni API implementate dal codice.

La tua organizzazione può scegliere di sviluppare /user-service/v1/ e /user-service/v2/ su rami di codice diversi; in altre parole, nessun deployment di codice implementerà entrambi contemporaneamente. Questo modello è possibile anche su App Engine, ma per suddividere il traffico è necessario spostare la versione principale dell'API nel nome del servizio stesso. Ad esempio, i tuoi clienti potrebbero utilizzare i seguenti URL:

http://user-service-v1.my-app.REGION_ID.r.appspot.com/user-service/v1/
http://user-service-v2.my-app.REGION_IDappspot.com/user-service/v2/

La versione principale dell'API passa al nome del servizio stesso, ad esempio user-service-v1 e user-service-v2. (Le parti /v1/ e /v2/ del percorso sono ridondanti in questo modello e potrebbero essere rimosse, anche se possono comunque essere utili nell'analisi dei log.) Questo modello richiede un po' più di lavoro perché probabilmente richiede aggiornamenti degli script di deployment per eseguire il deployment di nuovi servizi in caso di modifiche importanti della versione dell'API. Inoltre, tieni presente il numero massimo di servizi consentiti per applicazione App Engine.

Modifiche che provocano e non interrompono

È importante comprendere la differenza tra una modifica che provoca un errore e una modifica costante. L'interruzione delle modifiche è spesso sottrattiva, nel senso che sottrae parte del documento di richiesta o di risposta. La modifica della forma del documento o del nome delle chiavi può introdurre una modifica che provoca un errore. I nuovi argomenti obbligatori interrompono sempre le modifiche. L'interruzione delle modifiche può verificarsi anche se il comportamento del microservizio cambia.

I cambiamenti continui tendono ad essere cumulativi. Un nuovo argomento di richiesta facoltativo o una nuova sezione aggiuntiva nel documento di risposta sono modifiche senza interruzioni. Per ottenere modifiche senza interruzioni, la scelta della serializzazione on-the-wire è essenziale. Molte serializzazioni sono facili da usare modifiche senza interruzioni: JSON, buffer di protocollo o Thrift. Quando vengono deserializzate, queste serializzazioni ignorano silenziosamente le informazioni aggiuntive e impreviste. Nei linguaggi dinamici, le informazioni aggiuntive appaiono semplicemente nell'oggetto deserializzato.

Considera la seguente definizione JSON per il servizio /user-service/v1/:

{
  "userId": "UID-123",
  "firstName": "Jake",
  "lastName": "Cole",
  "username": "jcole@example.com"
}

La seguente modifica che provoca un errore richiedere il ripristino delle versioni del servizio come /user-service/v2/:

{
  "userId": "UID-123",
  "name": "Jake Cole",  # combined fields
  "email": "jcole@example.com"  # key change
}

Tuttavia, la seguente modifica senza interruzioni non richiede una nuova versione:

{
  "userId": "UID-123",
  "firstName": "Jake",
  "lastName": "Cole",
  "username": "jcole@example.com",
  "company": "Acme Corp."  # new key
}

Deployment di nuove versioni secondarie dell'API senza interruzioni

Durante il deployment di una nuova versione secondaria dell'API, App Engine consente il rilascio della nuova versione del codice affiancata alla vecchia versione del codice. In App Engine, anche se puoi gestire direttamente qualsiasi versione di cui è stato eseguito il deployment, solo una versione è quella di pubblicazione predefinita. Ricorda che esiste una versione di gestione predefinita per ogni servizio. In questo esempio abbiamo la vecchia versione del codice, apple, che è la versione di pubblicazione predefinita, ed eseguiamo il deployment della nuova versione del codice come versione affiancata, denominata banana. Tieni presente che gli URL dei microservizi per entrambi sono gli stessi /user-service/v1/, poiché stiamo eseguendo il deployment di una modifica minima non definitiva dell'API.

App Engine fornisce meccanismi per eseguire automaticamente la migrazione del traffico da apple a banana contrassegnando la nuova versione del codice banana come versione di pubblicazione predefinita. Se viene impostata la nuova versione di pubblicazione predefinita, non verranno instradate nuove richieste a apple e tutte le nuove richieste verranno instradate a banana. Questo è il modo in cui esegui il rollback a una nuova versione del codice che implementa una nuova versione API secondaria o patch senza alcun impatto sui microservizi client.

In caso di errore, il rollback viene eseguito invertendo la procedura riportata sopra: ripristina la versione di pubblicazione predefinita su quella precedente, apple nel nostro esempio. Tutte le nuove richieste torneranno alla versione precedente del codice e nessuna nuova richiesta verrà reindirizzata a banana. Tieni presente che le richieste in corso possono essere completate.

App Engine consente inoltre di indirizzare solo una determinata percentuale del traffico alla nuova versione del codice. Questo processo viene spesso chiamato processo di rilascio canary e il meccanismo è chiamato suddivisione del traffico in App Engine. Puoi indirizzare l'1%, il 10%, il 50% o qualsiasi percentuale di traffico che desideri sulle nuove versioni del codice e puoi modificare questo valore nel tempo. Ad esempio, potresti implementare la nuova versione del codice in 15 minuti, aumentando lentamente il traffico e verificando la presenza di eventuali problemi che potrebbero identificare la necessità di un rollback. Lo stesso meccanismo consente di eseguire test A/B su due versioni del codice: impostare la suddivisione del traffico al 50% e confrontare le prestazioni e le caratteristiche del tasso di errore delle due versioni del codice per confermare i miglioramenti previsti.

L'immagine seguente mostra le impostazioni di suddivisione del traffico nella console Google Cloud:

Impostazioni di suddivisione del traffico nella console Google Cloud

Deployment di nuove versioni principali delle API

Quando esegui il deployment delle versioni principali dell'API che non vanno a buon fine, il processo di rollback e rollback è lo stesso utilizzato per le versioni secondarie senza interruzioni dell'API. Tuttavia, in genere non eseguirai la suddivisione del traffico o i test A/B perché la versione API che provoca il problema è un URL appena rilasciato, ad esempio /user-service/v2/. Ovviamente, se hai modificato l'implementazione sottostante della vecchia versione principale dell'API, potresti comunque voler utilizzare la suddivisione del traffico per verificare che la precedente versione principale dell'API continui a funzionare come previsto.

Quando si esegue il deployment di una nuova versione principale dell'API, è importante ricordare che anche le versioni principali precedenti dell'API potrebbero continuare a funzionare. Ad esempio, /user-service/v1/ potrebbe essere ancora pubblicato dopo il rilascio di /user-service/v2/. Questo aspetto è una parte essenziale delle release di codici indipendenti. Puoi disattivare le versioni principali dell'API precedenti solo dopo aver verificato che nessun altro microservizio le richieda, inclusi altri microservizi che potrebbero dover eseguire il rollback a una versione del codice precedente.

Come esempio concreto, immagina di avere un microservizio denominato web-app, che dipende da un altro microservizio denominato user-service. Immagina che user-service debba modificare alcune implementazioni sottostanti che rendano impossibile supportare la vecchia versione principale dell'API attualmente in uso da web-app, ad esempio la compressione di firstName e lastName in un unico campo denominato name. Ciò significa che user-service deve disattivare una versione principale precedente dell'API.

Per apportare questa modifica, è necessario eseguire tre deployment separati:

  • Innanzitutto, user-service deve eseguire il deployment di /user-service/v2/ continuando a supportare /user-service/v1/. Questo deployment potrebbe richiedere la scrittura di codice temporaneo per supportare la compatibilità con le versioni precedenti, una conseguenza comune nelle applicazioni basate su microservizi.

  • Successivamente, web-app deve eseguire il deployment del codice aggiornato che modifichi la sua dipendenza da /user-service/v1/ a /user-service/v2/

  • Infine, dopo che il team user-service ha verificato che web-app non richiede più /user-service/v1/ e che web-app non deve eseguire il rollback, il team può eseguire il deployment del codice che rimuove il vecchio endpoint /user-service/v1/ e l'eventuale codice temporaneo necessario per supportarlo.

Sebbene tutte queste attività possano sembrare onerose, si tratta di un processo essenziale nelle applicazioni basate su microservizi ed è esattamente il processo che consente cicli di rilascio dello sviluppo indipendente. Per essere chiari, questo processo sembra essere molto dipendente, ma è importante sottolineare che ogni passaggio precedente può avvenire su linee temporali indipendenti e il rollback e il rollback avvengono nell'ambito di un singolo microservizio. Solo l'ordine dei passaggi è fisso e possono svolgersi nell'arco di molte ore, giorni o persino settimane.

Passaggi successivi