Integrare API e sistemi senza costruire un altro punto fragile
Integrare due sistemi sembra spesso un’attività lineare.
Un’applicazione espone un’API, l’altra deve ricevere alcuni dati e tra le due viene costruita una connessione.
Un ordine creato nell’e-commerce deve arrivare nell’ERP. Un cliente aggiornato nel CRM deve essere sincronizzato con una piattaforma di marketing. Un catalogo prodotti deve passare dal PIM al sito. Una disponibilità di magazzino deve essere comunicata a più canali di vendita.
All’inizio, una connessione diretta può sembrare la soluzione più semplice e ragionevole:
Sistema A → Sistema BIn molti casi lo è davvero.
Il problema emerge quando la connessione smette di trasferire semplicemente dei dati e inizia ad assumere altre responsabilità.
Deve trasformare formati, applicare regole, gestire eccezioni, memorizzare stati, riprovare operazioni fallite, coordinare più destinazioni e compensare comportamenti differenti tra i sistemi coinvolti.
A quel punto compare spesso una domanda:
Serve un middleware?
La risposta non dipende soltanto dal numero di sistemi o dalla tecnologia utilizzata.
Dipende soprattutto da quanta complessità l’integrazione deve assorbire e da dove vogliamo che quella complessità rimanga visibile e governabile.
Una connessione diretta può essere la scelta migliore.
Un middleware può diventare necessario.
Entrambe le soluzioni, però, possono trasformarsi in un nuovo punto fragile se vengono adottate senza chiarire responsabilità, dipendenze e modalità di gestione degli errori.
Le integrazioni punto-punto vengono spesso descritte come qualcosa da evitare.
In realtà, collegare direttamente due sistemi non è necessariamente una cattiva scelta.
Può essere la soluzione più appropriata quando:
- i sistemi coinvolti sono soltanto due;
- il flusso è semplice e ben delimitato;
- il formato dei dati è stabile;
- le regole di trasformazione sono limitate;
- il volume è gestibile;
- gli errori sono facilmente individuabili;
- la connessione ha un unico responsabile;
- non esistono altre applicazioni che devono riutilizzare la stessa logica.
In questo scenario, introdurre una piattaforma intermedia potrebbe aggiungere più complessità di quanta ne risolva.
Una connessione diretta riduce il numero di componenti da distribuire, configurare e monitorare. Può essere compresa più facilmente e, se realizzata bene, mantenuta senza particolari difficoltà.
Il problema non è quindi il collegamento punto-punto.
Il problema nasce quando una soluzione progettata per un rapporto semplice tra due sistemi viene estesa progressivamente fino a sostenere una rete di responsabilità che non erano previste all’inizio.
Le integrazioni raramente diventano fragili in un solo momento.
La complessità tende ad accumularsi attraverso richieste singolarmente ragionevoli.
All’inizio il requisito può essere:
Quando viene creato un ordine, invialo all’ERP.
Successivamente arrivano nuove condizioni:
- alcuni ordini devono essere esclusi;
- il codice cliente deve essere ricercato nel gestionale;
- i prodotti utilizzano identificativi differenti;
- gli indirizzi devono essere normalizzati;
- gli ordini esteri seguono regole diverse;
- in caso di errore bisogna riprovare;
- il CRM deve ricevere una copia dei dati;
- il magazzino deve aggiornare lo stato della spedizione;
- il servizio clienti deve sapere quando la sincronizzazione fallisce.
La connessione non sta più trasferendo soltanto un ordine.
Sta implementando una parte del processo operativo dell’azienda.
Questo cambiamento è importante perché modifica la natura del componente.
Da semplice collegamento tecnico, l’integrazione diventa il luogo in cui vengono coordinate regole, dati e responsabilità appartenenti a sistemi diversi.
Quando questa evoluzione non viene riconosciuta, la logica tende a distribuirsi in modo poco controllato:
- una parte nel sistema sorgente;
- una parte nel sistema di destinazione;
- una parte nello script di integrazione;
- alcune eccezioni in configurazioni manuali;
- altre regole nella conoscenza delle persone.
Il risultato può continuare a funzionare, ma diventa sempre più difficile capire dove venga presa una decisione e quale componente debba essere modificato.
Una singola connessione diretta può essere semplice.
Il problema emerge quando ogni sistema viene collegato direttamente a molti altri sistemi.
E-commerce ─────→ ERP │ ↑ ├───────────→ CRM │ ↑ └───────────→ PIM │Magazzino ──────────┘Con l’aumento delle connessioni, aumentano anche:
- i formati da conoscere;
- le credenziali da gestire;
- le regole duplicate;
- i punti in cui un dato può essere trasformato;
- le modalità di gestione degli errori;
- le dipendenze tra team e fornitori;
- le conseguenze di una modifica locale.
Supponiamo, per esempio, che il codice prodotto utilizzato nell’ERP sia diverso da quello presente nell’e-commerce.
La regola di conversione potrebbe essere implementata nella connessione tra e-commerce ed ERP.
Quando anche il CRM e il sistema di magazzino hanno bisogno dello stesso dato, la stessa logica potrebbe essere copiata in altre integrazioni.
A quel punto una variazione nella codifica richiede modifiche coordinate in più punti.
Il problema non è soltanto la duplicazione del codice. È la perdita di una responsabilità chiara.
Quale sistema possiede la corrispondenza tra gli identificativi? Dove deve essere aggiornata? Quale componente rappresenta la fonte corretta?
Le integrazioni diventano fragili quando le risposte a queste domande dipendono dalla storia del progetto anziché da una scelta esplicita.
Il termine middleware viene utilizzato per indicare soluzioni molto diverse.
Può essere:
- un piccolo servizio sviluppato internamente;
- una piattaforma di integrazione;
- un sistema di code e consumer;
- un orchestratore di workflow;
- un gateway;
- un insieme di funzioni serverless;
- un’applicazione che centralizza trasformazioni e instradamento.
La tecnologia, però, è meno importante della responsabilità che il componente deve assumere.
Un middleware è utile quando crea un confine comprensibile tra sistemi differenti.
Può occuparsi di:
- ricevere dati da una o più sorgenti;
- validarli;
- trasformarli in un formato condiviso;
- applicare regole di instradamento;
- coordinare più destinazioni;
- gestire retry e fallimenti;
- registrare lo stato delle operazioni;
- rendere osservabile il flusso;
- isolare le differenze tra le API;
- limitare la propagazione delle modifiche.
Una struttura minima può essere rappresentata così:
Sistema sorgente ↓Acquisizione ↓Validazione e normalizzazione ↓Regole di orchestrazione ↓Connettori verso i sistemi destinatari ↓Registrazione dell’esitoIl middleware non elimina la complessità.
La raccoglie in un punto in cui può essere resa più esplicita, coerente e controllabile.
Questo vantaggio esiste soltanto se il componente intermedio ha responsabilità chiare. In caso contrario, il middleware rischia di diventare un ulteriore livello opaco aggiunto tra sistemi già difficili da comprendere.
Prima di introdurre un middleware, considero utile verificare se il problema può essere risolto con una connessione più semplice.
Una connessione diretta è spesso sufficiente quando ricorrono queste condizioni.
Se un dato nasce in un sistema e deve essere utilizzato soltanto da un altro, non è sempre necessario introdurre un livello intermedio.
La relazione rimane comprensibile e il numero di dipendenze è limitato.
Rinominare alcuni campi, convertire una data o adattare una struttura semplice non richiede necessariamente una piattaforma dedicata.
La trasformazione deve però rimanere locale al rapporto tra i due sistemi e non rappresentare una regola di business condivisa.
Se un fallimento può essere individuato facilmente e l’operazione può essere ripetuta senza conseguenze, la semplicità della connessione diretta può essere un vantaggio.
Questo presuppone comunque la presenza di log, identificativi e una modalità di recupero.
Una connessione stabile, con requisiti limitati e sistemi poco variabili, può rimanere semplice per molto tempo.
Non tutte le integrazioni sono destinate a crescere.
Progettare fin dall’inizio una piattaforma estensibile per un flusso che probabilmente resterà isolato può produrre un investimento sproporzionato.
Deve essere evidente chi gestisce la connessione, dove si trova il codice o la configurazione, come viene distribuita e chi interviene quando qualcosa non funziona.
Una soluzione semplice con responsabilità esplicite è spesso più affidabile di un’architettura sofisticata senza una gestione definita.
Il passaggio a un componente intermedio diventa più ragionevole quando la complessità non appartiene più a una singola connessione.
Esistono alcuni segnali ricorrenti.
Se diversi sistemi devono ricevere lo stesso evento o la stessa informazione, collegarli tutti direttamente alla sorgente aumenta l’accoppiamento.
Ogni nuovo destinatario richiede una modifica o una nuova responsabilità nel sistema che produce il dato.
Un livello intermedio può separare la pubblicazione dell’informazione dalla sua distribuzione.
Quando più connessioni implementano la stessa normalizzazione, mappatura o regola di conversione, diventa utile individuare un punto in cui quella responsabilità possa essere gestita una sola volta.
Non significa necessariamente centralizzare ogni trasformazione.
Significa distinguere tra adattamenti specifici di una destinazione e regole che rappresentano un concetto condiviso dall’organizzazione.
Alcuni flussi non consistono in un semplice trasferimento.
Per completare un’operazione può essere necessario:
- ricevere un ordine;
- verificare il cliente nel CRM;
- recuperare i dati fiscali;
- creare il documento nell’ERP;
- prenotare la disponibilità;
- comunicare l’esito all’e-commerce.
In questo caso esiste un processo distribuito che deve mantenere uno stato e coordinare più passaggi.
Inserire tutta la logica in uno dei sistemi coinvolti può attribuirgli responsabilità che non gli appartengono.
Se un fallimento parziale lascia i sistemi in stati differenti, serve un punto capace di registrare ciò che è già avvenuto e decidere come proseguire.
Il middleware può gestire:
- tentativi successivi;
- sospensione del singolo elemento;
- compensazioni;
- interventi manuali;
- ripartenze da un passaggio specifico.
Senza questo coordinamento, ogni sistema conosce soltanto una parte della storia.
Un ERP legacy, un e-commerce SaaS e un’applicazione interna possono avere cicli di evoluzione molto diversi.
Il middleware può funzionare come livello di adattamento, evitando che ogni cambiamento di un sistema si propaghi immediatamente a tutti gli altri.
Questo non elimina la necessità di aggiornare le integrazioni, ma limita il numero di componenti direttamente coinvolti.
Quando i flussi sono numerosi, cercare gli errori separatamente in ogni applicazione diventa inefficiente.
Un componente intermedio può offrire una vista comune su:
- esecuzioni;
- elementi in attesa;
- errori;
- retry;
- tempi di elaborazione;
- destinazioni raggiunte;
- operazioni da recuperare.
Questa visibilità è spesso uno dei motivi più concreti per introdurre un middleware.
L’introduzione di un middleware non risolve automaticamente i problemi di integrazione.
Può, al contrario, creare un nuovo punto critico.
Questo accade quando il componente assume troppe responsabilità, rimane poco documentato o diventa l’unico luogo in cui è possibile comprendere il processo.
Centralizzare ogni regola può sembrare conveniente.
Con il tempo, però, il middleware può trasformarsi in una grande applicazione che conosce dettagli di tutti i sistemi e contiene una parte crescente della logica aziendale.
A quel punto ogni modifica richiede di intervenire nello stesso componente, anche quando la responsabilità dovrebbe appartenere a un dominio specifico.
Il middleware dovrebbe coordinare i sistemi, non sostituirli.
Per ridurre il numero di trasformazioni si può introdurre un modello dati comune.
È una scelta utile, ma difficile da mantenere quando il modello cerca di rappresentare ogni possibile variante di tutti i sistemi.
Un formato condiviso troppo astratto diventa complicato da comprendere. Un formato troppo vicino a un singolo sistema trasferisce invece il suo modello interno a tutte le altre applicazioni.
Il punto di equilibrio consiste nel rappresentare i concetti realmente condivisi, senza tentare di costruire un modello universale.
Alcuni strumenti permettono di creare integrazioni rapidamente attraverso interfacce visuali e configurazioni.
Questo accelera lo sviluppo iniziale, ma può rendere difficile capire:
- quale versione del flusso è attiva;
- dove viene applicata una regola;
- come vengono gestiti gli errori;
- quali modifiche sono state introdotte;
- come riprodurre un problema;
- come verificare il comportamento prima della pubblicazione.
La semplicità dell’interfaccia non garantisce la semplicità del sistema risultante.
Se ogni flusso passa attraverso lo stesso middleware, un problema della piattaforma può coinvolgere l’intera organizzazione.
La centralizzazione migliora la governance, ma aumenta anche l’impatto di alcuni guasti.
Diventano quindi importanti:
- isolamento dei flussi;
- limiti sulle risorse;
- code separate;
- gestione dei picchi;
- procedure di ripristino;
- distribuzioni controllate;
- monitoraggio della piattaforma stessa.
Il middleware deve essere trattato come un sistema operativo critico, non come un semplice contenitore di connessioni.
Cambiare tecnologia senza cambiare il modo in cui viene condivisa la conoscenza non risolve la dipendenza dalle singole persone.
Un middleware amministrato da un solo specialista può diventare fragile quanto gli script che avrebbe dovuto sostituire.
Servono convenzioni, documentazione, revisioni e responsabilità operative accessibili a più persone.
Uno dei modi più utili per mantenere comprensibile un’integrazione è distinguere responsabilità che spesso vengono mescolate.
Il trasporto riguarda il modo in cui un’informazione passa da un sistema all’altro.
Può avvenire tramite:
- richiesta HTTP;
- webhook;
- coda;
- evento;
- file;
- processo schedulato;
- lettura periodica di un’API.
Questa scelta risponde a domande come: quando viene trasferito il dato, con quale affidabilità e con quali garanzie di consegna?
La trasformazione adatta il dato tra modelli differenti.
Può includere:
- rinomina dei campi;
- conversione dei tipi;
- normalizzazione;
- composizione di valori;
- mapping degli identificativi;
- applicazione di valori predefiniti.
È importante distinguere le trasformazioni tecniche dalle decisioni di business.
Convertire una data è un adattamento tecnico. Decidere che alcuni clienti debbano seguire un flusso differente è una regola operativa.
Il processo stabilisce l’ordine delle operazioni e gestisce il loro risultato.
Risponde a domande come:
- quale passaggio viene eseguito per primo;
- cosa accade se un sistema non risponde;
- quali operazioni possono proseguire;
- quando l’intero processo è completo;
- chi deve intervenire in caso di blocco.
Separare questi livelli rende più semplice modificare il modo in cui un dato viene trasportato senza riscrivere le regole, oppure cambiare un connettore senza alterare il processo complessivo.
Ogni integrazione si basa su un contratto, anche quando non è stato documentato.
Il contratto comprende almeno:
- struttura dei dati;
- campi obbligatori;
- significato dei valori;
- identificativi;
- modalità di autenticazione;
- risposte attese;
- errori possibili;
- limiti di utilizzo;
- comportamento in caso di duplicazione;
- compatibilità tra versioni.
Quando questi aspetti rimangono impliciti, i sistemi possono avere interpretazioni differenti dello stesso scambio.
Un campo vuoto, per esempio, può significare:
- dato sconosciuto;
- valore non applicabile;
- richiesta di cancellazione;
- errore di acquisizione;
- informazione non ancora disponibile.
La struttura tecnica può essere valida, ma il significato operativo ambiguo.
Per questo un buon contratto non descrive soltanto il formato. Descrive anche le aspettative.
Per ogni informazione importante dovrebbe essere chiaro quale sistema ne rappresenta la fonte autorevole.
Il CRM possiede i dati del cliente? L’ERP possiede le condizioni commerciali? Il PIM possiede le descrizioni dei prodotti? Il magazzino possiede la disponibilità?
Senza questa distinzione, due sistemi possono modificare lo stesso dato e sovrascriversi reciprocamente.
Molti problemi di integrazione derivano dal modo in cui i record vengono riconosciuti.
Un cliente può avere:
- un ID nel CRM;
- un codice nell’ERP;
- un indirizzo email nell’e-commerce;
- un ulteriore identificativo nel sistema di assistenza.
Basare la sincronizzazione su un attributo modificabile, come l’email, può produrre errori quando quel valore cambia.
Serve quindi una strategia esplicita per correlare gli identificativi dei diversi sistemi.
Le API e i modelli dati cambiano.
Un contratto dovrebbe chiarire come vengono introdotte variazioni compatibili e incompatibili.
Aggiungere un campo facoltativo, per esempio, ha conseguenze diverse rispetto a cambiare il significato di un valore esistente.
L’obiettivo non è impedire il cambiamento, ma renderlo prevedibile.
Una delle decisioni più importanti riguarda il rapporto temporale tra i sistemi.
In una comunicazione sincrona, il sistema chiamante attende una risposta immediata.
Sistema A → richiesta → Sistema BSistema A ← risposta ← Sistema BQuesto modello è utile quando:
- il risultato serve per proseguire;
- il tempo di risposta è accettabile;
- il destinatario è sufficientemente disponibile;
- il fallimento può essere mostrato o gestito subito.
Il principale limite è l’accoppiamento temporale.
Se il sistema B non è disponibile, anche il processo del sistema A può bloccarsi.
In una comunicazione asincrona, il sistema produce un messaggio o registra un’attività che verrà elaborata successivamente.
Sistema A → coda o evento → elaborazione → Sistema BQuesto modello è utile quando:
- il risultato non deve essere immediato;
- è necessario assorbire picchi;
- il destinatario può essere temporaneamente indisponibile;
- si vogliono gestire retry indipendenti;
- più sistemi devono ricevere lo stesso evento.
L’asincronia migliora il disaccoppiamento, ma introduce nuove responsabilità:
- stato dei messaggi;
- duplicazioni;
- ordine di elaborazione;
- ritardi;
- code bloccate;
- monitoraggio;
- consistenza temporanea tra i sistemi.
Non è quindi una soluzione automaticamente più robusta. È un diverso modo di distribuire la complessità.
Nelle integrazioni è prudente assumere che una richiesta possa essere ricevuta più di una volta.
Un timeout, per esempio, non indica necessariamente che l’operazione non sia stata eseguita.
Il sistema chiamante potrebbe non aver ricevuto la risposta e decidere di riprovare, mentre il destinatario ha già completato l’operazione.
Senza protezioni, il secondo tentativo può creare:
- un nuovo ordine;
- un secondo pagamento;
- una notifica duplicata;
- un aggiornamento ripetuto;
- un nuovo record per la stessa entità.
Un’operazione idempotente produce lo stesso risultato anche quando viene richiesta più volte con lo stesso identificativo.
Per ottenerlo si possono utilizzare:
- chiavi di idempotenza;
- identificativi esterni univoci;
- vincoli nel database;
- controlli sullo stato;
- registrazione delle operazioni già completate.
Non tutte le azioni possono essere rese idempotenti nello stesso modo.
È però importante decidere esplicitamente cosa debba accadere quando una richiesta viene ripetuta.
Un’integrazione affidabile non deve soltanto trasferire dati.
Deve permettere di ricostruire il percorso di un singolo elemento.
Per un ordine, per esempio, dovrebbe essere possibile sapere:
- quando è stato ricevuto;
- da quale sistema;
- con quale identificativo;
- quali trasformazioni sono state applicate;
- quali sistemi sono stati contattati;
- quali operazioni sono riuscite;
- dove si è verificato un errore;
- se sono previsti altri tentativi;
- se serve un intervento manuale.
Questo richiede identificativi di correlazione coerenti tra i diversi passaggi.
Ordine: ORD-4821Correlation ID: 8f19... ↓Ricezione completata ↓Validazione completata ↓Creazione ERP fallita ↓Retry programmatoSenza questa continuità, ogni sistema contiene soltanto un frammento dell’accaduto.
L’osservabilità è particolarmente importante quando il processo è asincrono o coinvolge più destinazioni, perché non esiste una singola risposta capace di rappresentare l’esito complessivo.
Non sempre è necessario passare direttamente da alcune connessioni punto-punto a una piattaforma di integrazione completa.
È possibile procedere per fasi.
Il primo passo consiste nel creare un inventario:
- sistemi coinvolti;
- direzione dei flussi;
- dati trasferiti;
- frequenza;
- tecnologia;
- responsabile;
- criticità;
- modalità di recupero.
Anche un semplice diagramma può mostrare dipendenze che non erano più evidenti.
Prima di modificare l’architettura si possono introdurre convenzioni comuni per:
- logging;
- identificativi;
- gestione degli errori;
- retry;
- notifiche;
- credenziali;
- documentazione.
Questa standardizzazione riduce la fragilità senza richiedere una riscrittura immediata.
Le trasformazioni duplicate, la correlazione degli identificativi e le regole utilizzate da più flussi sono buoni candidati per essere spostati in componenti condivisi.
L’estrazione dovrebbe avvenire quando esiste un’esigenza concreta, non soltanto per anticipare una possibile riutilizzabilità futura.
Code ed eventi possono essere aggiunti nei punti in cui l’accoppiamento temporale crea problemi reali:
- servizi esterni instabili;
- elaborazioni lente;
- picchi di volume;
- più destinatari;
- necessità di retry indipendenti.
Non è necessario trasformare ogni comunicazione in un evento.
Anche mantenendo tecnologie differenti, può essere utile raccogliere in un unico punto lo stato operativo dei flussi più critici.
La centralizzazione della visibilità può produrre valore prima della centralizzazione dell’esecuzione.
La domanda “middleware o connessione diretta?” non ha una risposta universale.
Una connessione diretta ha un costo iniziale ridotto e mantiene semplice un problema realmente semplice.
Un middleware richiede un investimento maggiore, ma può ridurre duplicazioni, accoppiamento e difficoltà operative quando il numero di sistemi e responsabilità cresce.
La decisione dovrebbe considerare almeno:
- quanti sistemi partecipano al flusso;
- quante trasformazioni sono necessarie;
- quante regole vengono condivise;
- quanto spesso cambiano le API;
- quale volume deve essere gestito;
- quanto costa un errore;
- quanto è difficile recuperare;
- quante persone devono comprendere il processo;
- quanto è probabile che l’integrazione evolva.
Il punto non è scegliere l’architettura più sofisticata.
È scegliere quella che rende visibile e gestibile la complessità realmente presente.
Le integrazioni vengono spesso trattate come semplici attività di collegamento.
In realtà, rappresentano confini tra sistemi con modelli, tempi, responsabilità e vincoli differenti.
Una connessione diretta può essere robusta se il suo scopo rimane limitato, i contratti sono chiari e gli errori sono governabili.
Un middleware può essere utile quando coordina responsabilità condivise, isola i cambiamenti e rende osservabili processi distribuiti.
Entrambe le soluzioni diventano fragili quando la complessità rimane implicita.
Per questo, prima di scegliere una tecnologia, considero più importante rispondere ad alcune domande:
- chi possiede il dato;
- chi decide le regole;
- dove viene mantenuto lo stato;
- chi gestisce il fallimento;
- come si ricostruisce il percorso di un elemento;
- quale componente deve cambiare quando cambia un sistema.
Un’integrazione è governabile quando queste responsabilità sono comprensibili.
Il middleware, quando serve, non dovrebbe essere un luogo in cui nascondere la complessità.
Dovrebbe essere il punto in cui quella complessità viene resa esplicita.