Perché separare la classe CommandHandler con Handle () invece di gestire il metodo in Command stesso


13

Ho implementato una parte del modello CQRS usando S # arp Architecture in questo modo:

public class MyCommand
{
    public CustomerId { get; set; }

    // some other fields
}

public class MyCommandHandler<MyCommand> : ICommandHandler<MyCommand, CommandResult>
{
    Handle(MyCommand command)
    {
        // some code for saving Customer entity

        return CommandResult.Success;
    }
}

Mi chiedo perché non solo avere una classe che Commandcontenga sia dati che metodo di gestione? È una sorta di vantaggio della testabilità, in cui è necessario testare la logica di gestione dei comandi separatamente dalle proprietà dei comandi? O è un requisito aziendale frequente, in cui è necessario disporre di un comando gestito da diverse implementazioni di ICommandHandler<MyCommand, CommandResult>?


Ho avuto la stessa domanda, vale la pena cercare: blogs.cuttingedge.it/steven/posts/2011/…
rdhaundiyal

Risposte:


14

Divertente, questa domanda mi ha ricordato esattamente la stessa conversazione che ho avuto con uno dei nostri ingegneri sulla biblioteca di comunicazioni a cui stavo lavorando.

Invece dei comandi, avevo le classi Request e poi i RequestHandlers. Il design era molto simile a quello che stai descrivendo. Penso che parte della confusione che hai sia che vedi la parola inglese "comando" e pensi immediatamente "verbo, azione ... ecc".

Ma in questo disegno, pensa a Command (o Request) come a una lettera. O per chi non sa cosa sia un servizio postale, pensa all'e-mail. È semplicemente contenuto, disaccoppiato dal modo in cui tale contenuto dovrebbe essere interpretato.

Perché dovresti farlo? Nella maggior parte dei casi semplici, di Command Pattern non c'è motivo e si potrebbe fare in modo che questa classe esegua il lavoro direttamente. Tuttavia, fare il disaccoppiamento come nel tuo progetto ha senso se la tua azione / comando / richiesta deve percorrere una certa distanza. Ad esempio, attraverso, socket o pipe, o tra dominio e infrastruttura. O forse nella tua architettura i tuoi comandi devono essere persistenti (ad es. Il gestore dei comandi può eseguire 1 comando alla volta, a causa di alcuni eventi di sistema, arrivano 200 comandi e dopo i primi 40 processi viene arrestato). In tal caso, avendo una semplice classe di solo messaggio diventa molto semplice serializzare solo la parte del messaggio in JSON / XML / binary / qualunque e passarla lungo la pipeline fino a quando il suo gestore di comandi non è pronto per elaborarla.

Un altro vantaggio del disaccoppiamento di Command da CommandHandler è che ora hai l'opzione della gerarchia di ereditarietà parallela. Ad esempio, tutti i comandi potrebbero derivare da una classe di comandi di base che supporta la serializzazione. E forse hai 4 gestori di comandi su 20 che hanno molta somiglianza, ora puoi ricavarli dalla classe base del gestore venuto. Se avessi una gestione dei dati e dei comandi in una classe, questo tipo di relazione sarebbe rapidamente fuori controllo.

Un altro esempio per il disaccoppiamento sarebbe se il tuo comando richiedesse pochissimi input (ad es. 2 numeri interi e una stringa) eppure la sua logica di gestione fosse abbastanza complessa in cui vorresti archiviare i dati nelle variabili membro intermedie. Se si mettono in coda 50 comandi, non si desidera allocare memoria per tutta quella memoria intermedia, quindi si separa Command da CommandHandler. Ora accodate 50 strutture di dati leggere e l'archiviazione dei dati più complessa viene allocata una sola volta (o N volte se avete N gestori) da CommandHandler che sta elaborando i comandi.


Il punto è che in questo contesto il comando / richiesta non è remoto / persistente / ecc. Viene gestito direttamente. E non riesco a vedere come separare i due aiuterebbe con l'eredità. In realtà lo renderebbe più difficile. Anche l'ultimo paragrafo è un po 'mancato. La creazione di oggetti non è un'operazione costosa e 50 comandi sono un numero trascurabile.
Euforico,

@Euforico: come fai a sapere qual è il contesto? A meno che S # arp Architecture sia qualcosa di speciale, tutto ciò che vedo sono un paio di dichiarazioni di classe e non hai idea di come vengano utilizzate nel resto dell'applicazione. Se non ti piacciono i numeri che ho scelto come 50, scegli qualcosa come 50 al secondo. Se ciò non bastasse, scegli 1000 al secondo. Stavo solo cercando di fornire esempi. O non pensi che in questo contesto avrà così tanti comandi?
DXM,

Ad esempio, la struttura esatta è disponibile qui weblogs.asp.net/shijuvarghese/archive/2011/10/18/… . E da nessuna parte lì dice quello che hai detto. E per quanto riguarda la velocità, il problema è che hai usato l'argomento 'performance' senza profilazione. Se si dispone di requisiti per tale throughput, non si utilizzerà l'architettura generica ma si crea qualcosa di più specializzato.
Euforico,

1
Fammi vedere se questo è stato il tuo ultimo punto: OP ha chiesto esempi, e avrei dovuto dire, ad esempio, prima di progettare il modo semplice e l'applicazione funziona, quindi si ingrandisce e si estendono i luoghi in cui si utilizza il modello di comando, quindi vai in diretta e ottieni 10.000 macchine che parlano con il tuo server e il tuo server utilizza ancora la tua architettura originale, quindi profila e identifichi il problema, quindi puoi separare i dati di comando dalla gestione dei comandi, ma solo dopo il tuo profilo. Sarebbe davvero più felice se includessi tutto ciò nella risposta? Ha chiesto un esempio, gli ho dato uno.
DXM,

... quindi ho appena dato un'occhiata al post sul blog che hai pubblicato e sembra allinearsi con quello che ho scritto: separali se il tuo comando deve percorrere una certa distanza. Nel blog sembra riferirsi a un bus di comando che è fondamentalmente solo un'altra pipe, socket, coda di messaggi, esb ... ecc.
DXM,

2

Il modello di comando normale riguarda la presenza di dati e comportamento in una singola classe. Questo tipo di "modello Command / Handler" è leggermente diverso. L'unico vantaggio rispetto al modello normale è l'ulteriore vantaggio di non far dipendere i comandi dai framework. Ad esempio, il comando potrebbe richiedere l'accesso al DB, quindi deve avere un qualche tipo di contesto o sessione DB, il che significa che dipende dai framework. Ma questo comando potrebbe far parte del tuo dominio, quindi non vuoi che dipenda dai framework in base al principio di inversione di dipendenza . Separare i parametri di input e output dal comportamento e avere un dispatcher per collegarli può risolvere questo problema.

D'altra parte, perderai il vantaggio sia dell'ereditarietà che della composizione dei comandi. Il che penso sia vero potere.

Inoltre, nitpick minore. Solo perché ha Command nel nome non lo rende parte di CQRS. Si tratta di qualcosa di molto più fondamentale. Questo tipo di struttura può servire sia come comando che come query anche allo stesso tempo.


Ho visto il link weblogs.asp.net/shijuvarghese/archive/2011/10/18/… che hai sottolineato, ma non vedo alcun segno di bus nel codice Arch S # arp che ho. Quindi, immagino, tale separazione nel mio caso diffonde solo classi e spruzza la logica.
rgripper,


Hmm, grazie per averlo sottolineato. Quindi il mio caso è ancora un po 'peggio, perché nel codice ho ICommandProcessor è gestito dal CIO e risolto in CommandProcessor (che a sua volta sta creando un CIO per i gestori dei comandi) - una composizione fangosa. E nel progetto non sembrano esserci casi aziendali per più di un hadler per un comando.
rgripper,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.