Ogni sistema software sufficientemente complesso contiene workflow: sequenze di passi che devono avvenire in ordine, con gestione degli errori, retry e compensazioni in caso di fallimento. Che si tratti di un processo di onboarding utente, di un flusso di pagamento multi-step o di una pipeline di elaborazione dati, i workflow non triviali sono ovunque. La domanda non è se gestirli, ma come farlo in modo da scalare, tollerare i guasti e rimanere comprensibile a distanza di mesi.
In Nexora abbiamo progettato e deployato piattaforme di workflow automation in settori diversi: dall'e-commerce alla logistica, dai sistemi finanziari alle pipeline AI. Questo articolo raccoglie le lezioni imparate: quando scegliere un durable execution engine come Temporal, quando è più appropriato BPMN con Camunda, come progettare sistemi trigger/action alla Zapier, e come il Saga Pattern risolve le transazioni distribuite.
Il problema fondamentale: stato e guasti
Un workflow naïve è una serie di chiamate di funzione. Il problema emerge quando la funzione al passo 4 fallisce dopo che i passi 1, 2 e 3 hanno già prodotto side effect nel mondo reale — un'email inviata, un pagamento addebitato, un record creato nel database. Ripartire da zero non è sicuro; ignorare il fallimento non è corretto. Questo è il problema fondamentale che le piattaforme di workflow automation risolvono: mantenere lo stato del processo in modo duraturo, orchestrare i retry in modo intelligente e gestire la compensazione quando qualcosa va storto.
Comparazione degli approcci ai Workflow Engine
Temporal: il durable execution engine
Temporal è il workflow engine che ha cambiato il modo in cui molti team pensano all'affidabilità dei processi distribuiti. L'idea centrale è radicale nella sua semplicità: il codice del tuo workflow viene serializzato e rieseguito deterministicamente ogni volta che si riprende da un checkpoint. Il developer scrive codice ordinario — funzioni, loop, condizionali — e Temporal garantisce che quel codice venga portato a termine, anche se i worker si riavviano, la rete va giù o passano giorni tra un passo e l'altro.
L'architettura interna di Temporal si basa su un event log immutabile: ogni azione eseguita da un workflow viene registrata come evento. Quando un worker si riavvia, Temporal riproduce il log degli eventi per ricostruire lo stato in memoria, senza rieseguire gli activity — che potrebbero causare effetti collaterali doppi. Questo approccio, chiamato event sourcing del workflow, è la chiave della sua durabilità. Il risultato pratico è che un workflow Temporal può dormire per settimane aspettando un segnale esterno, occupare zero risorse computazionali nel frattempo, e riprendere esattamente da dove si era fermato.
import { proxyActivities, sleep, defineSignal, setHandler } from "@temporalio/workflow";
import type { OrderActivities } from "./activities";
const {
reserveInventory,
processPayment,
createShipment,
sendConfirmationEmail,
releaseInventory,
refundPayment,
} = proxyActivities<OrderActivities>({
startToCloseTimeout: "30 seconds",
retry: { maximumAttempts: 3, initialInterval: "1s", backoffCoefficient: 2 },
});
export const cancelSignal = defineSignal<[{ reason: string }]>("cancel");
export async function orderFulfillmentWorkflow(orderId: string): Promise<void> {
let cancelRequested = false;
setHandler(cancelSignal, ({ reason }) => {
console.log(`Cancellation requested for order ${orderId}: ${reason}`);
cancelRequested = true;
});
await reserveInventory(orderId);
if (cancelRequested) {
await releaseInventory(orderId);
return;
}
let paymentProcessed = false;
try {
await processPayment(orderId);
paymentProcessed = true;
await createShipment(orderId);
// Temporal can sleep for days without consuming resources
await sleep("24 hours");
await sendConfirmationEmail(orderId);
} catch (err) {
// Saga compensation: undo side effects in reverse order
if (paymentProcessed) await refundPayment(orderId);
await releaseInventory(orderId);
throw err;
}
}Il vantaggio nascosto di Temporal non è solo la durabilità: è la testabilità. I workflow Temporal possono essere testati in modo deterministico con mock degli activity, senza infrastruttura esterna. Questo rende il TDD dei processi distribuiti finalmente praticabile.
Netflix Conductor: orchestrazione dichiarativa
Mentre Temporal usa un approccio code-first, Netflix Conductor definisce i workflow in JSON o YAML. Ogni workflow è un grafo di task; ogni task corrisponde a un worker che esegue la logica di business. Il Conductor server gestisce il routing, il retry e lo stato; i worker sono semplici consumatori di code indipendenti dal linguaggio. Questo disaccoppiamento è il punto di forza principale: team diversi possono implementare worker in linguaggi diversi senza condividere nulla tranne il contratto del task. Netflix dichiara di eseguire oltre un miliardo di workflow al mese su questa architettura.
name: customer-onboarding
version: 1
tasks:
- name: validate_kyc
taskReferenceName: kyc_check
type: SIMPLE
inputParameters:
userId: ${workflow.input.userId}
retryCount: 2
retryDelaySeconds: 30
- name: setup_account
taskReferenceName: account_setup
type: SIMPLE
inputParameters:
userId: ${workflow.input.userId}
kycResult: ${kyc_check.output.result}
- name: parallel_setup
taskReferenceName: parallel_tasks
type: FORK_JOIN
forkTasks:
- - name: setup_billing
taskReferenceName: billing_setup
type: SIMPLE
inputParameters:
accountId: ${account_setup.output.accountId}
- - name: setup_notifications
taskReferenceName: notification_setup
type: SIMPLE
inputParameters:
userId: ${workflow.input.userId}
- name: join_tasks
taskReferenceName: join_parallel_tasks
type: JOIN
joinOn:
- billing_setup
- notification_setup
- name: send_welcome
taskReferenceName: email_task
type: SIMPLE
inputParameters:
userId: ${workflow.input.userId}
accountId: ${account_setup.output.accountId}BPMN: quando i processi diventano collaborativi
Business Process Model and Notation (BPMN 2.0) è lo standard ISO per modellare processi di business. A differenza di Temporal e Conductor, BPMN ha una rappresentazione visuale come first-class citizen: il diagramma non è documentazione del codice, è il codice stesso — o almeno la sua specifica eseguibile. Questo lo rende prezioso in contesti in cui i processi devono essere verificabili da stakeholder non tecnici, soggetti a compliance o includono fasi di approvazione umana.
- Processi human-in-the-loop: approvazioni, escalation e revisioni manuali in cui un task aspetta l'input di una persona reale
- Conformità normativa: settori come bancario, assicurativo e sanitario richiedono audit trail e processi certificabili
- Collaborazione cross-team: il diagramma BPMN diventa il linguaggio comune tra business analyst e sviluppatori
- Long-running processes: pratiche di mutuo, procedure di gara e processi che possono durare settimane o mesi
Architetture Trigger/Action: il modello Zapier
Le piattaforme trigger/action hanno democratizzato l'automazione per gli utenti non tecnici. L'architettura è elegante nella sua semplicità: un evento (trigger) attiva una catena di azioni. Internamente, una piattaforma come Zapier funziona con un sistema di polling o webhook per rilevare i trigger, una coda di lavoro per distribuire l'esecuzione delle azioni e un modello di connettore che astrae l'autenticazione e l'interazione con ogni servizio esterno. Costruire questa architettura internamente — per integrare sistemi aziendali senza richiedere codice custom — è un caso d'uso valido in molte organizzazioni.
interface Trigger {
id: string;
appId: string;
type: "webhook" | "polling";
config: Record<string, unknown>;
}
interface Action {
id: string;
appId: string;
operationId: string;
inputMapping: Record<string, string>; // Template expressions mapping trigger output to action input
}
interface Zap {
id: string;
trigger: Trigger;
actions: Action[];
enabled: boolean;
}
class TriggerActionEngine {
async executeZap(zapId: string, triggerPayload: unknown): Promise<void> {
const zap = await this.zapRepository.findById(zapId);
if (!zap.enabled) return;
const context: Record<string, unknown> = { trigger: triggerPayload };
for (const action of zap.actions) {
const connector = this.connectorRegistry.get(action.appId);
const resolvedInput = this.templateEngine.resolve(action.inputMapping, context);
try {
const result = await connector.execute(action.operationId, resolvedInput);
// Each action output is available for subsequent actions via context
context[`action_${action.id}`] = result;
} catch (error) {
await this.errorHandler.handle(zapId, action.id, error);
break; // Stop chain on unrecoverable error
}
}
}
}Il Saga Pattern per le transazioni distribuite
In un sistema distribuito non esiste una transazione atomica che abbraccia più servizi. Il Saga Pattern è la risposta matura a questo problema: invece di una singola transazione ACID, una saga è una sequenza di transazioni locali, ciascuna delle quali pubblica un evento o messaggio che innesca il passo successivo. Se un passo fallisce, vengono eseguite transazioni di compensazione — azioni che annullano semanticamente i passi precedenti, anche se non li annullano tecnicamente nel senso ACID del termine.
- 1Choreography-based saga: ogni servizio pubblica eventi a cui altri servizi reagiscono. Nessun coordinatore centrale, massima autonomia dei servizi, ma difficile da tracciare e debuggare a scala.
- 2Orchestration-based saga: un orchestratore centrale — idealmente implementato su Temporal o Conductor — dirige i passi e gestisce le compensazioni. Più complesso architetturalmente, ma molto più osservabile e testabile.
Quando scegliere cosa
La scelta tra questi approcci dipende da tre variabili: la complessità del processo, il profilo degli stakeholder e i requisiti di osservabilità. Temporal è la scelta default per processi tecnici complessi sviluppati da un team di ingegneri: offre il miglior rapporto tra potenza e costo operativo. BPMN con Camunda è la scelta giusta quando i processi devono essere validati da business analyst, includono fasi di approvazione umana o sono soggetti a audit. Conductor brilla nei sistemi poliglotti in cui team diversi implementano worker indipendenti. Le architetture trigger/action sono appropriate per l'integrazione di servizi SaaS, l'automazione low-code o i prodotti rivolti a utenti non tecnici.
Osservabilità come requisito di prima classe
Indipendentemente dall'engine scelto, un workflow in produzione senza osservabilità adeguata è una bomba a orologeria. Ogni piattaforma di workflow automation deve esporre: la lista delle istanze attive con il loro stato corrente, lo storico delle esecuzioni con i log di ogni passo, le metriche di latenza per attività e workflow, e gli alert per istanze bloccate o con retry esauriti. Temporal fornisce una UI chiamata Temporal Web e un namespace di metriche Prometheus. Per entrambi i sistemi, è consigliabile aggiungere un livello di tracciamento distribuito con OpenTelemetry per correlare le trace dei workflow con quelle degli altri servizi.
Conclusioni
Le piattaforme di workflow automation non sono un'astrazione opzionale — sono l'infrastruttura fondamentale per costruire sistemi distribuiti affidabili. La scelta dell'engine sbagliato genera debito tecnico che emerge nei momenti peggiori: durante un incidente in produzione, durante un audit di compliance, o quando un nuovo ingegnere deve capire come funziona un processo critico. Investire tempo nella scelta deliberata dell'approccio giusto, e nella costruzione di un layer di osservabilità adeguato, paga dividendi per anni.
Tag
Prossimo passo
Hai bisogno di implementare questa architettura?
Il nostro team di ingegneria costruisce e scala i sistemi descritti in questo articolo. Dalla discovery alla produzione — con risultati misurabili.
Articoli correlati