Contratti, indirizzamento e API per i microservizi

ID regione

Il REGION_ID è un codice abbreviato che Google assegna in base alla regione selezionata quando crei l'app. Il codice non corrisponde a un paese o a una provincia, anche se alcuni ID regione possono sembrare simili ai codici di paesi e province di uso comune. Per le app create dopo febbraio 2020, REGION_ID.r è incluso negli URL 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 a vicenda utilizzando API RESTful basate su HTTP. È anche possibile richiamare i microservizi in background utilizzando le code di attività e si applicano i principi di progettazione delle API descritti qui. È importante seguire determinati pattern per garantire che l'applicazione basata su microservizi sia stabile, sicura e funzioni correttamente.

Utilizzo di contratti solidi

Uno degli aspetti più importanti delle applicazioni basate su microservizi è la possibilità di eseguire il deployment dei microservizi in maniera totalmente indipendente l'uno dall'altro. Per raggiungere 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 tali contratti. Tieni presente che altri microservizi potrebbero dover eseguire il rollback a una versione precedente del codice che richiede un contratto precedente, per cui è importante tenerne conto nei criteri di deprecazione e disattivazione.

Probabilmente, l'aspetto organizzativo più complesso di un'applicazione stabile basata sui microservizi è la creazione di una cultura incentrata su contratti solidi e sottoposti al controllo delle versioni. I team di sviluppo devono interiorizzare la comprensione di una modifica che provoca un errore rispetto a una modifica che non provoca un errore. Devono sapere quando è richiesta una nuova release principale. Devono capire come e quando un vecchio contratto può essere ritirato dal servizio. I team devono utilizzare tecniche di comunicazione appropriate, tra cui avvisi di ritiro e disattivazione, per garantire la consapevolezza delle modifiche ai contratti dei microservizi. Anche se può sembrare scoraggiante, integrare queste pratiche nella cultura di sviluppo porterà a grandi miglioramenti in termini di velocità e qualità nel tempo.

Indirizzamento dei microservizi

È possibile indirizzare direttamente i servizi e le versioni del codice. Di conseguenza, puoi eseguire il deployment di nuove versioni del codice affiancate a quelle esistenti e puoi testare il nuovo codice prima di impostarlo come versione di pubblicazione predefinita.

Ogni progetto App Engine ha un servizio predefinito e ogni servizio ha una versione del codice predefinita. Per indirizzare 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 pubblicazione predefinita di questo 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 questa versione del codice utilizzando il seguente URL:

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

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

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

App Engine applica la regola secondo cui i nomi delle versioni del codice nel servizio predefinito non possono entrare in conflitto con i nomi dei servizi.

L'indirizzamento diretto di versioni di codice specifiche deve essere utilizzato solo per i test smoke e per facilitare i test A/B, l'avanzamento e il rollback. Il codice cliente deve invece indirizzare solo la versione di pubblicazione predefinita 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 indirizzamento consente ai microservizi di eseguire il deployment di nuove versioni dei propri servizi, incluse le correzioni di bug, senza richiedere modifiche ai client.

Utilizzo delle versioni API

Ogni API di microservizio deve avere una versione API principale nell'URL, ad esempio:

/user-service/v1/

Questa versione principale dell'API identifica chiaramente nei log la versione dell'API del microservizio che viene chiamata. Ancora più importante, la versione principale dell'API genera URL diversi, in modo che le nuove versioni principali dell'API possano essere pubblicate affiancate alle vecchie versioni principali dell'API:

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

Non è necessario includere la versione secondaria dell'API nell'URL perché le versioni secondarie dell'API per definizione non introdurranno modifiche che provocano errori. Infatti, l'inclusione della versione secondaria dell'API nell'URL comporterebbe una proliferazione di URL e causerebbe incertezza sulla capacità 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 il ramo principale viene sempre sottoposto a deployment su App Engine. In questo articolo vengono presentati due concetti distinti di versione:

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

  • Versione dell'API, che corrisponde direttamente a un URL 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 un singolo deployment di codice implementerà sia le versioni API precedenti che quelle nuove di un'API in una versione di codice comune. Ad esempio, il ramo principale di cui è stato eseguito il deployment potrebbe implementare sia /user-service/v1/ sia /user-service/v2/. Quando esegui il rollout di nuove versioni secondarie e patch, questo approccio ti consente di dividere il traffico tra due versioni del codice indipendentemente dalle versioni dell'API che il codice implementa effettivamente.

La tua organizzazione può scegliere di sviluppare /user-service/v1/ e /user-service/v2/ su rami di codice diversi, ovvero nessuna implementazione di codice implementerà entrambi contemporaneamente. Questo modello è possibile anche su App Engine, ma per dividere il traffico dovresti spostare la versione principale dell'API nel nome del servizio stesso. Ad esempio, i tuoi clienti utilizzerebbero 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 viene spostata nel 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 potrebbero comunque essere utili nell'analisi dei log.) Questo modello richiede un po' più di lavoro perché probabilmente richiede aggiornamenti agli script di deployment per eseguire il deployment di nuovi servizi in cambiamenti importanti della versione dell'API. Inoltre, tieni presente il numero massimo di servizi consentiti per applicazione App Engine.

Modifiche che provocano errori e modifiche che non provocano errori

È importante comprendere la differenza tra una modifica che causa interruzioni e una modifica che non causa interruzioni. Le modifiche che causano interruzioni sono spesso sottrattive, il che significa che rimuovono una parte del documento di richiesta o risposta. La modifica della forma del documento o del nome delle chiavi può introdurre una modifica che causa interruzioni. I nuovi argomenti obbligatori sono sempre modifiche che causano interruzioni. Le modifiche che causano interruzioni possono verificarsi anche se il comportamento del microservizio cambia.

Le modifiche non distruttive tendono a essere additive. Un nuovo argomento di richiesta facoltativo o una nuova sezione aggiuntiva nel documento di risposta sono modifiche non distruttive. Per ottenere modifiche non distruttive, la scelta della serializzazione on-the-wire è essenziale. Molte serializzazioni sono compatibili con modifiche non distruttive: JSON, buffer di protocollo o Thrift. Quando vengono deserializzate, queste serializzazioni ignorano silenziosamente le informazioni aggiuntive e impreviste. Nei linguaggi dinamici, le informazioni aggiuntive vengono visualizzate 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 richiederebbe una nuova versione del servizio come /user-service/v2/:

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

Tuttavia, la seguente modifica non distruttiva 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 non incompatibili

Quando viene eseguito il deployment di una nuova versione secondaria dell'API, App Engine consente di rilasciare la nuova versione del codice insieme alla versione precedente. Su App Engine, anche se puoi indirizzare direttamente una qualsiasi delle versioni di cui è stato eseguito il deployment, solo una versione è la versione di pubblicazione predefinita; ricorda che esiste una versione di pubblicazione predefinita per ogni servizio. In questo esempio, abbiamo la vecchia versione del codice, denominata apple, che è la versione di pubblicazione predefinita, e implementiamo la 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 implementando una modifica secondaria dell'API non distruttiva.

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. Quando viene impostata la nuova versione di pubblicazione predefinita, nessuna nuova richiesta verrà indirizzata a apple e tutte le nuove richieste verranno indirizzate a banana. In questo modo esegui il roll forward a una nuova versione del codice che implementa una nuova versione secondaria o patch dell'API senza alcun impatto sui microservizi client.

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

App Engine offre anche la possibilità di indirizzare solo una determinata percentuale del traffico alla nuova versione del codice. Questa procedura viene spesso chiamata procedura 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 preferisci verso le nuove versioni del codice e puoi modificare questo importo nel tempo. Ad esempio, potresti implementare la nuova versione del codice in 15 minuti, aumentando lentamente il traffico e monitorando eventuali problemi che potrebbero indicare la necessità di un rollback. Lo stesso meccanismo ti consente di eseguire test A/B su due versioni del codice: imposta la divisione del traffico al 50% e confronta le caratteristiche di rendimento e tasso di errore delle due versioni del codice per confermare i miglioramenti previsti.

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

Impostazioni di suddivisione del traffico nella console Google Cloud

Deployment di nuove versioni principali dell'API con modifiche incompatibili

Quando esegui il deployment di versioni principali dell'API che causano interruzioni, il processo di avanzamento e rollback è lo stesso delle versioni secondarie dell'API che non causano interruzioni. Tuttavia, in genere non esegui alcuna suddivisione del traffico o test A/B perché la versione dell'API che introduce modifiche incompatibili è un URL appena rilasciato, ad esempio /user-service/v2/. Naturalmente, se hai modificato l'implementazione sottostante della tua vecchia versione principale dell'API, potresti comunque voler utilizzare la suddivisione del traffico per verificare che la vecchia versione principale dell'API continui a funzionare come previsto.

Quando esegui il deployment di una nuova versione principale dell'API, è importante ricordare che potrebbero essere ancora in uso anche le versioni principali precedenti dell'API. Ad esempio, /user-service/v1/ potrebbe essere ancora in servizio quando viene rilasciato /user-service/v2/. Questo fatto è una parte essenziale delle release di codice indipendenti. Puoi disattivare le versioni principali precedenti dell'API solo dopo aver verificato che nessun altro microservizio le richieda, inclusi altri microservizi che potrebbero dover eseguire il rollback a una versione precedente del codice.

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 un'implementazione sottostante che renderà impossibile supportare la vecchia versione principale dell'API attualmente utilizzata da web-app, ad esempio comprimendo firstName e lastName in un unico campo chiamato name. ovvero user-service deve ritirare una versione principale precedente dell'API.

Per apportare questa modifica, devono essere eseguiti 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 modifica 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ò implementare il codice che rimuove il vecchio endpoint /user-service/v1/ e qualsiasi codice temporaneo necessario per supportarlo.

Sebbene tutta questa attività possa sembrare onerosa, è un processo essenziale nelle applicazioni basate su microservizi ed è proprio il processo che consente cicli di rilascio di sviluppo indipendenti. Per essere chiari, questo processo sembra essere piuttosto dipendente, ma è importante sottolineare che ogni passaggio precedente può verificarsi in sequenze temporali indipendenti e che l'avanzamento e il rollback avvengono nell'ambito di un singolo microservizio. Solo l'ordine dei passaggi è fisso e i passaggi potrebbero svolgersi nell'arco di molte ore, giorni o persino settimane.

Passaggi successivi