Come si adatta un'API REST per un dominio basato su comando / azione?


24

In questo articolo l'autore afferma che

A volte, è necessario esporre un'operazione nell'API che intrinsecamente non è RESTful.

e quello

Se un'API ha troppe azioni, significa che è stata progettata con un punto di vista RPC anziché utilizzare i principi RESTful o che l'API in questione è naturalmente più adatta per un modello di tipo RPC.

Ciò riflette ciò che ho letto e ascoltato anche altrove.

Tuttavia lo trovo abbastanza confuso e vorrei capire meglio la questione.

Esempio I: arresto di una macchina virtuale tramite un'interfaccia REST

Ci sono, credo, due modi fondamentalmente diversi per modellare un arresto di una macchina virtuale. Ogni modo potrebbe avere alcune varianti, ma concentriamoci sulle differenze fondamentali per ora.

1. Patch la proprietà dello stato della risorsa

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

(In alternativa, PUTnella risorsa secondaria /api/virtualmachines/42/state.)

La macchina virtuale verrà arrestata in background e in un secondo momento a seconda del tempo in cui lo spegnimento avrà esito positivo o no, lo stato potrebbe essere aggiornato internamente con "spegnimento".

2. PUT o POST sulla proprietà actions della risorsa

PUT /api/virtualmachines/42/actions
Content-Type:application/json  

{ "type": "shutdown" }

Il risultato è esattamente lo stesso del primo esempio. Lo stato verrà aggiornato in "arresto" immediatamente e forse eventualmente in "spegnimento".

Entrambi i disegni sono RESTful?

Quale design è migliore?

Esempio II: CQRS

E se disponessimo di un dominio CQRS con molte di tali "azioni" (ovvero comandi) che potrebbero potenzialmente portare ad aggiornamenti di più aggregati o che non possono essere associati a operazioni CRUD su risorse e risorse secondarie concrete?

Dovremmo provare a modellare quanti comandi concreti crea o aggiorna su risorse concrete, ove possibile (seguendo il primo approccio dall'esempio I) e utilizzare "endpoint di azione" per il resto?

O dovremmo associare tutti i comandi agli endpoint dell'azione (come nel secondo approccio dell'esempio I)?

Dove dovremmo tracciare la linea? Quando il design diventa meno RESTful?

Un modello CQRS è più adatto per un'API come RPC?

Secondo il testo citato sopra lo è, come ho capito.

Come puoi vedere dalle mie molte domande, sono un po 'confuso su questo argomento. Potete aiutarmi a comprenderlo meglio?


la "creazione di un'azione" non sembra RESTful, tranne se l'azione eseguita ha il proprio identificatore di risorsa in seguito. Altrimenti, modificare la proprietà "state" tramite PATCH o PUT ha più senso. Per la parte CQRS, non ho ancora una buona risposta.
Fabian Schmengler,

3
@Laiv Niente di sbagliato in questo. È una domanda accademica, mi piacerebbe avere una comprensione più profonda della questione.
leifbattermann,

Risposte:


19

Nel primo caso (arresto delle macchine virtuali), non prenderei in considerazione nessuna delle alternative OP RESTful. Certo, se si utilizza il modello di maturità di Richardson come parametro di riferimento, sono entrambe API leve 2 perché utilizzano risorse e verbi.

Nessuno dei due, tuttavia, utilizza i controlli hypermedia e, a mio avviso, è l'unico tipo di REST che differenzia il design dell'API RESTful da RPC. In altre parole, mantieni i livelli 1 e 2 e nella maggior parte dei casi avrai un'API in stile RPC.

Per modellare due modi diversi di arrestare una macchina virtuale, esponerei la macchina virtuale stessa come una risorsa che (tra le altre cose) contiene collegamenti:

{
    "links": [{
        "rel": "shut-down",
        "href": "/vms/1234/fdaIX"
    }, {
        "rel": "power-off",
        "href": "/vms/1234/CHTY91"
    }],
    "name": "Ploeh",
    "started": "2016-08-21T12:34:23Z"
}

Se un client desidera arrestare la macchina Ploehvirtuale, deve seguire il collegamento con il shut-downtipo di relazione. (Normalmente, come indicato nel ricettario RESTful Web Services , useresti un IRI o uno schema di identificazione più elaborato per i tipi di relazione, ma ho scelto di mantenere l'esempio il più semplice possibile.)

In questo caso, ci sono poche altre informazioni da fornire con l'azione, quindi il client dovrebbe semplicemente creare un POST vuoto contro l'URL nel href:

POST /vms/1234/fdaIX HTTP/1.1

(Poiché questa richiesta non ha un corpo, sarebbe allettante tentare di modellarla come una richiesta GET, ma le richieste GET non dovrebbero avere effetti collaterali osservabili, quindi il POST è più corretto.)

Allo stesso modo, se un client desidera spegnere la VM, seguirà power-offinvece il collegamento.

In altre parole, i tipi di relazione dei collegamenti forniscono vantaggi che indicano l'intento. Ogni tipo di relazione ha un significato semantico specifico. Questo è il motivo per cui a volte parliamo della rete semantica .

Per mantenere l'esempio il più chiaro possibile, ho oscurato intenzionalmente gli URL in ogni collegamento. Quando il server che ospita riceve la richiesta in entrata, si saprebbe che fdaIXi mezzi chiusi , e CHTY91mezzi di spegnimento .

Normalmente, codificherei semplicemente l'azione nell'URL stesso, in modo che gli URL siano /vms/1234/shut-downe /vms/1234/power-off, ma durante l'insegnamento, ciò offuschi la distinzione tra tipi di relazione (semantica) e URL (dettagli di implementazione).

A seconda di quali client hai, potresti prendere in considerazione la possibilità di rendere gli URL RESTful non hackerabili .

CQRS

Quando si tratta di CQRS, una delle poche cose su cui Greg Young e Udi Dahan concordano è che CQRS non è un'architettura di alto livello . Pertanto, sarei cauto nel rendere un'API RESTful troppo simile a CQRS, perché ciò significherebbe che i client diventano parte della tua architettura.

Spesso, la forza trainante di un'API RESTful reale (livello 3) è che si desidera essere in grado di evolvere l'API senza rompere i client e senza avere il controllo dei client. Se questa è la tua motivazione, CQRS non sarebbe la mia prima scelta.


Vuoi dire che i primi esempi non sono entrambi RESTful perché non usano i controlli hypermedia? Ma non ho nemmeno pubblicato alcuna risposta, solo gli URL e i corpi delle richieste.
leifbattermann,

4
@leifbattermann Non sono RESTful perché usano il corpo del messaggio per comunicare l'intento; questo è chiaramente RPC. Se hai usato i link per arrivare a quelle risorse, perché dovresti comunicare l'intento attraverso il corpo?
Mark Seemann,

Ha senso. Perché suggerisci un POST? L'azione non è idempotente? In ogni caso, come fai a dire al tuo cliente quale metodo HTTP usare?
leifbattermann,

2
@ guillaume31 DELETEmi sembra strano perché dopo aver spento il vm esisterà ancora, solo nello stato "power off" (o sth. così).
leifbattermann,

1
La risorsa non deve riflettere una VM in modo temporaneo, ma può rappresentare un'istanza di esecuzione di essa.
guillaume31,

6

Arresto di una macchina virtuale tramite un'interfaccia REST

Questo è in realtà un esempio piuttosto famoso, presentato da Tim Bray nel 2009 .

Roy Fielding, discutendo del problema, ha condiviso questa osservazione :

Personalmente preferisco i sistemi che trattano lo stato monitorato (come lo stato di alimentazione) come non modificabili.

In breve, si dispone di una risorsa di informazioni che restituisce una rappresentazione corrente dello stato monitorato; tale rappresentazione potrebbe includere un collegamento ipertestuale a un modulo che richiede una modifica a quello stato e il modulo ha un altro collegamento a una risorsa per gestire (ciascuna) richiesta di modifica.

Seth Ladd ha avuto le informazioni chiave sul problema

Abbiamo trasformato Running da un semplice stato di una persona a un vero Noun che può essere creato, aggiornato e di cui parlare.

Riportare questo al riavvio delle macchine. Direi che POST in / vdc / 434 / cluster / 4894 / server / 4343 / riavvii Una volta pubblicato, hai un URI che rappresenta questo riavvio e puoi OTTENERLO per gli aggiornamenti di stato. Attraverso la magia del collegamento ipertestuale, la rappresentazione del riavvio è collegata al server che viene riavviato.

Penso che coniare lo spazio URI sia economico e che gli URI siano ancora più economici. Crea una raccolta di attività, modellata come sostantivi, e POST, PUT e DELETE via!

La programmazione RESTful è la burocrazia di Vogon su scala web. Come si fa a fare qualcosa di RESTful? Inventa nuovi documenti per esso e digitalizza i documenti.

In un linguaggio un po 'più elaborato, quello che stai facendo è definire il protocollo dell'applicazione di dominio per "arrestare una VM" e identificare le risorse necessarie per esporre / implementare quel protocollo

Guardando i tuoi esempi

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

Va bene; non stai davvero trattando la richiesta stessa come una propria risorsa di informazioni separata, ma potresti comunque gestirla.

Ti sei perso un po 'nella tua rappresentazione del cambiamento.

Con PATCH, tuttavia, l'entità racchiusa contiene una serie di istruzioni che descrivono come modificare una risorsa attualmente residente sul server di origine per produrre una nuova versione.

Ad esempio, il tipo di supporto JSON Patch formatta le istruzioni come se si stesse modificando direttamente un documento JSON

[
    { "op": "replace", "path": "state", "value": "shutting down" }
]

In alternativa, l'idea è vicina, ma ovviamente non corretta. PUTè una sostituzione completa dello stato della risorsa nell'URL di destinazione , quindi probabilmente non sceglieresti un'ortografia che assomigli a una raccolta come destinazione di una rappresentazione di una singola entità.

POST /api/virtualmachines/42/actions

È coerente con la finzione che stiamo aggiungendo un'azione a una coda

PUT /api/virtualmachines/42/latestAction

È coerente con la finzione che stiamo effettuando un aggiornamento per l'elemento di coda nella coda; è un po 'strano farlo in questo modo. Il principio della minima sorpresa raccomanda di assegnare a ciascun PUT un proprio identificatore univoco, anziché metterli tutti in un unico posto e modificare più risorse contemporaneamente.

Si noti che, nella misura in cui discutiamo dell'ortografia dell'URI, a REST non importa; /cc719e3a-c772-48ee-b0e6-09b4e7abbf8bè un URI perfettamente cromulento per quanto riguarda il REST. La leggibilità, come con i nomi delle variabili, è una preoccupazione separata. L'uso di ortografie coerenti con RFC 3986 renderà le persone molto più felici.

CQRS

E se disponessimo di un dominio CQRS con molte di tali "azioni" (ovvero comandi) che potrebbero potenzialmente portare ad aggiornamenti di più aggregati o che non possono essere associati a operazioni CRUD su risorse e risorse secondarie concrete?

Greg Young su CQRS

CQRS è un modello molto semplice che offre molte opportunità per l'architettura che altrimenti potrebbero non esistere. CQRS non è un'eventuale coerenza, non è un evento, non è un messaggio, non ha modelli separati per la lettura e la scrittura, né utilizza l'approvvigionamento di eventi.

Quando la maggior parte delle persone parla di CQRS, in realtà parla dell'applicazione del modello CQRS all'oggetto che rappresenta il limite del servizio dell'applicazione.

Dato che stai parlando di CQRS nel contesto di HTTP / REST, sembra ragionevole supporre che tu stia lavorando in quest'ultimo contesto, quindi andiamo con quello.

Questo, sorprendentemente, è persino più facile del tuo esempio precedente. Il motivo è semplice: i comandi sono messaggi .

Jim Webber descrive HTTP come il protocollo applicativo di un ufficio degli anni '50; il lavoro viene svolto prendendo i messaggi e inserendoli nelle caselle di posta. La stessa idea vale: otteniamo una copia vuota di un modulo, lo compiliamo con le specifiche che conosciamo, lo consegniamo. Ta da

Dovremmo provare a modellare quanti comandi concreti crea o aggiorna su risorse concrete, ove possibile (seguendo il primo approccio dall'esempio I) e utilizzare "endpoint di azione" per il resto?

Sì, nella misura in cui le "risorse concrete" sono messaggi, piuttosto che entità nel modello di dominio.

Idea chiave: l'API REST è ancora un'interfaccia ; dovresti essere in grado di cambiare il modello sottostante senza che i clienti debbano cambiare i messaggi. Quando si rilascia un nuovo modello, si rilascia una nuova versione degli endpoint Web che sanno come prendere il protocollo di dominio e applicarlo al nuovo modello.

Un modello CQRS è più adatto per un'API come RPC?

Non proprio - in particolare, le cache web sono un ottimo esempio di "modello di lettura alla fine coerente". Rendere ciascuno dei tuoi punti di vista indirizzabili in modo indipendente, ognuno con le proprie regole di memorizzazione nella cache, ti dà un sacco di ridimensionamento gratuitamente. C'è relativamente poco appello a un approccio esclusivamente RPC alle letture.

Per quanto riguarda le scritture, è una domanda più complicata: inviare tutti i comandi a un singolo gestore in un singolo endpoint o in una singola famiglia di endpoint è sicuramente più semplice . REST è molto più di come trovi comunicare dove si trova l'endpoint al client.

Considerare un messaggio come una sua unica risorsa ha il vantaggio di poter usare PUT, avvisando i componenti intermedi del fatto che la gestione del messaggio è idempotente, in modo che possano partecipare a determinati casi di gestione degli errori, è bello avere . (Nota: dal punto di vista dei client, se le risorse hanno un URI diverso, allora sono risorse diverse; il fatto che possano avere tutti lo stesso codice del gestore richieste sul server di origine è un dettaglio di implementazione nascosto dall'uniforme interfaccia).

Fielding (2008)

Dovrei anche notare che quanto sopra non è ancora completamente RESTful, almeno come uso il termine. Tutto quello che ho fatto è stato descritto le interfacce di servizio, che non è altro che un RPC. Per renderlo RESTful, avrei bisogno di aggiungere ipertesto per introdurre e definire il servizio, descrivere come eseguire la mappatura utilizzando moduli e / o modelli di link e fornire codice per combinare le visualizzazioni in modi utili.


2

È possibile sfruttare 5 livelli di tipo di supporto per specificare il comando nel campo di intestazione del tipo di contenuto della richiesta.

Nell'esempio di VM, sarebbe qualcosa del genere

PUT /api/virtualmachines/42
Content-Type:application/json;domain-model=PowerOnVm

> HTTP/1.1 201 Created
Location: /api/virtualmachines/42/instance

Poi

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=ShutDownVm

O

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=PowerOffVm

Vedi https://www.infoq.com/articles/rest-api-on-cqrs


Attenzione, 5LMT è stata una soluzione proposta e non è supportata dagli standard . Mi sono imbattuto nell'articolo CQRS prima e ho imparato molto da esso.
Peter L

1

L'esempio nell'articolo collegato si basa sull'idea che l'avvio della macchina e lo spegnimento devono essere diretti da comandi invece che da cambiamenti nello stato delle risorse modellate. Quest'ultimo è praticamente ciò che REST vive e respira. Una migliore modellazione della VM richiede uno sguardo su come funziona la sua controparte nel mondo reale e su come tu, come essere umano, interagiresti con essa. Questo è lungo, ma penso che fornisca una buona visione del tipo di pensiero richiesto per fare una buona modellazione.

Esistono due modi per controllare lo stato di alimentazione del computer sulla mia scrivania:

  • Interruttore di alimentazione: interrompe immediatamente il flusso di elettricità verso l'alimentazione, portando l'intero computer a un arresto improvviso e disordinato.
  • Pulsante di accensione / spegnimento: Indica all'hardware di notificare al software che qualcosa all'esterno vuole che tutto venga chiuso. Il software esegue un arresto ordinato, avvisa l'hardware che è stato fatto e l'hardware segnala all'alimentatore che può passare allo stato di standby. Se l'interruttore di alimentazione è acceso, la macchina è in esecuzione e il software è in uno stato in cui non è in grado di rispondere al segnale di spegnimento, il sistema non si spegnerà se non spengo l'interruttore di alimentazione. (Una VM si comporterà esattamente allo stesso modo; se il segnale di arresto viene ignorato dal software, la macchina continuerà a funzionare, devo forzarla a spegnerla.) Se voglio essere in grado di riavviare la macchina, devo riaccendere l'interruttore, quindi premere il pulsante on / off. (Molti computer hanno la possibilità di utilizzare una pressione prolungata del pulsante di accensione per passare direttamente allo stato di standby, ma questo modello non ne ha davvero bisogno.) Questo pulsante può essere trattato come un interruttore a levetta perché premendo si traduce in un comportamento diverso a seconda dello stato quando viene premuto. Se l'interruttore di alimentazione è spento, premendo questo pulsante non si fa assolutamente nulla.

Per una VM, entrambi possono essere modellati come valori booleani di lettura / scrittura:

  • power- Se modificato in true, non accade nulla se non una nota che l'interruttore è stato posto in questo stato. Se modificato in false, la VM viene comandata in uno stato di spegnimento immediato. Per completezza, se il valore rimane invariato dopo una scrittura, non accade nulla.

  • onoff- Se modificato in true, non succede nulla se lo powerè false, altrimenti viene comandato l'avvio della VM. Quando cambiato false, non succede nulla se powerè false, tuttavia, la VM è comandato a notificare al software per fare un arresto normale, che farà e quindi notificare la VM che può andare in stato di spegnimento. Ancora una volta, per completezza, una scrittura senza modifiche non fa nulla.

Con tutto ciò arriva la consapevolezza che c'è una situazione in cui lo stato della macchina non riflette lo stato degli interruttori, ed è durante l'arresto. powersarà ancora truee onoffsarà false, ma il processore sta ancora eseguendo il suo arresto, e per questo dobbiamo aggiungere un'altra risorsa in modo da poter dire cosa sta effettivamente facendo la macchina:

  • running- Un valore di sola lettura che è truequando la VM è in esecuzione e falsequando non lo è, determinata chiedendo all'hypervisor il suo stato.

Il risultato è che se si desidera avviare una macchina virtuale, è necessario assicurarsi che siano state impostate le risorse powere . (Si potrebbe consentire di saltare il passaggio rendendolo auto-ripristinante, quindi se impostato su , diventa dopo che la VM è stata fermamente verificata. Se questa è una cosa RESTfully-pura da fare è foraggio per un'altra discussione.) Se si vuole fare un arresto normale, si imposta per e tornare più tardi per vedere se la macchina in realtà fermato, l'impostazione per se non lo ha fatto.onofftruepowerfalsetrueonofffalsepowerfalse

Come nel mondo reale, hai ancora il problema di essere indirizzato all'avvio della VM dopo che è stato onoffmodificato falsema è ancora runningperché è in fase di arresto. Come affrontarlo è una decisione politica.


0

Entrambi i disegni sono RESTful?

Quindi, se vuoi pensare in modo riposante, dimentica i comandi. Il client non dice al server di arrestare la VM. Il client "chiude dow" (metaforicamente parlando) la propria copia della rappresentazione della risorsa aggiornando il suo stato e quindi reinserisce quella rappresentazione sul server. Il server accetta quella nuova rappresentazione dello stato e come effetto collaterale di ciò, in realtà arresta la VM. L'aspetto degli effetti collaterali è lasciato al server.

È meno di

Ehi server, client qui, ti dispiacerebbe spegnere la VM

e altro ancora

Ehi server, client qui, ho aggiornato lo stato della risorsa VM 42 nello stato di arresto, aggiorno la tua copia di questa risorsa e quindi faccio quello che ritieni appropriato

Come effetto collaterale del server che accetta questo nuovo stato, può verificare quali azioni deve effettivamente eseguire (come l'arresto fisico della VM 42), ma questo è trasparente per il client. Il client non si preoccupa delle azioni che il server deve intraprendere per diventare coerente con questo nuovo stato

Quindi dimentica i comandi. Gli unici comandi sono i verbi in HTTP per il trasferimento di stato. Il client non sa, né gli importa, come il server porterà la VM fisica nello stato di spegnimento. Il client non sta inviando comandi al server per raggiungere questo obiettivo, sta solo dicendo che questo è il nuovo stato, scoprilo .

Il potere di questo è che disaccoppia il client dal server in termini di controllo del flusso. Se in seguito il server cambia il modo in cui funziona con le macchine virtuali, al client non importa. Non ci sono endpoint di comando da aggiornare. In RPC se si cambia il punto finale API da shutdownper shut-downaver rotto tutti i tuoi clienti come ora non so il comando per chiamare sul server.

REST è simile alla programmazione dichiarativa, dove invece di elencare una serie di istruzioni per cambiare qualcosa, invece devi semplicemente dichiarare come vuoi che sia e lasciare che l'ambiente di programmazione lo capisca.


Grazie per la tua risposta. La seconda parte sul disaccoppiamento di client e server si allinea molto alla mia comprensione. Hai una risorsa / collegamento che esegue il backup della prima parte della tua risposta? Quale vincolo REST viene risolto se utilizzo risorse, metodi HTTP, hypermedia, messaggi auto-descrittivi, ecc.?
Leifbattermann,

Dopotutto, non ci sono problemi con l'utilizzo di risorse, metodi HTTP, ecc. HTTP è un protocollo RESTful. Quando si presenta un problema, si utilizza quello che si definisce "endpoint di azione". In REST ci sono risorse, che rappresentano concetti o cose (come la macchina virtuale 42 o il mio conto bancario) e i verbi HTTP usano per trasferire lo stato di queste risorse tra client e server. Questo è tutto. Quello che non dovresti fare è provare a creare nuovi comandi combinando endpoint non di risorse con verbi HTTP. Quindi 'virtualmachines / 42 / actions' non è una risorsa e non dovrebbe esistere in un sistema RESTful.
Cormac Mulhall,

O per dirla in altro modo, il client non dovrebbe tentare di eseguire comandi sul server (al di là dei verbi HTTP limitati interessati esclusivamente al trasferimento di risorse dello stato). Il client dovrebbe aggiornare la sua copia della risorsa e quindi semplicemente chiedere al server di accettare questo nuovo stato. L'accettazione di questo nuovo stato può avere effetti collaterali (la VM 42 viene spenta fisicamente) ma è al di là delle preoccupazioni del cliente. Se il client non sta tentando di eseguire comandi sul server, non vi è alcun accoppiamento tramite tali comandi tra il client e il server.
Cormac Mulhall,

Puoi eseguire il comando sulla risorsa ... Come faresti, diciamo "deposito" e "prelievo" su un conto bancario? Userebbe CRUD per qualcosa che non è CRUD.
Konrad,

È meglio usare POST /api/virtualmachines/42/shutdowninvece di avere "effetti collaterali". L'API deve essere comprensibile per l'utente, come faccio a sapere che ad esempio DELETE /api/virtualmachines/42spegnerà la VM? Un effetto collaterale per me è un bug, dovremmo progettare le nostre API in modo che siano comprensibili e auto-descrittive
Konrad,
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.