Ricapitoliamo rapidamente perché le tradizionali API REST basate su CRUD non sono sufficienti quando si tratta di gestire flussi di lavoro complessi:
- Mancanza di rappresentazione dello stato
- Difficoltà nella gestione di processi a lungo termine
- Nessun supporto integrato per rollback o transazioni compensative
- Capacità limitata di rappresentare logiche di business complesse
Queste limitazioni diventano dolorosamente evidenti quando si cerca di modellare processi reali come l'evasione degli ordini, flussi di approvazione a più fasi o qualsiasi scenario in cui è necessario mantenere lo stato e gestire i fallimenti in modo elegante.
Entrano in gioco le API REST guidate dagli eventi
Quindi, come affrontiamo queste sfide rispettando comunque i principi RESTful? La risposta sta nell'abbracciare architetture guidate dagli eventi all'interno del nostro design API. Ecco come possiamo ripensare il nostro approccio:
1. Macchine a stati orientate alle risorse
Invece di pensare in termini di operazioni CRUD, considera le tue risorse come macchine a stati. Ogni risorsa può avere un insieme di stati validi e transizioni tra di essi.
{
"id": "order-123",
"state": "pending",
"allowedTransitions": ["confirm", "cancel"]
}
In questo modello, le transizioni di stato diventano il modo principale di interagire con le risorse. Puoi esporre queste transizioni come sotto-risorse o attraverso azioni personalizzate.
2. Operazioni asincrone
Per processi a lungo termine, implementa operazioni asincrone. Quando un client avvia un flusso di lavoro complesso, restituisci uno stato 202 Accepted insieme a una risorsa che rappresenta lo stato dell'operazione.
POST /orders/123/fulfill HTTP/1.1
Host: api.example.com
HTTP/1.1 202 Accepted
Location: /operations/456
Il client può quindi interrogare la risorsa dell'operazione per verificarne lo stato:
{
"id": "operation-456",
"status": "in_progress",
"percentComplete": 75,
"result": null
}
3. Event Sourcing
Implementa l'event sourcing per mantenere una cronologia completa dei cambiamenti di stato. Questo approccio consente una migliore auditabilità e abilita scenari di rollback complessi.
{
"id": "order-123",
"events": [
{"type": "OrderCreated", "timestamp": "2023-05-01T10:00:00Z"},
{"type": "PaymentReceived", "timestamp": "2023-05-01T10:05:00Z"},
{"type": "ShippingArranged", "timestamp": "2023-05-01T10:10:00Z"}
],
"currentState": "shipped"
}
4. Rollback basati su compensazione
Per processi a più fasi, implementa rollback basati su compensazione. Ogni fase del processo dovrebbe avere un'azione compensativa corrispondente che può annullarne gli effetti.
{
"id": "workflow-789",
"steps": [
{"action": "reserveInventory", "compensation": "releaseInventory", "status": "completed"},
{"action": "chargeCreditCard", "compensation": "refundPayment", "status": "failed"}
],
"currentStep": 1,
"status": "rolling_back"
}
Consigli pratici per l'implementazione
Ora che abbiamo coperto la teoria, vediamo alcuni consigli pratici per implementare questi concetti:
1. Usa i controlli ipermediali
Sfrutta HATEOAS (Hypertext As The Engine Of Application State) per guidare i client attraverso flussi di lavoro complessi. Includi link alle azioni possibili in base allo stato attuale di una risorsa.
{
"id": "order-123",
"state": "pending",
"links": [
{"rel": "confirm", "href": "/orders/123/confirm", "method": "POST"},
{"rel": "cancel", "href": "/orders/123/cancel", "method": "POST"}
]
}
2. Implementa Webhook per aggiornamenti in tempo reale
Per processi a lungo termine, considera l'implementazione di webhook per notificare i client dei cambiamenti di stato, piuttosto che richiedere loro di interrogare continuamente per aggiornamenti.
3. Usa chiavi di idempotenza
Quando si gestiscono operazioni asincrone, utilizza chiavi di idempotenza per garantire che le operazioni non vengano accidentalmente duplicate a causa di problemi di rete o tentativi di ripetizione del client.
POST /orders/123/fulfill HTTP/1.1
Host: api.example.com
Idempotency-Key: 5eb63bbbe01eeed093cb22bb8f5acdc3
4. Implementa il pattern Saga per transazioni distribuite
Per flussi di lavoro complessi che coinvolgono più servizi, considera l'implementazione del pattern Saga per gestire transazioni distribuite e rollback.
Possibili insidie
Prima di precipitarti a rifattorizzare tutte le tue API, sii consapevole di queste potenziali sfide:
- Aumento della complessità nel design e nell'implementazione delle API
- Curva di apprendimento più alta per i consumatori delle API
- Possibile sovraccarico delle prestazioni dovuto all'archiviazione e all'elaborazione degli eventi
- Necessità di una gestione robusta degli errori e di meccanismi di ripetizione
Conclusione
Progettare API REST per flussi di lavoro guidati dagli eventi richiede un cambiamento di mentalità dalle semplici operazioni CRUD a un approccio più sfumato che considera stato, processi asincroni e logiche di business complesse. Abbracciando concetti come macchine a stati, event sourcing e rollback basati su compensazione, possiamo creare API più robuste e flessibili che rappresentano meglio i processi del mondo reale.
Ricorda, l'obiettivo non è rendere le cose inutilmente complesse, ma creare API che possano gestire le complessità del tuo dominio aziendale rispettando comunque i principi RESTful. Come con qualsiasi decisione architettonica, considera il tuo caso d'uso specifico e i requisiti prima di immergerti.
Ora vai e progetta delle API guidate dagli eventi eccezionali! E se ti trovi a desiderare la semplicità del CRUD... beh, c'è sempre GraphQL. Ma questa è una storia per un altro giorno.
"Il segreto per costruire grandi app è non costruire mai grandi app. Suddividi le tue applicazioni in piccoli pezzi. Poi, assembla quei pezzi testabili e a misura di morso nella tua grande applicazione."— Justin Meyer
Buona programmazione!