¿Por qué separar la clase CommandHandler con Handle () en lugar de manejar el método en Command?

13

Tengo una parte del patrón CQRS implementado usando S # arp Architecture de esta manera:

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

Me pregunto por qué no solo tener una clase que Commandcontiene datos y un método de manejo. ¿Es un tipo de beneficio de comprobabilidad, donde necesita probar la lógica de manejo de comandos por separado de las propiedades del comando? ¿O es algún requisito comercial frecuente, donde necesita tener un comando manejado por diferentes implementaciones ICommandHandler<MyCommand, CommandResult>?

rgripper
fuente
Tuve la misma pregunta, vale la pena mirar: blogs.cuttingedge.it/steven/posts/2011/…
rdhaundiyal

Respuestas:

14

Es curioso, esta pregunta me recordó exactamente la misma conversación que tuve con uno de nuestros ingenieros sobre la biblioteca de comunicaciones en la que estaba trabajando.

En lugar de comandos, tuve clases de Request y luego tuve RequestHandlers. El diseño se parecía mucho a lo que estás describiendo. Creo que parte de la confusión que tienes es que ves la palabra inglesa "comando", y al instante piensas "verbo, acción ... etc.".

Pero en este diseño, piense en Command (o Request) como una carta. O para aquellos que no saben qué es un servicio postal, piense en el correo electrónico. Es simplemente contenido, desacoplado de cómo se debe actuar sobre ese contenido.

¿Por qué harías esto? En la mayoría de los casos simples, del Patrón de comando no hay razón y puede hacer que esta clase realice el trabajo directamente. Sin embargo, hacer el desacoplamiento como en su diseño tiene sentido si su acción / comando / solicitud debe recorrer cierta distancia. Por ejemplo, a través de enchufes o tuberías, o entre dominio e infraestructura. O tal vez en su arquitectura sus comandos deben ser persistentes (por ejemplo, el controlador de comandos puede hacer 1 comando a la vez, debido a algunos eventos del sistema, llegan 200 comandos y después del cierre de los primeros 40 procesos). En ese caso, al tener una clase simple de solo mensaje, se vuelve muy simple serializar solo la parte del mensaje en JSON / XML / binary / whatever y pasarlo por la tubería hasta que su controlador de comandos esté listo para procesarlo.

Otra ventaja de desacoplar Command de CommandHandler es que ahora tiene la opción de jerarquía de herencia paralela. Por ejemplo, todos sus comandos podrían derivar de una clase de comando base que admita la serialización. Y tal vez tenga 4 de 20 manejadores de comandos que tengan mucha similitud, ahora puede derivarlos de la clase base de manejador vino. Si tuviera que manejar datos y comandos en una clase, este tipo de relación se descontrolaría rápidamente.

Otro ejemplo para el desacoplamiento sería si su comando requiriera muy poca entrada (por ejemplo, 2 enteros y una cadena), pero su lógica de manejo era lo suficientemente compleja como para almacenar datos en las variables miembro intermedias. Si pone en cola 50 comandos, no desea asignar memoria para todo ese almacenamiento intermedio, por lo que separa Command de CommandHandler. Ahora puede poner en cola 50 estructuras de datos livianas y el almacenamiento de datos más complejo se asigna solo una vez (o N veces si tiene N controladores) por el CommandHandler que procesa los comandos.

DXM
fuente
El punto es que, en este contexto, el comando / solicitud no es remoto / persistente / etc. Se maneja directamente. Y no puedo ver cómo separar los dos ayudaría con la herencia. Realmente lo haría más difícil. El último párrafo también es un poco extraño. La creación de objetos no es una operación costosa y 50 comandos es un número descuidado.
Eufórico
@Euphoric: ¿cómo sabes cuál es el contexto? A menos que S # arp Architecture sea algo especial, todo lo que veo es un par de declaraciones de clase y no tienes idea de cómo se usan en el resto de la aplicación. Si no le gustan los números que elegí como 50, elija algo así como 50 por segundo. Si eso no es suficiente, elige 1000 por segundo. Solo intentaba dar ejemplos. ¿O no crees que en este contexto tendrá tantos comandos?
DXM
Por ejemplo, la estructura exacta se ve aquí weblogs.asp.net/shijuvarghese/archive/2011/10/18/… . Y en ninguna parte allí dice lo que dijiste. Y sobre la velocidad, el problema es que usaste el argumento de "rendimiento" sin crear perfiles. Si tiene requisitos para dicho rendimiento, no utilizará una arquitectura genérica, sino que creará algo más especializado.
Eufórico
1
Déjame ver si este fue tu último punto: OP pidió ejemplos, y debería haber dicho, por ejemplo, primero diseñas la forma simple y tu aplicación funciona, luego escalas y extiendes los lugares donde usas el patrón de comando, luego se activa y obtiene 10,000 máquinas que hablan con su servidor y su servidor todavía usa su arquitectura original, luego se perfila e identifica el problema, y ​​luego se pueden separar los datos de los comandos del manejo de los comandos, pero solo después de su perfil. ¿Realmente te haría más feliz si incluyera todo eso en la respuesta? Me pidió un ejemplo, yo le di uno.
DXM
... así que eché un vistazo a la publicación de blog que publicaste y parece alinearse con lo que escribí: sepáralos si tu comando tiene que recorrer cierta distancia. En el blog parece estar refiriéndose a un bus de comandos que es básicamente otra tubería, socket, cola de mensajes, esb ... etc.
DXM
2

El patrón de comando normal se trata de tener los datos y el comportamiento en una sola clase. Este tipo de 'patrón de comando / controlador' es ligeramente diferente. La única ventaja en comparación con el patrón normal es la ventaja añadida de que su comando no dependa de los marcos. Por ejemplo, su comando puede necesitar acceso a la base de datos, por lo que debe tener algún tipo de contexto o sesión de base de datos, lo que significa que depende de los marcos. Pero este comando puede ser parte de su dominio, por lo que no desea que dependa de marcos de acuerdo con el Principio de Inversión de Dependencia . Separar los parámetros de entrada y salida del comportamiento y tener un despachador para conectarlos puede solucionar esto.

Por otro lado, perderá la ventaja de la herencia y la composición de los comandos. Lo que creo que es el verdadero poder.

Además, menor nitpick. El hecho de que tenga Command in name no lo hace parte de CQRS. Eso se trata de algo mucho más fundamental. Este tipo de estructura puede servir como comando y como consulta incluso al mismo tiempo.

Eufórico
fuente
He visto el enlace weblogs.asp.net/shijuvarghese/archive/2011/10/18/… que ha señalado, pero no veo ningún signo de autobús en el código S # arp Arch que tengo. Entonces, supongo, tal separación en mi caso solo extiende clases y salpica lógica.
rgripper
Hmm, gracias por señalarlo. Entonces mi caso es incluso un poco peor, porque en el código que tengo ICommandProcessor está IOC'ed y resuelto en CommandProcessor (que en sí mismo está haciendo un IOC para los controladores de comandos), una composición turbia. Y en el proyecto parece que no hay casos comerciales para más de un hadler para un comando.
rgripper