Sourcing degli eventi e REST

16

Mi sono imbattuto in Event Sourcing design e vorrei utilizzarlo in un'applicazione in cui è necessario un client REST (RESTful per la precisione). Tuttavia non riesco a collegarli insieme dato che REST è abbastanza simile a CRUD e il sourcing di eventi è basato su attività. Mi stavo chiedendo come è possibile progettare la creazione di comandi basati su richieste al server REST. Considera questo esempio:

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

Come costruire il server in modo che io possa utilizzare l'event sourcing. Stavo pensando a queste possibilità:

  1. Determina sul server quali campi sono stati modificati e crea i comandi appropriati ( RenameFileCommand , MoveFileCommand , ChangeOwnerCommand , ...) e li invii singolarmente. Tuttavia, in questa configurazione, ognuno dei comandi può fallire, lasciando gli altri fuori dalla transazione e quindi dalla modifica "atomica" alla risorsa.

  2. Invia un solo comando ( UpdateFileCommand ) e nel gestore comandi, più precisamente nell'aggregato, determina quali campi sono stati modificati e invia invece singoli eventi ( FileRenamedEvent , FileMovedEvent , OwnerChangedEvent , ...)

  3. Questo non mi piace affatto: nella richiesta al server specificherò nelle intestazioni quale comando utilizzare, perché l'interfaccia utente è ancora basata su attività (ma la comunicazione viene eseguita tramite REST). Tuttavia, fallirà in qualsiasi altro uso della comunicazione REST (ad es. In app esterne) poiché non sono in grado di modificare solo un campo in una richiesta. Inoltre porto un accoppiamento abbastanza grande nel backend UI, REST e ES.

Quale preferisci o esiste un modo migliore per gestirlo?

Nota a margine: app scritta in Java e Axon Framework per l'event-sourcing.

    
posta redhead 17.02.2015 - 07:44
fonte

2 risposte

11

Penso che potresti avere un processo utente per la mancata corrispondenza dell'implementazione qui.

Primo: un utente vuole onestamente eseguire simultaneamente più modifiche a un file? Un rinominare (che può includere o non includere un cambio di percorso?), Il cambio di proprietà e forse la modifica del contenuto del file (per ragioni di discussione) sembrano azioni separate.

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

In questo caso, raccomando vivamente contro qualsiasi implementazione che invia più eventi - RenameFileCommand , MoveFileCommand , ChangeOwnerCommand - per rappresentare questo intento utente singolo .

Perché? Perché gli eventi possono fallire. Forse è estremamente raro, ma il tuo utente ha presentato un'operazione che sembrava atomica: se uno solo degli eventi downstream ha esito negativo, lo stato dell'applicazione non è più valido.

Stai anche invitando i rischi della corsa su una risorsa che è chiaramente condivisa tra ognuno dei gestori di eventi. Dovrai scrivere "ChangeOwnerCommand" in modo che il nome del file e il percorso del file non siano importanti, perché potrebbero non essere aggiornati al momento della ricezione del comando.

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

Ciò che intendo è - cosa succede se qualcun altro esegue un'altra operazione sul file quasi alla stessa ora. I 6 comandi potrebbero accumularsi in qualsiasi ordine. Se avessimo solo 2 comandi atomici, il precedente comando potrebbe avere successo e il successivo comando potrebbe fallire "la risorsa è stata modificata dall'ultimo recupero". Ma non c'è protezione contro questo quando i comandi non sono atomici, quindi la coerenza del sistema è violata.

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

Essenzialmente, l'idea è che POST, PUT, DELETE e GET vanno bene per le piccole applicazioni, ma quando hai bisogno di iniziare a pensare a come put e post e delete potrebbero essere interpretati dall'altra parte, introduci l'accoppiamento. (ad esempio "quando CANCELLO la risorsa associata al mio conto bancario, l'account deve essere chiuso") e la soluzione proposta è quella di trattare REST in un modo più elaborato da un evento. Ad esempio, consente a POST l'intento dell'utente come singola risorsa evento.

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 usare il controllo di versione di etag sulle tue risorse.

Come per le altre applicazioni che utilizzano un'API molto diversa per le tue risorse. Puzza di guai. Puoi costruire una facciata della vecchia API sopra l'API RESTful e indirizzarla alla facciata? esporre un servizio che esegue più aggiornamenti a un file in sequenza tramite il server REST?

Se non costruisci l'interfaccia RESTful sulla vecchia soluzione, né costruisci una facciata della vecchia interfaccia sopra la soluzione REST e tenti di mantenere entrambe le API puntate su una risorsa dati condivisa, avvertirai grossi grattacapi .

    
risposta data 17.02.2015 - 12:18
fonte
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 (pur seguendo 5 livelli di tipo di media).

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

One common approach is using RPC-style resources for example /api/InventoryItem/{id}/rename. While this seemingly removes the need for arbitrary verbs, it is against REST's resource-oriented presentation. We need to be reminded that a resource is a noun and HTTP verb is the verb/action and self-descriptive messages (one of the tenets of REST) are the vehicle to convey other axes of information and intent. In fact the command in the payload of the HTTP message should be enough to express any arbitrary action. However, relying on the body of the message has problems of its own since the body is usually delivered as a stream and buffering the body in its entirety before identifying action is not always possible nor wise. Here we present an approach based on level 4 (domain model) of 5LMT where the command type is presented as a parameter of the Content-Type header:

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: link

Puoi leggere ulteriori informazioni su 5 livelli di tipo di media qui: link

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 nell'intestazione extra e rimane fedele ai principi RESTful.

    
risposta data 18.09.2015 - 22:08
fonte

Leggi altre domande sui tag