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!