Storia: Nelle architetture monolitiche, le modifiche alle API erano gestibili attraverso le fasi di test di integrazione. Tuttavia, con l'adozione dei microservizi, l'espansione delle dipendenze dei servizi ha creato "l'inferno delle versioni" in cui un singolo cambiamento distruttivo poteva cascata di errori in decine di consumatori downstream. Questo ha reso necessario un passaggio da revisioni manuali delle OpenAPI a cancelli di validazione automatizzati e basati su contratti all'interno dei pipeline CI/CD.
Il problema: I test tradizionali convalidano che un'API funzioni in isolamento, ma non riescono a rilevare se le modifiche agli schemi di richiesta/riposta violano contratti impliciti con i consumatori esistenti. Le revisioni manuali delle specifiche sono soggette a errori e non possono scalare a centinaia di servizi interdipendenti, portando a incidenti in produzione in cui i campi obsoleti vengono rimossi mentre sono ancora in uso attivo.
La soluzione: Implementare un pipeline di validazione multilivello integrando l'analisi delle differenze delle OpenAPI con il testing basato sui contratti degli utenti. Utilizzare strumenti come Optic o Swagger Diff per classificare le modifiche come distruttive (rimozione di campi, cambiamenti di tipo) o non distruttive (aggiunte opzionali). Integrare Pact per verificare che le modifiche del fornitore soddisfino le aspettative dei consumatori registrate. Applicare un'automazione del versioning semantico in cui il pipeline calcola i necessari aumenti di versione basati sulla gravità del cambiamento rilevato e blocca le distribuzioni se l'aumento è insufficiente.
validate_api_compatibility: stage: test script: - optic diff openapi.yaml --base main --severity breaking - pact-verifier --provider-app-version $CI_COMMIT_SHA --publish-verification-results - python scripts/check_semver.py --schema-diff-report optic-report.json rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"
Il nostro team gestiva un API di Gateway per Pagamenti al servizio di dodici microservizi interni e tre partner bancari esterni. Durante un miglioramento di routine per aggiungere campi di autenticazione 3D Secure 2.0, un sviluppatore rimosse il campo stringa obsoleto transactionReference, sostituendolo con una struttura ad oggetto.
Descrizione del problema: Il cambiamento superò i test unitari e di integrazione poiché la nuova struttura gestiva correttamente i dati. Tuttavia, tre microservizi contabili legacy si aspettavano ancora il campo stringa piatto. La revisione manuale delle OpenAPI trascurò la natura distruttiva di questo cambiamento strutturale. All'atto del deployment, i lavori di riconciliazione fallirono con errori di deserializzazione, causando un'interruzione di quattro ore.
Differenti soluzioni considerate:
Revisione manuale tra pari con checklist: Richiedere che ingegneri senior rivedano tutte le modifiche alle OpenAPI utilizzando una checklist di cambiamenti distruttivi. Questo approccio si basa sulla vigilanza umana ma è fondamentalmente inaffidabile sotto pressione, non scala con cicli di distribuzione rapidi e non può tenere conto delle dipendenze nascoste dei consumatori.
Confronto automatico dello Schema JSON: Implementare uno strumento di differenza di base che segnala qualsiasi differenza strutturale come errore. Questo fornisce un riscontro veloce, ma produce eccessivi falsi positivi trattando le modifiche additive (nuovi campi opzionali) come violazioni, costringendo i team a mantenere lunghe liste di eccezioni e infine a ignorare gli avvisi a causa dell'affaticamento da allerta.
Testing del contratto del consumatore con cancelli di versioning semantico: Distribuire Pact per contratti guidati dai consumatori combinati con Optic CLI per l'analisi delle differenze delle OpenAPI. Questo convalida le modifiche contro le interazioni reali registrate dei consumatori, assicurando che solo le modifiche sinceramente distruttive attivino i fallimenti. Suggerisce automaticamente aumenti di versioning semantico e mantiene il rispetto della timeline di deprezzamento. Lo svantaggio è l'investimento iniziale richiesto per integrare i team di consumatori e l'overhead di archiviazione per gli artefatti dei contratti.
Soluzione scelta e motivazione: Abbiamo scelto il testing del contratto del consumatore perché si allineava con la necessità di distribuzioni autonome della nostra architettura a microservizi mentre preveniva interruzioni a cascata. A differenza delle revisioni manuali, si scalava orizzontalmente. A differenza degli strumenti di differenza di base, comprendeva l'impatto semantico. Abbiamo accettato il costo di onboarding obbligando inizialmente i test dei contratti solo per i percorsi di servizio critici.
Risultato: I cambiamenti distruttivi sono stati eliminati dalle versioni di produzione nei successivi otto mesi. La frequenza delle distribuzioni è aumentata da ogni due settimane a giornaliera poiché i team si fidavano dei cancelli automatizzati. Quando lo stesso refactoring è stato tentato in seguito, la verifica Pact è fallita immediatamente nella richiesta di pull, evidenziando l'incompatibilità con il servizio legacy.
Come distingui cambiamenti sintattici distruttivi da cambiamenti semantici distruttivi nella validazione delle API REST?
I cambiamenti sintattici coinvolgono modifiche strutturali rilevabili attraverso il confronto degli schemi OpenAPI, come la rimozione di campi o la modifica del tipo. I cambiamenti semantici preservano la struttura ma alterano il comportamento, come cambiare il valore predefinito di un parametro opzionale o modificare l'ordine di ordinamento degli array restituiti. La pura validazione dello schema manca di interruzioni semantiche, richiedendo test comportamentali attraverso test di contratto o confronto del traffico shadow per rilevare le modifiche nella logica aziendale.
Cos'è il pattern espansione-contratto e come dovrebbe l'automazione farlo rispettare?
Il pattern espansione-contratto richiede di aggiungere nuove funzionalità insieme a quelle vecchie (espansione), migrare i consumatori, quindi rimuovere il codice vecchio (contratto). L'automazione deve tenere traccia dei metadati di deprezzamento dei campi con date di scadenza, facendo fallire le build se i campi deprecati vengono rimossi prematuramente. Inoltre, il sistema dovrebbe monitorare la telemetria per verificare zero traffico sugli endpoint deprecati prima di autorizzare la rimozione, garantendo una vera prontezza dei consumatori piuttosto che una mera compatibilità a livello di codice.
Come convalidi la compatibilità delle API quando i consumatori sono terze parti esterne che non possono partecipare nel tuo pipeline di testing dei contratti?
Per i consumatori esterni dove i contratti bi-direzionali di Pact sono impossibili, implementa una simulazione di consumatori sintetici utilizzando il shadowing del traffico e il testing VCR. Registra i modelli di produzione per creare mock rappresentativi, quindi riproducili contro le nuove versioni delle API. Combina questo con distribuzioni canary dotate di trigger di rollback automatici, e mantieni rigide politiche di LTS per le API pubbliche con obbligatorie notifiche di deprezzamento che si estendono per diversi trimestri.