2016-02-29 27 views
20

Nel mio progetto utilizzando la metodologia DDD.Rest API e DDD

Il progetto ha l'offerta aggregata (entità). Questo aggregato ha molti casi d'uso.

Per questo aggregato ho bisogno di creare un api di riposo.

Con standard: creare ed eliminare nessun problema.

1) CreateDealUseCase (nome, prezzo e molti altri parametri);

POST /rest/{version}/deals/ 
{ 
    'name': 'deal123', 
    'price': 1234; 
    'etc': 'etc' 
} 

2) DeleteDealUseCase (id)

DELETE /rest/{version}/deals/{id} 

ma cosa fare con il resto dei casi d'uso?

  • HoldDealUseCase (id, ragione);
  • UnholdDealUseCase (id);
  • CompleteDealUseCase (id e molti altri parametri);
  • CancelDealUseCase (id, amercement, reason);
  • ChangePriceUseCase (id, newPrice, reason);
  • ChangeCompletionDateUseCase (id, newDate, amercement, whyChanged);
  • ecc (casi di utilizzo 20 totali) ...

Quali sono le soluzioni?

1) Utilizzare i verbi:

PUT /rest/{version}/deals/{id}/hold 
{ 
    'reason': 'test' 
} 

Ma! I verbi non possono essere utilizzati nell'URL (nella teoria REST).

2) utilizzare lo stato completato (che sarà dopo il caso d'uso):

PUT /rest/{version}/deals/{id}/holded 
{ 
    'reason': 'test' 
} 

Personalmente per me sembra brutto. Forse sto sbagliando?

3) Uso 1 richiesta PUT per tutte le operazioni:

PUT /rest/{version}/deals/{id} 
{ 
    'action': 'HoldDeal', 
    'params': {'reason': 'test'} 
} 

PUT /rest/{version}/deals/{id} 
{ 
    'action': 'UnholdDeal', 
    'params': {} 
} 

E 'difficile da gestire nel backend. Inoltre, è difficile da documentare. Poiché 1 azione ha molte diverse varianti di richieste, da cui dipende già da risposte specifiche.

Tutte le soluzioni presentano notevoli inconvenienti.

Ho letto molti articoli sul REST su Internet. Ovunque solo una teoria, come essere qui con il mio problema specifico?

+1

Non voglio dire quanto segue come risposta, quindi forse gli altri possono dare la loro opinione nel caso in cui sia una pessima idea. Che ne dici: '/ rest/{version}/dealsheld /', '/ rest/{version}/dealscompleted/{id}', ecc. Dal momento che uno dovrebbe sapere in quale stato si sta affrontando in ogni caso. Uno schema del genere avrebbe senso? –

risposta

16

Ho letto molti articoli sul REST su Internet.

In base a ciò che vedo qui, si ha realmente bisogno di guardare almeno uno dei colloqui di Jim Webber su REST e DDD

Ma cosa fare con il resto dei casi d'uso?

Ignora l'API per un momento - come faresti con i moduli HTML?

Probabilmente si dispone di una pagina Web che presenta una rappresentazione di Deal, con una serie di link su di esso. Un collegamento ti porta al modulo HoldDeal e un altro link ti porta al modulo ChangePrice e così via. Ognuna di queste forme avrebbe zero o più campi da compilare e i moduli invierebbero a ogni risorsa alcune risorse per aggiornare il modello di dominio.

Pubblicherebbero tutti alla stessa risorsa? Forse, forse no. Avrebbero tutti lo stesso tipo di supporto, quindi se si stavano postando tutti allo stesso endpoint Web, bisognerebbe decodificare il contenuto dall'altro lato.

Dato questo approccio, come si implementa il sistema? Bene, il tipo di media vuole essere json, basato sui tuoi esempi, ma non c'è niente di sbagliato in tutto il resto.

1) Utilizzare i verbi:

va bene.

Ma! I verbi non possono essere utilizzati nell'URL (nella teoria REST).

Um ... no. REST non si cura dell'ortografia dei tuoi identificatori di risorse. Ci sono un sacco di best practice URI che affermano che i verbi sono cattivi - è vero - ma non è qualcosa che deriva da REST.

Ma se le persone sono così esigenti, si chiama l'endpoint per il comando anziché il verbo. (es: "hold" non è un verbo, è un caso d'uso).

Usa 1 richiesta PUT per tutte le operazioni:

Onestamente, che non è male. Non vorrai condividere l'uri (a causa del modo in cui viene specificato il metodo PUT), ma usa un modello in cui i client possono specificare un identificatore univoco.

Ecco la carne: si sta costruendo un'API sopra i verbi HTTP e HTTP. HTTP è progettato per il trasferimento di documenti . Il cliente ti fornisce un documento, descrivendo una modifica richiesta nel tuo modello di dominio e applica la modifica al dominio (o meno) e restituisce un altro documento che descrive il nuovo stato.

Prendendo in prestito dal vocabolario CQRS per un momento, stai postando i comandi per aggiornare il tuo modello di dominio.

PUT /commands/{commandId} 
{ 
    'deal' : dealId 
    'action': 'HoldDeal', 
    'params': {'reason': 'test'} 
} 

Giustificazione - che venga inserito un comando specifico (un comando con un ID specifico) nella coda di comando, che è una raccolta.

PUT /rest/{version}/deals/{dealId}/commands/{commandId} 
{ 
    'action': 'HoldDeal', 
    'params': {'reason': 'test'} 
} 

Sì, va bene anche questo.

Dai un'occhiata a RESTBucks. È un protocollo di coffee shop, ma tutte le API passano semplicemente piccoli documenti in giro per far avanzare la macchina di stato.

+2

Sembra che tu abbia inventato le chiamate di procedura remota basate su REST. – xfg

+1

Ma cosa succede se non si desidera creare 20 endpoint che seguano il comportamento del modello di dominio? 20 endpoint è estremamente difficile da mantenere.Cosa succede se si dispone di un endpoint e un livello aggiuntivo tra i livelli dell'applicazione e del dominio che confronta e elabora i dati inviati per attivare il comportamento del dominio corretto? – mko

7

Progetta il tuo resto api indipendentemente dal livello del dominio.

Uno dei concetti chiave del design basato su domini è basso accoppiamento tra i diversi livelli software. Quindi, quando progetti la tua API di riposo, pensi al miglior riposo che potresti avere. Quindi, è il ruolo del livello applicazione a chiamare gli oggetti dominio per eseguire il caso d'uso richiesto.

Non riesco a progettare il tuo riposo per te, perché non so cosa stai cercando di fare, ma qui ci sono alcune idee.

Come ho capito, hai una risorsa Deal. Come hai detto, la creazione/eliminazione sono facili:

  • POST/resto/{version}/occupa
  • DELETE// {version} riposo/offerte/{id}.

Quindi, si desidera "mantenere" un accordo. Non so cosa significhi, devi pensare a cosa cambia nella risorsa "Deal". Cambia un attributo? se sì, allora stai semplicemente modificando la risorsa Deal.

PUT/resto/{version}/offerte/{id}

{ 
    ... 
    held: true, 
    holdReason: "something", 
    ... 
} 

Ha aggiungere qualcosa? Puoi avere diverse prese su un affare? Mi sembra che "hold" sia un nome. Se è brutto, trova un nome migliore.

POST/resto/{version}/offerte/{id}/stive

{ 
    reason: "something" 
} 

un'altra soluzione: dimenticare la teoria REST. Se pensi che la tua API sia più chiara, più efficiente, più semplice con l'uso dei verbi nell'URL, allora con tutti i mezzi, fallo. Probabilmente puoi trovare un modo per evitarlo, ma se non puoi, non fare qualcosa di brutto solo perché è la norma.

Vedere twitter's api: molti sviluppatori dicono che Twitter ha un'API ben progettata. Tadaa, usa i verbi! A chi importa, finché è bello e facile da usare?

non riesco a progettare il vostro api per te, tu sei l'unico che conosce i vostri casi d'uso, ma lo dirò ancora una volta il mio due consigli:

  • di progettazione l'API REST da sé, e quindi utilizzare il livello applicazione per chiamare gli oggetti dominio appropriati nell'ordine corretto. Questo è esattamente ciò per cui è lo strato dell'applicazione.
  • Non seguire ciecamente le norme e le teorie. Sì, dovresti cercare di seguire le buone pratiche e le norme il più possibile, ma se non puoi lasciarle indietro (dopo un'attenta considerazione ovviamente)
+1

Il design basato sul dominio riguarda il dominio. I client API devono essere progettati tenendo presente anche il dominio. Altrimenti perderai la maggior parte dei benefici di DDD. –

+0

Sì, ma questo non significa che dovresti esporre tutta la complessità del tuo dominio ai consumatori dell'API. L'API potrebbe esporre un sottoinsieme delle funzionalità del livello dominio, ad esempio. – Kaidjin

+0

Non si deve esporre la logica dei gestori di comandi o di altri componenti interni. Ma si tratta di comandi aggregati. Non è tutta la complessità, è la forma pubblica del dominio. –

0

Utilizzare o non utilizzare i verbi negli URL REST è un contraddittorio soggetto. Tuttavia, se non vuoi usare i verbi, puoi sempre fare il PUT a /rest/{version}/deals e aggiungere il parametro di query /rest/{version}/deals/{id}?action=hold. Seguendo lo stesso modello, puoi eseguire la parte Action del corpo della richiesta PUT.

Il livello applicazione formerà quindi un comando più specifico e lo invierà in modo che la logica del dominio rimanga isolata in gestori di comandi separati.

+0

Quel PUT dovrebbe essere un POST se stai dicendo che dovrebbe eseguire un'azione su alcune risorse già esistenti. – roarsneer

+0

@bkhl dice chi? 'PUT' deve essere idempotente, perché non può applicare una modifica se il risultato sarebbe lo stesso? –

+0

PUT in REST è per l'aggiornamento di una risorsa caricando una nuova rappresentazione. http://restcookbook.com/HTTP%20Methods/put-vs-post/ ha una breve spiegazione – roarsneer

0

Separo gli use case (UC) in 2 gruppi: comandi e query (CQRS) e ho 2 controller REST (uno per i comandi e un altro per le query). Le risorse REST non devono essere oggetti modello per eseguire operazioni CRUD su di esse come risultato di POST/GET/PUT/DELETE. Le risorse possono essere qualsiasi oggetto tu voglia. In effetti in DDD non dovresti esporre il modello di dominio ai controller.

(1) RestApiCommandController: Un metodo per comando use case. La risorsa REST nell'URI è il nome della classe di comando. Il metodo è sempre POST, perché si crea il comando e quindi lo si esegue tramite un bus di comando (un mediatore nel mio caso). Il corpo della richiesta è un oggetto JSON che mappa le proprietà del comando (gli argomenti dell'UC).

Ad esempio: http://localhost:8181/command/asignTaskCommand/

@RestController 
@RequestMapping("/command") 
public class RestApiCommandController { 

private final Mediator mediator;  

@Autowired 
public RestApiCommandController (Mediator mediator) { 
    this.mediator = mediator; 
}  

@RequestMapping(value = "/asignTaskCommand/", method = RequestMethod.POST) 
public ResponseEntity<?> asignTask (@RequestBody AsignTaskCommand asignTaskCommand) {  
    this.mediator.execute (asigTaskCommand); 
    return new ResponseEntity (HttpStatus.OK); 
} 

(2) RestApiQueryController: Un metodo per caso usi la frase. Qui la risorsa REST nell'URI è l'oggetto DTO restituito dalla query (come elemento di una raccolta o solo uno solo). Il metodo è sempre GET e i parametri della query UC sono parametri nell'URI.

Ad esempio: http://localhost:8181/query/asignedTask/1

@RestController 
@RequestMapping("/query") 
public class RestApiQueryController { 

private final Mediator mediator;  

@Autowired 
public RestApiQueryController (Mediator mediator) { 
    this.mediator = mediator; 
}  

@RequestMapping(value = "/asignedTask/{employeeId}", method = RequestMethod.GET) 
public ResponseEntity<List<AsignedTask>> asignedTasksToEmployee (@PathVariable("employeeId") String employeeId) { 

    AsignedTasksQuery asignedTasksQuery = new AsignedTasksQuery (employeeId); 
    List<AsignedTask> result = mediator.executeQuery (asignedTasksQuery); 
    if (result==null || result.isEmpty()) { 
     return new ResponseEntity (HttpStatus.NOT_FOUND); 
    } 
    return new ResponseEntity<List<AsignedTask>>(result, HttpStatus.OK); 
} 

NOTA: mediatore appartiene al livello dell'applicazione DDD. È il limite UC, cerca il comando/query ed esegue il servizio applicativo appropriato.