Pourquoi séparer la classe CommandHandler avec Handle () au lieu de gérer la méthode dans la commande elle-même


13

J'ai une partie du modèle CQRS implémentée à l'aide de l' architecture S # arp comme ceci:

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;
    }
}

Je me demande pourquoi ne pas simplement avoir une classe Commandcontenant à la fois des données et une méthode de gestion? Est-ce une sorte d'avantage de testabilité, où vous devez tester la logique de gestion des commandes séparément des propriétés des commandes? Ou est-ce une exigence commerciale fréquente, où vous devez avoir une commande gérée par différentes implémentations de ICommandHandler<MyCommand, CommandResult>?


J'avais la même question, qui vaut la peine d'être regardée
rdhaundiyal

Réponses:


14

Drôle, cette question m'a juste rappelé exactement la même conversation que j'ai eue avec l'un de nos ingénieurs à propos de la bibliothèque de communications sur laquelle je travaillais.

Au lieu de commandes, j'avais des classes Request et ensuite des RequestHandlers. Le design ressemblait beaucoup à ce que vous décrivez. Je pense qu'une partie de la confusion que vous avez est que vous voyez le mot anglais "command", et pensez instantanément "verb, action ... etc".

Mais dans cette conception, considérez la commande (ou la demande) comme une lettre. Ou pour ceux qui ne savent pas ce qu'est un service postal, pensez au courrier électronique. Il s'agit simplement d'un contenu, découplé de la manière dont ce contenu doit être appliqué.

Pourquoi voudriez-vous faire cela? Dans la plupart des cas simples, le modèle de commande n'a aucune raison et vous pouvez demander à cette classe d'effectuer le travail directement. Cependant, faire le découplage comme dans votre conception est logique si votre action / commande / demande doit parcourir une certaine distance. Par exemple, à travers, des sockets ou des tuyaux, ou entre le domaine et l'infrastructure. Ou peut-être que dans votre architecture, vos commandes doivent être persistantes (par exemple, le gestionnaire de commandes peut exécuter 1 commande à la fois, en raison de certains événements système, 200 commandes arrivent et après l'arrêt des 40 premiers processus). Dans ce cas, ayant une classe simple message uniquement, il devient très simple de sérialiser uniquement la partie message en JSON / XML / binaire / quoi que ce soit et de le transmettre dans le pipeline jusqu'à ce que son gestionnaire de commandes soit prêt à le traiter.

Un autre avantage du découplage de Command de CommandHandler est que vous avez maintenant la possibilité de disposer d'une hiérarchie d'héritage parallèle. Par exemple, toutes vos commandes peuvent dériver d'une classe de commandes de base qui prend en charge la sérialisation. Et peut-être que vous avez 4 gestionnaires de commande sur 20 qui ont beaucoup de similitudes, maintenant vous pouvez les dériver de la classe de base de gestionnaire venu. Si vous deviez gérer les données et les commandes dans une seule classe, ce type de relation deviendrait rapidement incontrôlable.

Un autre exemple de découplage serait si votre commande nécessitait très peu d'entrée (par exemple 2 entiers et une chaîne) mais que sa logique de gestion était suffisamment complexe pour que vous souhaitiez stocker des données dans les variables de membre intermédiaire. Si vous mettez en file d'attente 50 commandes, vous ne voulez pas allouer de mémoire pour tout ce stockage intermédiaire, vous séparez donc Command de CommandHandler. Maintenant, vous mettez en file d'attente 50 structures de données légères et le stockage de données plus complexe n'est alloué qu'une seule fois (ou N fois si vous avez N gestionnaires) par le CommandHandler qui traite les commandes.


Le fait est que, dans ce contexte, la commande / demande n'est pas distante / persistante / etc. Elle est traitée directement. Et je ne vois pas comment la séparation des deux aiderait à l'héritage. Cela rendrait les choses plus difficiles. Le dernier paragraphe est également un peu raté. La création d'objets n'est pas une opération coûteuse et 50 commandes sont un nombre négligeable.
Euphoric

@Euphoric: comment savez-vous quel est le contexte? Sauf si l'architecture S # arp est quelque chose de spécial, tout ce que je vois, c'est quelques déclarations de classe et vous ne savez pas comment elles sont utilisées dans le reste de l'application. Si vous n'aimez pas les nombres que j'ai choisis comme 50, choisissez quelque chose comme 50 par seconde. Si cela ne suffit pas, choisissez 1000 par seconde. J'essayais juste de donner des exemples. Ou vous ne pensez pas dans ce contexte qu'il aura autant de commandes?
DXM

Par exemple, la structure exacte est visible ici weblogs.asp.net/shijuvarghese/archive/2011/10/18/… . Et nulle part là, il ne dit ce que vous avez dit. Et à propos de la vitesse, le problème est que vous avez utilisé l'argument «performance» sans profilage. Si vous avez des exigences pour un tel débit, vous n'allez pas utiliser une architecture générique mais construire quelque chose de plus spécialisé.
Euphoric

1
Laissez-moi voir si c'était votre dernier point: OP a demandé des exemples, et j'aurais dû dire, par exemple, d'abord que vous concevez la manière simple et que votre application fonctionne, puis vous évoluez et vous étendez les endroits où vous utilisez le modèle de commande, puis vous passez en direct et vous obtenez 10 000 machines parlant à votre serveur et votre serveur utilise toujours votre architecture d'origine, puis vous profilez et identifiez le problème, puis vous pouvez séparer les données de commande de la gestion des commandes, mais seulement après avoir profilé. Cela vous rendrait-il vraiment plus heureux si j'incluais tout cela dans la réponse? Il a demandé un exemple, je lui en ai donné un.
DXM

... donc je viens de parcourir ce billet de blog que vous avez publié et il semble correspondre à ce que j'ai écrit: séparez-les si votre commande doit parcourir une certaine distance. Dans le blog, il semble faire référence à un bus de commande qui est essentiellement un autre canal, socket, file d'attente de messages, esb ... etc
DXM

2

Le modèle de commande normal consiste à avoir les données et le comportement dans une seule classe. Ce type de «modèle de commande / gestionnaire» est légèrement différent. Le seul avantage par rapport au modèle normal est l'avantage supplémentaire de ne pas faire dépendre votre commande des frameworks. Par exemple, votre commande peut avoir besoin d'un accès DB, elle doit donc avoir une sorte de contexte ou de session DB, ce qui signifie qu'elle dépend des frameworks. Mais cette commande peut faire partie de votre domaine, vous ne voulez donc pas qu'elle dépende des frameworks selon le principe d'inversion de dépendance . Séparer les paramètres d'entrée et de sortie du comportement et avoir un répartiteur pour les câbler peut résoudre ce problème.

D'un autre côté, vous perdrez à la fois l'héritage et la composition des commandes. Je pense que c'est le vrai pouvoir.

Aussi, minuscule piqûre. Ce n'est pas parce qu'il a Command dans son nom qu'il fait partie du CQRS. Il s'agit de quelque chose de beaucoup plus fondamental. Ce type de structure peut servir à la fois de commande et de requête même en même temps.


J'ai vu le lien weblogs.asp.net/shijuvarghese/archive/2011/10/18/… que vous avez souligné, mais je ne vois aucun signe de bus dans le code S # arp Arch que j'ai. Donc, je suppose, une telle séparation dans mon cas ne fait que propager les classes et éclabousser la logique.
rgripper


Hmm, merci d'avoir souligné. Ensuite, mon cas est encore un peu pire, car dans le code que j'ai ICommandProcessor est IOC'ed et résolu en CommandProcessor (qui fait lui-même un IOC pour les gestionnaires de commandes) - une composition boueuse. Et dans le projet, il semble n'y avoir aucune analyse de rentabilisation pour plus d'un hadler pour une commande.
rgripper
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.