Uequitous language e resource REST API?

4

Ho pubblicato una domanda riguardante il linguaggio Ubiquitous e il livello di maturità nell'API REST.

Ubiquitous language and maturity level in REST API?

Dopo una lunga codifica ho capito che ulteriori domande possono essere derivate da questo post.

Ad esempio, se utilizzo l'API basata sulle risorse, l'aspetto dell'API è simile http://domain.com/products/555 per un GET di base.

Nel caso in cui voglio aggiornare una risorsa dovrebbe assomigliare a questo

http://domain.com/products/555 PUT

aspettando un payload come

{
id: 555,
name: "Sample product",
availability: 111
}

Per utilizzare tutte le funzionalità del linguaggio DDD e ubiquitario, il modello di dominio dovrebbe contenere metodi come

  • UpdateProductName
  • ChangeAvailability
  • BlockProductFromFurtherSale ecc.

Quindi, se l'API si aspetta questo payload, sto pensando a due possibili soluzioni.

a) Dividere questo endpoint di aggiornamento singolo in più come:

  • http://domain.com/products/555/UpdateProductName PUT
    {
    id: 555,
    name: "Sample product"
    }
    
  • http://domain.com/products/555/ChangeAvailability PUT
    {
    id: 555,
    availability: 111
    }
    
  • http://domain.com/products/555/BlockProductFromFurtherSale PUT
    {
    id: 555
    }
    

b) Avere logica aggiuntiva nel livello applicazione per

  • confronta il dominio corrente con il nuovo carico utile
  • rileva i valori che sono stati modificati e devono essere aggiornati
  • chiama i metodi di dominio appropriati in base ai valori modificati

Esiste un modo consigliato per gestire questo scenarioio?

    
posta Dario Granich 15.06.2016 - 11:19
fonte

2 risposte

3

Cercherò di essere esaustivo riguardo alle possibili soluzioni che potresti utilizzare. Come hai scritto, ritengo che un prodotto abbia i seguenti attributi: id, nome, disponibilità

1. Progettare una risorsa per ogni attributo

  • / products / 555 / name : GET restituisce il nome corrente dell'id del prodotto 555. PUT newname modifica il nome corrente dell'ID prodotto 555 con newname
  • / products / 555 / availability : GET restituisce la disponibilità corrente. PUT 99 modifica la disponibilità corrente a 99 .

... e così via ...

Si noti che le risorse non sono "orientate al metodo", corrispondono a un concetto (nome, disponibilità, ecc. di un prodotto) che potrebbe essere aggiornato, recuperato, cancellato, ecc. e non limitato a una particolare funzione.

Si noti inoltre che non sto descrivendo quale sia la chiamata al metodo internamente diversa quando una risorsa riceve una richiesta GET o PUT . Questo è totalmente ortogonale. Scriverò una piccola nota al riguardo, alla fine della risposta.

Difetto principale di questo approccio: per aggiornare gli attributi x , un client deve inviare richieste x ... Si vede chiaramente che potrebbe essere noioso per un cliente farlo e consumare anche la larghezza di banda. Ma se va bene, allora vai avanti.

2. Una risorsa con tutti gli attributi

  • / products / 555 : contiene tutti gli attributi di un prodotto (nome, disponibilità, ecc.). Un GET restituisce questi attributi. Per aggiornarli, hai 2 possibilità:

2.1 aggiornamento con PUT

Il metodo PUT ha lo scopo di aggiornare completamente la rappresentazione delle risorse. Significa che il tuo cliente deve inviare tutti gli attributi di un prodotto nel payload.

Immagina che la rappresentazione corrente della risorsa / products / 555 sia:

{ "name": "bread", "availability": 10 }

Se vuoi aggiornare solo la disponibilità a 99 , devi inviare la rappresentazione completa della risorsa, in questo modo:

PUT /products/555 { "name": "bread", "availability": 99 }

Grande svantaggio di questo approccio: un cliente che desidera aggiornare un campo dovrà inviare l'intera rappresentazione della risorsa.

2.2 aggiornamento con PATCH

Il verbo PATCH ha lo scopo di aggiornare parzialmente una rappresentazione della risorsa. Considerando la rappresentazione corrente del prodotto è { "name": "bread", "availability": 10 } . Se un cliente desidera aggiornare solo availability , invierà:

PATCH /products/555 { "availability": 42 }

Fai attenzione, perché PATCH non è idempotente. Significa che questa richiesta:

PATCH /products/555 { "availability": 42 }

può portare ad avere la seguente rappresentazione delle risorse:

{ "name": "bread", "availability": 42 }

o

{ "name": "sugar", "availability": 42 }

Quindi, rompe l'idempotenza ... che PUT garantisce :)! (poiché con PUT si invia la rappresentazione completa nel corpo della richiesta)

Potresti anche aggiornare diversi attributi, se la tua risorsa prodotto ce l'ha. Questo è ovviamente meno consumo di banda che la soluzione descritta in 1.

Nota: chiamata metodi di base

Come detto in precedenza, i metodi call sono ortogonali al design delle tue risorse.

Nel tuo caso, hai qualche metodo a grana fine per aggiornare solo un attributo della classe Product ... va bene.

Ma potresti incapsulare questo in un metodo a grana grossa, qualcosa del genere:

Product updateProduct(Product p) {
  p.updateName();
  p.changeAvailability();
  ...
  return p;
}
    
risposta data 15.06.2016 - 14:22
fonte
1

Is there a recommended way to handle this scenaio?

È necessario rivedere il discorso di Jim Webber su DDD per Sistemi restful .

La trama di base: per modificare i tuoi aggregati, consegna documenti (ovvero messaggi) ai tuoi endpoint HTTP e le modifiche apportate ai tuoi aggregati sono un effetto collaterale della manipolazione del documento.

Quindi la soluzione (a) sta andando nella giusta direzione.

Credo che dovresti avere una risorsa separata per ogni comando - con questo intendo che dovresti essere in grado di distinguere un comando ripetuto da due comandi che hanno gli stessi argomenti (sul percorso felice, non importa molto - ma può essere importante se si inviano comandi su una rete inaffidabile, come TCP / IP).

In pratica, ciò significa che ogni comando ha il proprio identificatore univoco generato dal client, che viene utilizzato nell'URI, nello stesso modo in cui l'ID prodotto viene utilizzato per identificare la risorsa del prodotto.

Poiché fornirai ogni volta una rappresentazione completa del comando, il metodo appropriato è PUT (o post, se PUT non è supportato).

Dopodiché, è solo una questione di design URI; soggetto al vincolo che l'URI deve recuperare per qualsiasi dato che si sceglie di elidere dal corpo del messaggio stesso. Ma uno dei seguenti potrebbe essere adatto

PUT /commands/a3a14d61-5343-442f-8c2b-febc4d9d8164
PUT /products/555/commands/a3a14d61-5343-442f-8c2b-febc4d9d8164
PUT /products/555/{commandType}/a3a14d61-5343-442f-8c2b-febc4d9d8164
PUT /products/555/commands/{commandType}/a3a14d61-5343-442f-8c2b-febc4d9d8164
PUT /products/555/{commandType}/commands/a3a14d61-5343-442f-8c2b-febc4d9d8164

REST non si preoccupa di quale colore è il capanno della bicicletta, quindi vai con quello che si adatta meglio alle tue linee guida esistenti.

    
risposta data 15.06.2016 - 15:01
fonte