2016-05-10 15 views
7

Sono relativamente nuovo ai concetti di progettazione basati su domini e ho riscontrato un problema con la restituzione di risposte appropriate in un'API durante l'utilizzo di un bus di comando con comandi e gestori di comandi per la logica del dominio.Come recuperare i dati da un bus di comando?

Diciamo che stiamo costruendo un'applicazione con un approccio di progettazione basato sul dominio. Abbiamo una parte posteriore e una parte anteriore. Il back-end ha tutta la nostra logica di dominio con un'API esposta. Il front-end utilizza l'API per effettuare richieste all'applicazione.

Stiamo costruendo la nostra logica di dominio con comandi e gestori di comandi mappati su un bus di comando. Sotto la nostra directory Dominio abbiamo un comando per creare una risorsa post chiamata CreatePostCommand. È mappato al gestore CreatePostCommandHandler tramite il bus di comando.

final class CreatePostCommand 
{ 
    private $title; 
    private $content; 

    public function __construct(string $title, string $content) 
    { 
     $this->title = $title; 
     $this->content= $content; 

    } 

    public function getTitle() : string 
    { 
     return $this->title; 
    } 

    public function getContent() : string 
    { 
     return $this->content; 
    } 
} 

final class CreatePostCommandHandler 
{ 
    private $postRepository; 

    public function __construct(PostRepository $postRepository) 
    { 
     $this->postRepository = $postRepository; 
    } 

    public function handle(Command $command) 
    { 
     $post = new Post($command->getTitle(), $command->getContent()); 
     $this->postRepository->save($post); 
    } 
} 

Nella nostra API abbiamo un endpoint per la creazione di un post. Questo è instradato il metodo createPost in un PostController nella nostra directory dell'applicazione.

final class PostController 
{ 
    private $commandBus; 

    public function __construct(CommandBus $commandBus) 
    { 
     $this->commandBus = $commandBus; 
    } 

    public function createPost($req, $resp) 
    { 
     $command = new CreatePostCommand($command->getTitle(), $command->getContent()); 
     $this->commandBus->handle($command); 

     // How do we get the data of our newly created post to the response here? 

     return $resp; 
    } 
} 

Ora, nel nostro metodo createPost vogliamo restituire i dati della nostra nuova funzione nel nostro oggetto risposta così la nostra applicazione front-end può conoscere la risorsa appena creata. Ciò è problematico poiché sappiamo che per definizione il bus di comando non dovrebbe restituire alcun dato. Così ora siamo bloccati in una posizione confusa in cui non sappiamo come aggiungere il nostro nuovo post all'oggetto risposta.

Non sono sicuro di come procedere con questo problema da qui, alcune domande mi vengono in mente:

  • C'è un modo elegante per restituire i dati di un articolo, nella risposta?
  • Sto implementando erroneamente il modello Command/CommandHandler/CommandBus?
  • Si tratta semplicemente del caso di utilizzo errato per il modello Command/CommandHandler/CommandBus?
+0

Possibile duplicato di [Cosa deve essere restituito dall'API per i comandi CQRS?] (Http://stackoverflow.com/questions/29916468/what-should-be-returned-from-the-api-for-cqrs- comandi) – guillaume31

risposta

8

In primo luogo, si noti che se noi Collegare il controllore direttamente al gestore di comandi, ci troviamo di fronte un problema simile:

public function createPost($req, $resp) 
    { 
     $command = new CreatePostCommand($command->getTitle(), $command->getContent()); 
     $this->createPostCommandHandler->handle($command); 

     // How do we get the data of our newly created post to the response here? 
     return $resp; 
    } 

Il bus sta introducendo un livello di riferimento indiretto, che consente di disaccoppiare il controller dalla il gestore di eventi, ma il problema che stai incontrando è più fondamentale.

non sono sicuro di come procedere con questo problema da qui

TL; DR - dice il dominio quello identificatori di utilizzare, invece di chiedere il dominio quello identificatore era utilizzato.

public function createPost($req, $resp) 
    { 
     // TADA 
     $command = new CreatePostCommand($req->getPostId() 
       , $command->getTitle(), $command->getContent()); 

     $this->createPostCommandHandler->handle($command); 

     // happy path: redirect the client to the correct url 
     $this->redirectTo($resp, $postId) 
    } 

In breve, il cliente, piuttosto che il modello di dominio o lo strato di persistenza, possiede la responsabilità di generare l'id della nuova entità. Il componente dell'applicazione può leggere l'identificatore nel comando stesso e utilizzarlo per coordinare la successiva transizione di stato.

L'applicazione, in questa implementazione, sta semplicemente traducendo il messaggio dalla rappresentazione DTO alla rappresentazione del dominio.

Un'implementazione alternativa utilizza l'identificatore di comando, e deriva da quel comando le identità che verranno utilizzati

 $command = new CreatePostCommand(
       $this->createPostId($req->getMessageId()) 
       , $command->getTitle(), $command->getContent()); 

Named UUIDs sono una scelta comune in quest'ultimo caso; sono deterministici e hanno piccole probabilità di collisione.

Ora, questa risposta è una sorta di trucco: in questo caso abbiamo solo dimostrato di non aver bisogno di un risultato dal gestore comandi.

In generale, preferiremmo averne uno; Post/Redirect/Get è un buon idioma da utilizzare per l'aggiornamento del modello di dominio, ma quando il client ottiene la risorsa, vogliamo assicurarci che stiano ottenendo una versione che include le modifiche che hanno appena realizzato.

Se le tue letture e le tue scritture utilizzano lo stesso libro di registrazione, questo non è un problema - qualsiasi cosa tu legga è sempre la versione più recente disponibile.

Tuttavia, è un modello di architettura comune nella progettazione basata sul dominio, nel qual caso il modello di scrittura (gestione del post) verrà reindirizzato al modello di lettura, che di solito pubblica dati obsoleti. Quindi potresti voler includere una versione minima nella richiesta get, in modo che il gestore sappia aggiornare la sua cache stantia.

C'è un modo elegante per restituire i dati del post nella risposta?

C'è un esempio nel codice di esempio che hai fornito con la tua domanda:

public function createPost($req, $resp) 

Pensateci: $ req è una rappresentazione del messaggio di richiesta HTTP, che è grosso modo analogo al comando, e $ resp è essenzialmente un handle per una struttura dati in cui è possibile scrivere i risultati.

In altre parole, passare un callback o un handle di risultato con il comando e lasciare che il gestore comandi inserisca i dettagli.

Naturalmente, ciò dipende dal vostro bus che supporta i callback; non garantito.

Un'altra possibilità, che non richiede la modifica della firma del gestore comandi, consiste nel disporre che il controllore si iscriva agli eventi pubblicati dal gestore comandi. Si coordina uno correlation id tra il comando e l'evento e si usa quello per richiamare l'evento risultato di cui si ha bisogno.

Le specifiche non contano molto - l'evento generato durante l'elaborazione del comando potrebbe essere scritto a un bus messaggio, o copiato in una casella di posta, o ....

+0

Anche se sono d'accordo, questa è una soluzione possibile, non sono del tutto in linea con il motivo per cui lo fai. Perché dovresti creare un comando "Crea" idempotente? Se l'obiettivo è renderlo ripetibile, in tal caso, vorresti ripeterlo (con lo stesso ID, cioè)? – guillaume31

+0

Inoltre, non sono venduto sulla distinzione tra Application (Service?) E Command Handler e le implicazioni che ha. Significa che il controller è il servizio di applicazione nel tuo caso? Metti il ​​gestore di comandi nel dominio? – guillaume31

+0

@ guillaume31 Quando si utilizza un bus di servizio durevole, si desidera l'idempotency in qualsiasi gestore di messaggi per impedire modifiche duplicate. Quel gestore di comandi ha il ruolo di un servizio di applicazione in DDD. – MikeSW

2

Sto usando questo approccio e Sto restituendo i risultati del comando. Tuttavia, questa è una soluzione che funziona solo se i gestori dei comandi fanno parte dello stesso processo. Fondamentalmente, sto usando un mediatore, il controller e il gestore di comandi ne ottengono un'istanza (di solito come dipendenza del costruttore).

Pseudo codice di funzione di gestione del controller

var cmd= new MyCommand(); 
var listener=mediator.GetListener(cmd.Id); 
bus.Send(cmd); 
//wait until we get a result or timeout 
var result=listener.Wait(); 
return result; 

comando pseudo codice

var result= new CommandResult(); 
add some data here 
mediator.Add(result,cmd.Id); 

Ecco come si ottiene un feedback immediato. Tuttavia, questo non dovrebbe essere usato per implementare un processo aziendale.

Btw, questo non ha nulla a che fare con DDD, è fondamentalmente un approccio CQS basato su messaggi che può essere ed è utilizzato in un'app DDD.

+0

Non è chiaro il motivo per cui i gestori di comando devono essere parte dello stesso processo; sembra uno scambio di messaggi diretto, che funziona attraverso i confini del processo. – VoiceOfUnreason

+0

@VoiceOfUnreason Perché tutto ciò che attraversa i confini del processo ha il potenziale per diventare di lunga durata e tutto diventa molto complicato. Questa è una soluzione semplice che funziona benissimo con questo vincolo – MikeSW

Problemi correlati