Implementazione del pattern di comando in un'API RESTful

12

Sono in procinto di progettare un'API HTTP, sperando di renderla il più possibile RESTful.

Ci sono alcune azioni che la funzionalità si diffonde su poche risorse e che a volte devono essere annullate.

Ho pensato a me stesso, sembra uno schema di comando, ma come posso modellarlo in una risorsa?

Introdurrò una nuova risorsa chiamata XXAction, come DepositAction, che verrà creata tramite qualcosa di simile

POST /card/{card-id}/account/{account-id}/Deposit
AmountToDeposit=100, different parameters...

questo in realtà creerà una nuova DepositAction e attiverà il suo metodo Do / Execute. In questo caso, la restituzione di 201 stato HTTP creato indica che l'azione è stata eseguita correttamente.

Più tardi se un cliente desidera esaminare i dettagli dell'azione che può

GET /action/{action-id}

L'aggiornamento / PUT dovrebbe essere bloccato, suppongo, perché non è rilevante qui.

E per annullare l'azione, ho pensato di usare

DELETE /action/{action-id}

che in realtà chiamerà il metodo Annulla dell'oggetto pertinente e cambierà il suo stato.

Diciamo che sono contento di un solo Do-Undo, non ho bisogno di ripetere.

Questo approccio è ok?

Ci sono delle insidie, ragioni per non usarlo?

Questo è compreso dal POV dei client?

    
posta Mithir 23.01.2013 - 08:11
fonte

2 risposte

13

Stai aggiungendo in un livello di astrazione che è fonte di confusione

La tua API inizia in modo molto semplice e pulito. Un POST HTTP crea una nuova risorsa di deposito con i parametri specificati. Poi vai fuori dai binari introducendo l'idea di "azioni" che sono un dettaglio di implementazione piuttosto che una parte fondamentale dell'API.

In alternativa considera questa conversazione HTTP ...

POST /card/{card-id}/account/{account-id}/Deposit

AmountToDeposit=100, different parameters...

201 CREATED

Location=/card/123/account/456/Deposit/789

Ora si desidera annullare questa operazione (tecnicamente questo non dovrebbe essere consentito in un sistema di contabilità bilanciato, ma quale è l'hey):

DELETE /card/123/account/456/Deposit/789

204 NO CONTENT

Il consumatore dell'API sa di avere a che fare con una risorsa di deposito ed è in grado di determinare quali operazioni sono consentite su di essa (di solito tramite OPTION in HTTP).

Sebbene l'implementazione dell'operazione di cancellazione sia condotta attraverso "azioni" oggi non è garantito che quando si esegue la migrazione di questo sistema da, ad esempio, C # a Haskell e si mantenga il front-end, il concetto secondario di "azione" continui aggiungere valore, mentre il concetto primario di Deposito certamente lo fa.

Modifica per coprire un'alternativa a CANCELLA e deposito

Per evitare un'operazione di cancellazione, ma rimuovere in modo efficace il deposito, devi procedere come segue (utilizzando una transazione generica per consentire il deposito e il prelievo):

POST /card/{card-id}/account/{account-id}/Transaction

Amount=-100, different parameters...

201 CREATED

Location=/card/123/account/456/Transation/790

Viene creata una nuova risorsa di transazione che ha esattamente la quantità opposta (-100). Ciò ha l'effetto di riportare l'account su 0, annullando la Transazione originale.

Potresti prendere in considerazione la creazione di un endpoint di "utilità" come

POST /card/{card-id}/account/{account-id}/Transaction/789/Undo <- BAD!

per ottenere lo stesso effetto. Tuttavia, ciò interrompe la semantica di un URI come identificatore introducendo un verbo. È meglio attenersi ai nomi negli identificatori e mantenere le operazioni vincolate ai verbi HTTP. In questo modo puoi facilmente creare un permalink dall'identificatore e utilizzarlo per ottenere GET e così via.

    
risposta data 23.01.2013 - 16:00
fonte
1

Il motivo principale dell'esistenza di REST è la resilienza agli errori di rete. A tal fine tutte le operazioni dovrebbero essere idempotent .

L'approccio di base sembra ragionevole, ma il modo in cui descrivi la creazione di DepositAction non sembra idempotente, che dovrebbe essere corretto. Avendo client fornire un ID univoco che verrà utilizzato per rilevare richieste duplicate. Quindi la creazione cambierebbe in

PUT /card/{card-id}/account/{account-id}/Deposit/{action-id}
AmountToDeposit=100, different parameters...

Se un altro PUT allo stesso URL viene creato con lo stesso contenuto di prima, la risposta dovrebbe essere ancora 201 created se il contenuto è lo stesso e l'errore se il contenuto è diverso. Ciò consente al client di ritrasmettere semplicemente la richiesta quando fallisce, poiché il client non può stabilire se la richiesta o la risposta è andata persa.

Ha più senso usare PUT, perché scrive solo la risorsa ed è idempotente, ma l'uso del POST non causerebbe alcun problema.

Per esaminare i dettagli della transazione il cliente GET lo stesso URL, cioè

GET /card/{card-id}/account/{account-id}/Deposit/{action-id}

e per annullarlo, lo può CANCELLARE. Ma se in realtà ha qualcosa a che fare con i soldi, come suggerisce il campione, suggerirei di metterlo in pratica con dei flag "cancellati" aggiunti, anche se per responsabilità (che rimane traccia della transazione creata e cancellata).

Ora devi scegliere un metodo per creare l'id univoco. Hai diverse opzioni:

  1. Emetti il prefisso specifico del client in precedenza nello scambio che deve essere incluso.
  2. Aggiungi una richiesta POST speciale per ottenere un ID univoco vuoto dal server. Questa richiesta non deve essere idempotente (e non può, davvero), perché gli ID non utilizzati non causano alcun problema.
  3. Usa semplicemente UUID. Tutti li usano e nessuno sembra avere alcun problema con né quelli basati su MAC né quelli casuali.
risposta data 23.01.2013 - 09:18
fonte

Leggi altre domande sui tag