RESTFul: stato che modifica le azioni

52

Ho intenzione di creare API RESTfull ma ci sono alcune domande architettoniche che stanno creando alcuni problemi nella mia testa. Aggiungere la logica di backend bussiness ai client è un'opzione che vorrei evitare poiché l'aggiornamento di più piattaforme client è difficile da mantenere in tempo reale quando la logica del business può cambiare rapidamente.

Diciamo che abbiamo articolo come risorsa (api / articolo), come dovremmo implementare azioni come pubblicare, non pubblicare, attivare o disattivare e così via, ma per cercare di mantenerlo il più semplice possibile?

1) Dovremmo usare api / article / {id} / {action} poiché un sacco di logica di backend può accadere lì come spingere verso posizioni remote o cambiare proprietà multiple. Probabilmente la cosa più difficile qui è che abbiamo bisogno di inviare tutti i dati dell'articolo all'API per l'aggiornamento e non è possibile implementare il lavoro multiutente. Ad esempio, l'editor potrebbe inviare 5 secondi di dati obsoleti e correggere la sovrascrittura che alcuni altri giornalisti hanno fatto solo 2 secondi fa e non c'è modo di spiegarlo ai clienti poiché quelli che pubblicano un articolo non sono in alcun modo connessi all'aggiornamento del contenuto.

2) Creare una nuova risorsa può anche essere un'opzione, api / article- {action} / id, ma poi la risorsa restituita non sarebbe article- {action} ma articolo di cui non sono sicuro se questo è corretto. Anche nella classe di codice di codice lato server sta gestendo il lavoro actuall su entrambe le risorse e non sono sicuro se questo va contro RESTfull pensiero

Qualsiasi suggerimento è ben accetto ..

    
posta Miro Svrtan 25.03.2012 - 12:12
fonte

7 risposte

39

Trovo che le pratiche descritte qui siano utili:

What about actions that don't fit into the world of CRUD operations?

This is where things can get fuzzy. There are a number of approaches:

  1. Restructure the action to appear like a field of a resource. This works if the action doesn't take parameters. For example an activate action could be mapped to a boolean activated field and updated via a PATCH to the resource.
  2. Treat it like a sub-resource with RESTful principles. For example, GitHub's API lets you star a gist with PUT /gists/:id/star and unstar with DELETE /gists/:id/star.
  3. Sometimes you really have no way to map the action to a sensible RESTful structure. For example, a multi-resource search doesn't really make sense to be applied to a specific resource's endpoint. In this case, /search would make the most sense even though it isn't a resource. This is OK - just do what's right from the perspective of the API consumer and make sure it's documented clearly to avoid confusion.
    
risposta data 17.01.2015 - 20:24
fonte
9

Le operazioni che determinano importanti cambiamenti di stato e di comportamento sul lato server come l'azione "pubblica" che descrivi sono difficili da modellare esplicitamente in REST. Una soluzione che vedo spesso è guidare un comportamento così complesso implicitamente attraverso i dati.

Considera l'ordine di merci attraverso un'API REST esposta da un commerciante online. L'ordine è un'operazione complessa. Diversi prodotti verranno imballati e spediti, il tuo account verrà addebitato e riceverai una ricevuta. Puoi cancellare il tuo ordine per un periodo di tempo limitato e ovviamente c'è una garanzia di rimborso totale che ti consente di rispedire i prodotti per un rimborso.

Invece di un'operazione di acquisto complessa, una tale API potrebbe consentire di creare una nuova risorsa, un ordine di acquisto. All'inizio puoi apportare le modifiche desiderate: aggiungere o rimuovere prodotti, modificare l'indirizzo di spedizione, scegliere un'altra opzione di pagamento o annullare del tutto l'ordine. Puoi fare tutto questo perché non hai ancora comprato nulla, stai solo manipolando alcuni dati sul server.

Una volta completato l'ordine di acquisto e trascorso il periodo di prova, il server blocca l'ordine per evitare ulteriori modifiche. Solo in questo momento inizia la complessa sequenza di operazioni, ma non puoi controllarla direttamente, solo indirettamente attraverso i dati che hai inserito precedentemente nell'ordine di acquisto.

In base alla descrizione, "pubblicare" potrebbe essere implementato in questo modo. Invece di esporre un'operazione, devi inserire una copia della bozza che hai esaminato e vuoi pubblicare come nuova risorsa sotto / pubblica. Ciò garantisce che eventuali aggiornamenti successivi alla bozza non verranno pubblicati anche se l'operazione di pubblicazione stessa viene completata ore dopo.

    
risposta data 25.03.2012 - 16:20
fonte
6

we need to send all article data back to API for updating and multiuser work could not be implemented. For instance editor could send 5 seconds older data and overwrite fix that some other journalist just did 2 seconds ago and there is no way that I could explain to clients this since those publishing an article is really not in any way connected to updating the content.

Questo genere di cose è una sfida qualunque cosa tu faccia, è un problema molto simile al controllo del codice distribuito (mercurial, git, ecc.), e la soluzione, scritta in HTTP / ReST, sembra un po 'simile.

Supponiamo che tu abbia due utenti, Alice e Bob, entrambi che lavorano su /articles/lunch . (per chiarezza, la risposta è in grassetto)

In primo luogo, alice crea l'articolo.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

Il server non ha creato una risorsa, perché non c'era nessuna "versione" allegata alla richiesta, (supponendo un identificatore di /articles/{id}/{version} . Per eseguire la creazione, Alice è stata reindirizzata all'URL dell'articolo / versione lei " verrà creato. L'agente utente di Alice riapplicherà la richiesta al nuovo indirizzo.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

E ora l'articolo è stato creato. poi, bob guarda l'articolo:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob guarda lì:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Decide di aggiungere il proprio resto.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Come con Alice, Bob viene reindirizzato a dove creerà una nuova versione.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Infine, Alice decide che vorrebbe aggiungere al suo articolo:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Invece di essere reindirizzato come di consueto, viene restituito al client un diverso codice di stato, 409 , che indica ad Alice che la versione da cui stava provando a derivare è già stata ramificata. Le nuove risorse sono state create comunque (come mostrato dall'intestazione Location ) e le differenze tra i due sono state incluse nel corpo della risposta. Alice ora sa che la richiesta che ha appena fatto deve essere unita a un po 'come.

Tutto questo reindirizzamento è correlato alla semantica di PUT , che richiede che vengano create nuove risorse esattamente dove chiede la linea di richiesta. questo potrebbe anche salvare un ciclo di richieste usando POST , ma poi il numero di versione dovrebbe essere codificato nella richiesta da qualche altra magia, che mi sembrava meno ovvia ai fini dell'illustrazione, ma sarebbe probabilmente ancora preferibile in una vera API per ridurre al minimo i cicli di richiesta / risposta.

    
risposta data 25.03.2012 - 17:32
fonte
2

REST è orientato ai dati e in quanto tale le risorse funzionano al meglio come "cose" e non come azioni. La semantica implicita dei metodi http; GET, PUT, DELETE, ecc. Servono a rinforzare l'orientamento. POST, naturalmente, è l'eccezione.

Una risorsa può essere una combinazione di dati, ad es. contenuto dell'articolo; e metadata ie. pubblicato, bloccato, revisione. Esistono molti altri modi possibili per suddividere i dati, ma devi passare attraverso quale sarà il flusso di dati per primo per determinare quello ottimale (se ce n'è uno). Ad esempio, potrebbe essere che le revisioni dovrebbero essere la loro risorsa sotto l'articolo, come suggerisce TokenMacGuy.

Per quanto riguarda l'implementazione, probabilmente farei qualcosa di simile a ciò che suggerisce TockenMacGuy. Aggiungerei anche i campi dei metadati sull'articolo, non sulla revisione, come "bloccato" e "pubblicato".

    
risposta data 26.03.2012 - 06:30
fonte
2

Ecco un altro esempio che non riguarda il contenuto dei documenti ma più lo stato transitorio. (Trovo il versioning - dato che, in generale, ogni versione può essere una nuova risorsa - una sorta di facile problema.)

Diciamo che voglio esporre un servizio in esecuzione su una macchina tramite un REST in modo che possa essere arrestato, avviato, riavviato e così via.

Qual è l'approccio più RESTful qui? POST / service? Command = restart, ad esempio? O POST / servizio / stato con un corpo di, diciamo, 'in esecuzione'?

Sarebbe bello codificare le migliori pratiche qui e se REST sia l'approccio giusto in questo tipo di situazione.

In secondo luogo, supponiamo di voler guidare un'azione da un servizio che non influisce sul suo stato, ma che innesca invece un effetto collaterale. Ad esempio, un servizio di mailer che invia un report, creato al momento della chiamata, a un gruppo di indirizzi email.

GET / report potrebbe essere un modo per ottenere personalmente una copia del rapporto; ma cosa succede se vogliamo spingere sul lato server ulteriori azioni come l'email come dico sopra. O scrivere in un database.

Questi casi danzano intorno al divario tra risorse e azioni, e vedo modi per gestirli in un modo orientato al REST, ma francamente sembra un po 'un trucco per farlo. Forse la domanda chiave è se un'API REST debba supportare gli effetti collaterali in generale.

    
risposta data 22.04.2013 - 07:33
fonte
1

Non pensarci direttamente come manipolando lo stato dell'articolo. Invece, stai inserendo un ordine di cambiamento che richiede la creazione dell'articolo.

È possibile modellare la messa in un ordine di modifica come creazione di una nuova risorsa ordine di modifica (POST). Ci sono molti vantaggi. Ad esempio, è possibile specificare una data e un'ora future in cui l'articolo deve essere pubblicato come parte dell'ordine di modifica e lasciare che il server si preoccupi di come è implementato.

Se la pubblicazione non è un processo istantaneo, non è necessario attendere che finisca prima di tornare al client. Basta riconoscere che l'ordine di modifica è stato creato e restituire l'ID dell'ordine di modifica. È quindi possibile utilizzare l'URL corrispondente a tale ordine di modifica per condividere lo stato dell'ordine di modifica.

Un'intuizione chiave per me era riconoscere che questa metafora dell'ordine dei cambiamenti è solo un altro modo per descrivere la programmazione orientata agli oggetti. Invece di risorse, chiamiamo quindi oggetti. Invece di cambiare gli ordini, li chiamiamo messaggi. Un modo per inviare un messaggio da A a B in OO è avere A call un metodo su B. Un altro modo per farlo, in particolare quando A e B sono in computer diversi, è di avere A creare un nuovo oggetto, M, e inviarlo a B. REST formalizza semplicemente tale processo.

    
risposta data 29.06.2017 - 02:51
fonte
0

Se ti capisco correttamente, penso che quello che hai sia un problema di determinazione di una "regola aziendale" più di un problema tecnico.

Il fatto che un articolo possa essere sovrascritto potrebbe essere risolto introducendo livelli di autorizzazione in cui gli utenti senior possono ignorare le versioni degli utenti junior. Inoltre, introducendo versioni e una colonna per acquisire lo stato dell'articolo (ad esempio "in sviluppo", 'finale', ecc.), potresti superare questo. Puoi anche consentire all'utente di selezionare una determinata versione mediante una combinazione di tempo di invio e il numero di versione.

In tutti i casi precedenti, il tuo servizio deve implementare le regole aziendali impostate. Quindi puoi chiamare il servizio con i parametri: userid, article, version, action (dove la versione è facoltativa, anche in questo caso dipende dalle tue regole aziendali).

    
risposta data 25.03.2012 - 13:47
fonte

Leggi altre domande sui tag