Approvvigionamento di eventi e REST


17

Mi sono imbattuto nel design di Event Sourcing e vorrei utilizzarlo in un'applicazione in cui è necessario un client REST (RESTful per essere precisi). Tuttavia, non riesco a collegarli insieme poiché REST è abbastanza simile a CRUD e l'approvvigionamento di eventi è basato su attività. Mi chiedevo come puoi progettare la creazione di comandi in base alle richieste al server REST. Considera questo esempio:

Con REST è possibile inserire un nuovo stato nella risorsa chiamata File. In una richiesta è possibile inviare un nuovo nome di file, è possibile modificare la cartella principale e / o cambiare il proprietario del file e così via.

Come costruire il server in modo da poter utilizzare il sourcing degli eventi. Stavo pensando a queste possibilità:

  1. Determinare il server di quali campi sono stati modificati e creano comandi appropriati ( RenameFileCommand, MoveFileCommand, ChangeOwnerCommand, ...) e la spedizione questi singolarmente. Tuttavia, in questa configurazione, ciascuno dei comandi può non riuscire a lasciare gli altri fuori transazione e quindi fuori dalla modifica "atomica" della risorsa.

  2. Invio solo comando ( UpdateFileCommand) e nel gestore comando, più precisamente nel complesso, determinare quali campi sono stati modificati e inviare singoli eventi invece ( FileRenamedEvent, FileMovedEvent, OwnerChangedEvent, ...)

  3. Questo non mi piace affatto: nella richiesta al server vorrei specificare nelle intestazioni quale comando usare, perché l'interfaccia utente è ancora basata su attività (ma la comunicazione avviene tramite REST). Tuttavia fallirà in qualsiasi altro uso della comunicazione REST (ad es. In app esterne) poiché non sono tenuti a modificare solo un campo in una richiesta. Inoltre porto un accoppiamento piuttosto grande nell'interfaccia utente, REST e back-end basato su ES.

Quale preferiresti o c'è un modo migliore per gestirlo?

Nota a margine: app scritta in Java e Axon Framework per l'approvvigionamento di eventi.


Certamente non il terzo. Vorrei fare il primo, ma devo pensarci.
inf3rno,

Hai delle domande sul fatto che una singola richiesta HTTP possa generare più comandi? Capisco bene? A parer mio. dovrebbe essere in grado di farlo, ma non ho letto DDD per un po ', quindi devo controllare un codice di esempio su come implementarlo.
inf3rno,

Ho trovato un esempio, ma è CRUD. github.com/szjani/predaddy-issuetracker-sample/blob/3.0/src/hu/… Chiederò all'autore qual è la sua opinione, lui sa di più su DDD di me.
inf3rno,

1
Il mio è che dovresti usare più comandi in una "unità di lavoro" se vuoi una coerenza immediata. Se stai parlando di un'eventuale coerenza, la domanda non ha senso per me. Un'altra possibile soluzione per inviare un CompositeCommand che può contenere i Comandi che si desidera eseguire atomicamente. Può essere una raccolta semplice, l'unica cosa che conta è che l'autobus possa gestirla correttamente.
inf3rno,

1
Secondo lui dovresti cercare di ottenere una relazione 1: 1 tra comandi e richieste HTTP. Se non riesci a farlo (è un cattivo odore), allora dovresti usare la massa (l'ho chiamato composito) per renderlo atomico.
inf3rno,

Risposte:


11

Penso che potresti avere un processo utente per l'implementazione non corrispondente qui.

Primo: un utente vorrà onestamente eseguire contemporaneamente più modifiche a un file? Una ridenominazione (che può o meno includere un cambio di percorso?), Un cambio di proprietà e forse un cambiamento del contenuto del file (per ragioni di argomento) sembrano azioni separate.

Prendiamo il caso in cui la risposta è "sì" - i tuoi utenti vogliono davvero apportare queste modifiche contemporaneamente.

In quel caso, mi raccomando vivamente contro ogni implementazione che invia più eventi - RenameFileCommand, MoveFileCommand, ChangeOwnerCommand- per rappresentare questo unico intento utente.

Perché? Perché gli eventi possono fallire. Forse è estremamente raro, ma l'utente ha inviato un'operazione che sembrava atomica: se un singolo evento a valle fallisce, lo stato dell'applicazione non è più valido.

Stai anche invitando i rischi legati alla razza su una risorsa che è chiaramente condivisa tra ciascuno dei gestori di eventi. Dovrai scrivere "ChangeOwnerCommand" in modo tale che il nome e il percorso del file non contino, poiché potrebbero non essere aggiornati al momento della ricezione del comando.

Quando si implementa un sistema riposante non guidato da eventi con lo spostamento e la ridenominazione dei file, preferisco garantire la coerenza utilizzando qualcosa di simile a un sistema eTag: assicurarsi che la versione della risorsa che si sta modificando sia la versione che l'utente ha recuperato l'ultima volta, e se fallisce è stato modificato da allora. Ma se stai inviando più comandi per questa singola operazione utente, dovrai incrementare la versione della tua risorsa dopo ogni comando - quindi non hai modo di sapere che la risorsa che l'utente sta modificando è davvero la stessa versione della risorsa che ha letto l'ultima volta .

Quello che intendo con ciò è: cosa succede se qualcun altro esegue un'altra operazione sul file quasi nello stesso momento. I 6 comandi potrebbero impilarsi in qualsiasi ordine. Se avessimo solo 2 comandi atomici, il comando precedente potrebbe avere esito positivo e il comando successivo potrebbe non riuscire "la risorsa è stata modificata dall'ultimo recupero". Ma non c'è protezione contro questo quando i comandi non sono atomici, quindi la coerenza del sistema viene violata.

È interessante notare che c'è un movimento verso qualcosa come l'architettura basata su eventi in REST, chiamato "Riposo senza PUT", raccomandato nel radar della tecnologia Thoughtworks, gennaio 2015 . C'è un blog molto più lungo su Riposo senza PUT qui .

Fondamentalmente, l'idea è che POST, PUT, DELETE e GET vadano bene per le piccole applicazioni, ma quando è necessario iniziare a supporre come interpretare put e post ed eliminare all'altra estremità, si introduce l'accoppiamento. (ad es. "quando ELIMCO la risorsa associata al mio conto bancario, il conto deve essere chiuso") E la soluzione proposta è quella di trattare il REST in modo più proveniente da un evento. vale a dire POST l'utente intento come una risorsa evento singolo.

L'altro caso è più semplice. Se i tuoi utenti non vogliono fare tutte quelle operazioni contemporaneamente, non lasciarle. POST un evento per ogni intento dell'utente. Ora puoi utilizzare il versioning etag sulle tue risorse.

Per quanto riguarda le altre applicazioni che utilizzano un'API molto diversa per le tue risorse. Puzza di guai. Puoi costruire una facciata della vecchia API in cima alla tua API RESTful e puntarla verso la facciata? cioè esporre un servizio che esegue più aggiornamenti di un file in sequenza tramite il server REST?

Se non costruisci l'interfaccia RESTful sopra la vecchia soluzione, né costruisci una facciata della vecchia interfaccia sopra la soluzione REST e provi a mantenere entrambe le API che puntano a una risorsa di dati condivisa, sperimenterai grossi mal di testa.


Riesco a vedere la discrepanza, tuttavia, da REST in linea di principio è possibile aggiornare lo stato di più campi INSERENDO il nuovo stato alla risorsa (da una rappresentazione). Ecco come funziona REST per definizione: trasferimento dello stato rappresentativo, il rovescio della medaglia è che l'intento dell'utente è perso nel processo. Inoltre, REST è quasi uno standard per API esterne. Voglio semplicemente progettare l'app in modo da poterle utilizzare entrambe. La rimozione di PUT è come una soluzione alternativa a causa di ES. Da quello che posso vedere, un comando di aggiornamento che emette più eventi è fattibile finché il comando e la gestione degli eventi sono in una transazione.
rossa,

Etag è qualcosa che vorrei fare comunque. Anche il tuo link a "post di blog più lunghi" è lo stesso del primo link.
rossa,

Tornerò su questo dopo qualche considerazione con più pensieri riguardo a PUT. Certamente, rimuovere PUT non è solo "una soluzione alternativa per ES". Ho corretto il link al blog.
perfezionista il

4

Proprio ora mi sono imbattuto nel seguente articolo, che incoraggia a specificare i nomi dei comandi nella richiesta al server nell'intestazione Content-Type (mentre seguo 5 livelli di tipo di supporto).

Nell'articolo, menzionano che lo stile RPC è dannoso per REST e suggeriscono di estendere Content-Type per specificare il nome del comando:

Un approccio comune è l'utilizzo di risorse in stile RPC, ad esempio / api / InventoryItem / {id} / rename. Mentre ciò sembra eliminare la necessità di verbi arbitrari, è contro la presentazione orientata alle risorse di REST. Dobbiamo ricordare che una risorsa è un sostantivo e un verbo HTTP è il verbo / azione e i messaggi auto-descrittivi (uno dei principi di REST) ​​sono il veicolo per trasmettere altri assi di informazioni e intenti. In effetti il ​​comando nel payload del messaggio HTTP dovrebbe essere sufficiente per esprimere qualsiasi azione arbitraria. Tuttavia, fare affidamento sul corpo del messaggio ha problemi a sé stanti poiché il corpo viene solitamente distribuito come flusso e bufferizzato il corpo nella sua interezza prima di identificare l'azione non è sempre possibile né saggio.

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

L'articolo è qui: http://www.infoq.com/articles/rest-api-on-cqrs

Puoi leggere ulteriori informazioni su 5 livelli di tipo di media qui: http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html


Anche se stanno esponendo gli eventi del dominio all'API REST, che considererei una cattiva pratica, mi piace la soluzione in quanto non crea un nuovo "protocollo" esclusivamente per CQRS, sia che invii nomi di comandi nel corpo o in più intestazione e rimane fedele ai principi RESTful.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.