sourcing net microsoft event español c# command-query-separation

c# - microsoft - cqrs net



¿Cómo se aplicaría una separación de consulta de comando(CQS), cuando se necesitan datos de resultados de un comando? (9)

Bueno, esta es una pregunta bastante antigua, pero la publico solo para que quede constancia. Siempre que use un evento, puede usar un delegado. Use eventos si tiene muchas partes interesadas, de lo contrario use un delegado en un estilo de devolución de llamada:

void CreateNewOrder(Customer buyer, Product item, Action<Order> onOrderCreated)

También puede tener un bloqueo para el caso donde la operación falló.

void CreateNewOrder(Customer buyer, Product item, Action<Order> onOrderCreated, Action<string> onOrderCreationFailed)

Esto disminuye la complejidad ciclomática en el código del cliente.

CreateNewOrder(buyer: new Person(), item: new Product(), onOrderCreated: order=> {...}, onOrderCreationFailed: error => {...});

Espero que esto ayude a cualquier alma perdida por ahí ...

En la definición de wikipedia de separación de consultas de comando , se afirma que

Más formalmente, los métodos deberían devolver un valor solo si son referencialmente transparentes y, por lo tanto, no tienen efectos secundarios.

Si estoy emitiendo un comando, ¿cómo debo determinar o informar si ese comando fue exitoso, ya que según esta definición la función no puede devolver datos?

Por ejemplo:

string result = _storeService.PurchaseItem(buyer, item);

Esta llamada tiene un comando y una consulta, pero la parte de la consulta es el resultado del comando. Supongo que podría refactorizar esto usando el patrón de comando, así:

PurchaseOrder order = CreateNewOrder(buyer, item); _storeService.PerformPurchase(order); string result = order.Result;

Pero esto parece que está aumentando el tamaño y la complejidad del código, lo que no es una dirección muy positiva para refactorizar.

¿Alguien puede darme una mejor manera de lograr la separación de comando-consulta cuando necesita el resultado de una operación?

¿Me estoy perdiendo de algo?

¡Gracias!

Notas: Martin Fowler tiene esto que decir acerca de los límites de cqs CommandQuerySeparation :

A Meyer le gusta usar la separación de comando-consulta en absoluto, pero hay excepciones. Hacer estallar una pila es un buen ejemplo de un modificador que modifica el estado. Meyer dice correctamente que puedes evitar tener este método, pero es un lenguaje útil. Así que prefiero seguir este principio cuando puedo, pero estoy preparado para romperlo para obtener mi pop.

Desde su punto de vista, casi siempre vale la pena refactorizar la separación entre comando y consulta, excepto por algunas excepciones simples menores.


CQS se usa principalmente cuando se implementa el Diseño impulsado por dominio, y por lo tanto usted debería (como Oded también indica) usar una Arquitectura dirigida por eventos para procesar los resultados. Su string result = order.Result; por lo tanto, siempre estaría en un controlador de eventos, y no directamente después en el código.

Echa un vistazo a este gran artículo que muestra una combinación de CQS, DDD y EDA.


Esta pregunta es antigua pero aún no ha recibido una respuesta satisfactoria, por lo que elaboraré un poco mi comentario de hace casi un año.

Utilizar una arquitectura dirigida por eventos tiene mucho sentido, no solo para lograr una separación clara entre comando y consulta, sino también porque abre nuevas opciones de arquitectura y, por lo general, se ajusta a un modelo de programación asíncrono (útil si necesita escalar su arquitectura). La mayoría de las veces, encontrará que la solución puede estar en modelar su dominio de manera diferente.

Así que vamos a tomar su ejemplo de compra. StoreService.ProcessPurchase sería un comando adecuado para procesar una compra. Esto generaría un PurchaseReceipt . Esta es una mejor manera de devolver el recibo en Order.Result . Para mantener las cosas muy simples, puede devolver el recibo del comando y violar el CQRS aquí. Si desea una separación más limpia, el comando ReceiptGenerated un evento ReceiptGenerated que puede suscribirse.

Si piensas en tu dominio, este podría ser un mejor modelo. Cuando te registras en un cajero, sigues este proceso. Antes de que se genere su recibo, es posible que deba pagar una tarjeta de crédito. Es probable que esto tome más tiempo. En un escenario sincrónico, esperaría en el cajero, incapaz de hacer otra cosa.



La pregunta es: ¿Cómo aplica CQS cuando necesita el resultado de un comando?

La respuesta es: usted no. Si desea ejecutar un comando y recuperar un resultado, no está utilizando CQS.

Sin embargo, la pureza dogmática en blanco y negro podría ser la muerte del universo. Siempre hay casos de borde y áreas grises. El problema es que comienzas a crear patrones que son una forma de CQS, pero que ya no son CQS puros.

Una mónada es una posibilidad. En lugar de que tu Comando devuelva el vacío, puedes devolver la Mónada. una Mónada "vacía" podría tener este aspecto:

public class Monad { private Monad() { Success = true; } private Monad(Exception ex) { IsExceptionState = true; Exception = ex; } public static Monad Success() => new Monad(); public static Monad Failure(Exception ex) => new Monad(ex); public bool Success { get; private set; } public bool IsExceptionState { get; private set; } public Exception Exception { get; private set; } }

Ahora puedes tener un método de "Comando" así:

public Monad CreateNewOrder(CustomerEntity buyer, ProductEntity item, Guid transactionGuid) { if (buyer == null || string.IsNullOrWhiteSpace(buyer.FirstName)) return Monad.Failure(new ValidationException("First Name Required")); try { var orderWithNewID = ... Do Heavy Lifting Here ...; _eventHandler.Raise("orderCreated", orderWithNewID, transactionGuid); } catch (Exception ex) { _eventHandler.RaiseException("orderFailure", ex, transactionGuid); // <-- should never fail BTW return Monad.Failure(ex); } return Monad.Success(); }

El problema con el área gris es que se abusa fácilmente. Poner información de devolución, como el nuevo OrderID en la Mónada, permitiría a los consumidores decir: "¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡olvídate !!! Además, no todos los comandos requieren una mónada. Debería verificar la estructura de su aplicación para asegurarse de que realmente haya alcanzado un caso de vanguardia.

Con una mónada, ahora su consumo de comando podría verse así:

//some function child in the Call Stack of "CallBackendToCreateOrder"... var order = CreateNewOrder(buyer, item, transactionGuid); if (!order.Success || order.IsExceptionState) ... Do Something?

En una base de código muy lejos. . .

_eventHandler.on("orderCreated", transactionGuid, out order) _storeService.PerformPurchase(order);

En una interfaz gráfica de usuario muy lejos. . .

var transactionID = Guid.NewGuid(); OnCompletedPurchase(transactionID, x => {...}); OnException(transactionID, x => {...}); CallBackendToCreateOrder(orderDetails, transactionID);

Ahora tiene toda la funcionalidad y la amabilidad que desea con solo un poco de área gris para la mónada, pero ASEGURANDO que no está exponiendo accidentalmente un mal patrón a través de la mónada, así que limita lo que puede hacer con ella.


Llego muy tarde a esto, pero hay algunas opciones más que no se han mencionado (sin embargo, no estoy seguro de que sean realmente tan buenas):

Una opción que no he visto antes es crear otra interfaz para que el controlador de comandos la implemente. Tal vez ICommandResult<TCommand, TResult> que implementa el controlador de comandos. Luego, cuando se ejecuta el comando normal, establece el resultado en el resultado del comando y la persona que llama luego saca el resultado a través de la interfaz ICommandResult. Con IoC, puede hacerlo para que devuelva la misma instancia que el Controlador de comandos para que pueda extraer el resultado. Sin embargo, esto podría romper SRP.

Otra opción es tener algún tipo de Tienda compartida que le permita asignar los resultados de los comandos de una manera que luego pueda recuperar una Consulta. Por ejemplo, digamos que su comando tenía un montón de información y luego tenía una Guía de OperationId o algo así. Cuando el comando finaliza y obtiene el resultado, envía la respuesta a la base de datos con ese OperationId Guid como la clave o algún tipo de diccionario compartido / estático en otra clase. Cuando la persona que llama recupera el control, llama a una consulta para que se retire según el Guid dado.

La respuesta más fácil es simplemente empujar el resultado en el Comando mismo, pero eso puede ser confuso para algunas personas. La otra opción que veo mencionada son los eventos, que técnicamente puedes hacer, pero si estás en un entorno web, eso hace que sea mucho más difícil de manejar.

Editar

Después de trabajar más con esto, terminé creando una "CommandQuery". Es un híbrido entre comando y consulta, obviamente. :) Si hay casos en los que necesite esta funcionalidad, entonces puede usarla. Sin embargo, es necesario que haya una buena razón para hacerlo. NO será repetible y no se puede almacenar en caché, por lo que existen diferencias en comparación con las otras dos.


Me gustan las sugerencias de arquitectura impulsadas por el evento que otras personas han dado, pero solo quiero exponerlas desde otro punto de vista. Tal vez necesite ver por qué en realidad está devolviendo datos desde su comando. ¿Necesita realmente el resultado, o podría salirse con la suya si lanza una excepción si falla?

No estoy diciendo esto como una solución universal, pero el cambio a un modelo más fuerte de "excepción en caso de fallo" en lugar del "enviar una respuesta" me ayudó mucho a hacer que la separación realmente funcionara en mi propio código. Por supuesto, luego terminas teniendo que escribir muchos más manejadores de excepciones, por lo que es un intercambio ... Pero al menos es otro ángulo a considerar.


Tómese un poco más de tiempo para pensar POR QUÉ quiere la Separación de Consulta de Comando.

"Le permite utilizar las consultas a voluntad sin preocuparse por cambiar el estado del sistema".

Por lo tanto, está bien devolver un valor de un comando para que la persona que llama sepa que tuvo éxito

porque sería inútil crear una consulta separada con el único propósito de

averiguar si un comando anterior funcionó correctamente. Algo como esto está bien en

mis libros:

boolean succeeded = _storeService.PurchaseItem(buyer, item);

Una desventaja de su ejemplo es que no es obvio lo que devuelve su

método.

string result = _storeService.PurchaseItem(buyer, item);

No está claro qué es exactamente el ''resultado''.

El uso de CQS (Separación de consulta de comando) le permite hacer las cosas más obvias

similar a la siguiente:

if(_storeService.PurchaseItem(buyer, item)){ String receipt = _storeService.getLastPurchaseReciept(buyer); }

Sí, esto es más código, pero está más claro lo que está sucediendo.


Veo mucha confusión arriba entre CQS y CQRS (como Mark Rogers notó en una respuesta también).

CQRS es un enfoque arquitectónico en DDD donde, en el caso de una consulta, no se crean gráficos de objetos completos a partir de raíces agregadas con todas sus entidades y tipos de valor, sino solo objetos de vista ligeros para mostrar en una lista.

CQS es un buen principio de programación a nivel de código en cualquier parte de su aplicación. No solo el área de dominio. El principio existe mucho más tiempo que DDD (y CQRS). Dice que no arruine los comandos que cambian cualquier estado de la aplicación con consultas que solo devuelven datos y se pueden invocar en cualquier momento sin cambiar ningún estado. En mis viejos tiempos con Delphi, el lenguaje mostraba una diferencia entre las funciones y los procedimientos. Se consideró una mala práctica codificar los ''procedimientos de función'', ya que también los llamamos a ellos.

Para responder a la pregunta: se podría pensar en una forma de evitar ejecutar un comando y obtener un resultado. Por ejemplo, al proporcionar un objeto de comando (patrón de comando) que tiene un método de ejecución nula y una propiedad de resultado de comando de solo lectura.

¿Pero cuál es la razón principal para adherirse a CQS? Mantenga el código legible y reutilizable sin tener que mirar los detalles de la implementación. Su código debe ser confiable para no causar efectos secundarios inesperados. Entonces, si el comando desea devolver un resultado, y el nombre de la función o el objeto de devolución indica claramente que es un comando con un resultado de comando, aceptaré la excepción a la regla CQS. No hay necesidad de hacer las cosas más complejas. Estoy de acuerdo con Martin Fowler (mencionado anteriormente) aquí.

Por cierto, ¿no seguiría estrictamente esta regla a romper todo el principio de API?