Contratti, indirizzamento e API per i microservizi

ID regione

REGION_ID è un codice abbreviato assegnato da Google in base all'area geografica 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.

In genere, i microservizi su App Engine si chiamano reciprocamente utilizzando API RESTful basate su HTTP. È anche possibile richiamare i microservizi in background utilizzando le code di attività, in cui 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 con buone prestazioni.

Uso di contratti solidi

Uno degli aspetti più importanti delle applicazioni basate su microservizi è la capacità di eseguire il deployment di microservizi in modo completamente indipendente l'uno dall'altro. Per ottenere questa indipendenza, ogni microservizio deve fornire ai propri client un contratto ben definito e sottoposto al controllo delle versioni, ovvero altri microservizi. Ogni servizio non deve violare questi contratti sottoposti al controllo delle versioni finché non viene appurato che nessun altro microservizio fa affidamento su un particolare contratto sottoposto al controllo delle versioni. 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 una modifica che provoca un errore piuttosto che di una modifica che non provoca un'interruzione. Devono sapere quando è richiesta una nuova release principale. Devono sapere come e quando un vecchio contratto può essere messo fuori servizio. I team devono adottare tecniche di comunicazione appropriate, tra cui gli avvisi di ritiro e di ritiro, per garantire la consapevolezza delle modifiche ai contratti dei microservizi. Anche se ciò può sembrare scoraggiante, l'integrazione di queste pratiche nella tua cultura di sviluppo produrrà grandi miglioramenti in termini di velocità e qualità nel tempo.

Indirizzamento dei microservizi

I servizi e le versioni del codice possono essere gestiti direttamente. Di conseguenza, puoi eseguire il deployment di nuove versioni del codice accanto a quelle esistenti e testare il nuovo codice prima di impostarla come versione predefinita.

Ogni progetto App Engine ha un servizio predefinito e ogni servizio ha una versione del codice predefinita. Per utilizzare 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 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 di codice non predefinita denominata cherry nel servizio default, puoi accedere a quella 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 di codice nel servizio predefinito non possono entrare in conflitto con i nomi dei servizi.

Le versioni di codice specifiche con indirizzo diretto devono essere utilizzate solo per i test del fumo e per facilitare i test A/B, il rollback e il rollback. Il codice client deve invece indirizzare solo la 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 indirizzamento consente ai microservizi di eseguire il deployment di nuove versioni dei loro servizi, incluse correzioni di bug, senza richiedere alcuna modifica ai client.

Utilizzo delle versioni API

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

/user-service/v1/

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

/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 introducono alcuna modifica che provoca un errore. Infatti, l'inclusione della versione secondaria dell'API nell'URL causerebbe una proliferazione degli 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 viene sempre eseguito il deployment del ramo principale in App Engine. Ci sono due concetti distinti della versione in questo articolo:

  • Versione Code, che viene mappata direttamente a una versione del servizio App Engine e rappresenta un particolare tag 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 dell'API 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/. 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 diversi rami di codice; in altre parole, nessun deployment del codice implementerà entrambi contemporaneamente. Questo modello è disponibile anche in App Engine, ma per suddividere il traffico devi 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 viene spostata nel nome del servizio, 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, ma potrebbero comunque essere utili per l'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 caso di modifiche significative alla versione dell'API. Inoltre, tieni presente il numero massimo di servizi consentiti per applicazione App Engine.

Modifiche radicali a confronto con modifiche originali

È importante capire la differenza tra una modifica che provoca un'interruzione e una modifica che non provoca un'interruzione. Le modifiche che provocano un errore sono spesso sottrazioni, il che significa che estraggono una parte del documento della richiesta o della risposta. La modifica della forma del documento o del nome delle chiavi può comportare una modifica che provoca un errore. I nuovi argomenti obbligatori causano sempre un errore nelle modifiche. Le modifiche che provocano un errore possono verificarsi anche se il comportamento del microservizio viene modificato.

Le modifiche universali tendono a essere cumulative. Un nuovo argomento della richiesta facoltativo o una nuova sezione aggiuntiva nel documento di risposta rappresentano modifiche non irreversibili. Per apportare modifiche senza interruzioni, la scelta della serializzazione on-the-wire è essenziale. Molte serializzazioni sono compatibili con modifiche costanti: JSON, Protocol Buffers o Thrift. Quando vengono deserializzate, queste serializzazioni ignorano silenziosamente le informazioni aggiuntive e impreviste. Nei linguaggi dinamici, le informazioni aggiuntive semplicemente compaiono 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 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 sostanziale 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

Durante il deployment di una nuova versione secondaria dell'API, App Engine consente di rilasciare la nuova versione del codice accanto alla vecchia versione del codice. In App Engine, anche se puoi indirizzare direttamente qualsiasi versione di cui è stato eseguito il deployment, solo una versione è quella predefinita. Ricorda che esiste una versione di pubblicazione predefinita per ogni servizio. In questo esempio abbiamo la nostra vecchia versione del codice, denominata apple, che è la versione di pubblicazione predefinita, e ne eseguiamo il deployment 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 minore e ricorrente all'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. Quando è impostata la nuova versione di pubblicazione predefinita, non verranno inoltrate nuove richieste a apple e tutte le nuove richieste verranno instradate a banana. Questo è il modo in cui si esegue il rollback a una nuova versione del codice che implementa una nuova versione dell'API secondaria o patch senza impatto sui microservizi client.

In caso di errore, il rollback si ottiene invertendo la procedura precedente: ripristina la versione di pubblicazione predefinita su quella precedente, apple nel nostro esempio. Tutte le nuove richieste verranno reindirizzate alla versione precedente del codice e nessuna nuova richiesta verrà instradata 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 è 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 vuoi verso le nuove versioni del codice e puoi modificare questa quantità nel tempo. Ad esempio, puoi implementare la nuova versione del codice in 15 minuti, aumentando lentamente il traffico e controllando la presenza di eventuali problemi che potrebbero indicare la necessità di un rollback. Questo stesso meccanismo consente di eseguire test A/B di due versioni del codice: impostare la suddivisione del traffico al 50% e confrontare le caratteristiche di prestazioni 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 console Google Cloud:

Impostazioni di suddivisione del traffico nella console Google Cloud

Deployment di nuove versioni più importanti dell'API

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

Quando esegui il deployment di una nuova versione principale dell'API, è importante ricordare che anche le vecchie versioni principali dell'API potrebbero continuare a essere pubblicate. Ad esempio, /user-service/v1/ potrebbe continuare a essere pubblicato quando verrà rilasciato /user-service/v2/. Questo aspetto è una parte essenziale delle release indipendenti del codice. Puoi disattivare le vecchie versioni principali dell'API solo dopo aver verificato che nessun altro microservizio le richiede, compresi 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 alcune implementazioni sottostanti che rendono impossibile supportare la vecchia versione principale dell'API attualmente in uso da web-app, ad esempio la compressione di firstName e lastName in un singolo campo chiamato name. Ciò significa che user-service deve disattivare una versione principale precedente dell'API.

Per apportare questa modifica, è necessario effettuare tre deployment distinti:

  • 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 cambia 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, può eseguire il deployment del codice che rimuove l'endpoint /user-service/v1/ precedente 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 dello sviluppo indipendenti. Per essere chiari, questo processo sembra piuttosto dipendente, ma soprattutto, ogni passaggio precedente può verificarsi su tempistiche indipendenti e il rollback e il rollback avvengono nell'ambito di un singolo microservizio. Solo l'ordine dei passaggi è fisso e i passaggi possono avvenire nell'arco di molte ore, giorni o persino settimane.

Passaggi successivi