¿Cómo se podría aplicar la separación de comandos de consulta (SCC), cuando se necesitan datos de resultados de un comando?

StackOverflow https://stackoverflow.com/questions/3688068

  •  02-10-2019
  •  | 
  •  

Pregunta

En la definición de comando de separación de consulta de Wikipedia, se afirma que

  

Más formalmente, los métodos deben devolver sólo un valor   si son referencialmente transparente   y por lo tanto no poseen efectos secundarios.

Si estoy emitiendo un comando, ¿cómo debería determinar o informe si ese comando tuvo éxito, ya que por esta definición la función no puede devolver datos?

Por ejemplo:

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

Esta llamada tiene tanto un comando de consulta y en ella, pero la parte de consulta es resultado del comando. Creo que podría refactorizar esto utilizando el patrón de comandos, 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, que no es una dirección muy positiva a refactor hacia.

Puede alguien dar una mejor manera de lograr la separación-consulta de comandos cuando se necesita el resultado de una operación?

Me estoy perdiendo algo aquí?

Gracias!

Notas: Martin Fowler tiene esto que decir acerca de los límites de cqs Command-Query Separation :

  

Meyer le gusta usar comandos-consulta   la separación absoluta, pero hay   excepciones. Hacer estallar una pila es un buen   ejemplo de un modificador que modifica   estado. Meyer dice correctamente que se   puede evitar tener este método, pero   es un modismo útil. Así que prefiero   seguir este principio cuando puedo, pero   Estoy dispuesto a romperlo para conseguir mi   pop.

Desde su punto de vista, es casi siempre vale la pena a refactor hacia la separación comando / consulta, a excepción de algunas pequeñas excepciones simples.

¿Fue útil?

Solución

Esta pregunta es viejo, pero no ha recibido una respuesta satisfactoria, sin embargo, por lo que voy a elaborar un poco en mi comentario de hace casi un año.

Uso de un evento impulsado por la arquitectura tiene mucho sentido, no sólo para lograr una separación clara comando / consulta, sino también porque abre nuevas opciones de arquitectura y por lo general se ajusta a un modelo de programación asíncrona (útil si necesita escalar su arquitectura) . Más a menudo que no, se encuentra la solución puede estar en el modelado de su dominio diferente.

Así que tomemos el ejemplo de la compra. StoreService.ProcessPurchase sería un comando adecuado para el procesamiento de una compra. Esto generaría un PurchaseReceipt. Se trata de una mejor manera en lugar de devolver el recibo en Order.Result. Para mantener las cosas muy simple, puede devolver el recibo de la orden y violan CQRS aquí. Si desea una separación más limpia, el comando levantaría un evento ReceiptGenerated que puede suscribirse.

Si se piensa en su dominio, esto puede ser en realidad un modelo mejor. Cuando se va a retirar en un cajero, sigue este proceso. Antes de generar el recibo, un cheque de tarjeta de crédito puede ser debido. Es probable que esto llevará más tiempo. En un escenario sincrónica, se tenga que esperar en el cajero, incapaz de hacer nada más.

Otros consejos

Veo mucha confusión anterior entre SCC y CQRS (como Mark Rogers notó en una respuesta tan bien).

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

SCC es una buena programación principio sobre el nivel de código en cualquier parte de su aplicación. No sólo el área de dominio. El principio existe camino más largo que DDD (y CQRS). No dice a desordenar comandos que cambian cualquier estado de la aplicación con consultas que solo devuelven datos y se pueden invocar en cualquier momento sin necesidad de cambiar cualquier estado. En mis tiempos con Delphi, el lanquage mostró una diferencia entre las funciones y procedimientos. Se considera una mala práctica de código '' functionprocedures como los llamábamos atrás que también.

Para responder a la pregunta planteada: Se podría pensar en una forma de trabajo en torno a la ejecución de un comando y recibir como respuesta un resultado. Por ejemplo, proporcionando un objeto de comando (patrón de comandos) que tiene un vacío método y un sólo lectura propiedad resultado del comando ejecutar.

Pero ¿cuál es la razón principal para adherirse a SCC? Mantener el código legible y reutilizable sin la necesidad de mirar los detalles de implementación. El código debe ser digno de confianza para no causar efectos secundarios inesperados. Así que si el comando quiere devolver un resultado, y el nombre de la función o el objeto de retorno indica claramente que es un comando con un resultado de comandos, voy a aceptar la excepción a la regla de SCC. No hay necesidad de hacer las cosas más complejas. Estoy de acuerdo con Martin Fowler (mencionado anteriormente) aquí.

Por cierto: no sería estrictamente siguiendo esta regla romper todo el principio API fluida?

Me gustan los eventos impulsados ??arquitectura sugerencias otras personas han dado, pero yo sólo quiero tirar en otro punto de vista. Tal vez usted necesita para analizar por qué en realidad estás devuelvan datos de su comando. Es lo que realmente necesita el resultado fuera de él, o podría salirse con lanzar una excepción si falla?

No estoy diciendo esto como una solución universal, pero el cambio a una más fuerte "excepción en caso de fallo" en lugar de "enviar de vuelta una respuesta" modelo me ha ayudado mucho en la toma de la separación de hecho el trabajo en mi propio código. Por supuesto, a continuación, al final tener que escribir mucho más controladores de excepciones, por lo que es una solución de compromiso ... Pero es, al menos, otro punto de vista a tener en cuenta.

La pregunta es; ¿Cómo se aplica SCC cuando se necesita el resultado de un comando?

La respuesta es: no lo hace. Si desea ejecutar un comando y volver consecuencia, no está utilizando CQS.

Sin embargo, en blanco y negro pureza dogmática podría ser la muerte del universo. Siempre hay casos extremos y zonas grises. El problema es que se empieza a crear patrones que son una forma de SCC, pero ya no CQS puro.

A Mónada es una posibilidad. En lugar de su nula regresar de comandos, se puede volver Mónada. un "vacío" mónada 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 usted puede tener un método de "comandos" de esta manera:

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 la zona gris es que se abusa de ella fácilmente. Poner la información de retorno, como el nuevo IdPedido en la mónada permitiría a los consumidores dicen, "Olvídate de espera para el evento, tenemos el ID de la derecha aquí !!!" Además, no todos los comandos requeriría una mónada. Que realmente debe comprobar la estructura de su aplicación para asegurarse de que haya alcanzado realmente un caso extremo.

Con una mónada, ahora su consumo de comando podría tener este aspecto:

//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, muy lejos. . .

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

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

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

Ahora usted tiene toda la funcionalidad y la probidad que desee con sólo un poco de zona gris de la mónada, pero asegurándose de que no está accidentalmente exponiendo un mal patrón a través de la mónada, lo que limita lo que puede hacer con a él.

Bueno, esta es una pregunta bastante viejo, pero puedo enviar esto sólo para el registro. Siempre que utilice un evento, en su lugar puede utilizar un delegado. Use eventos si tiene muchas partes interesadas, de lo contrario utilizar un delegado en un estilo de devolución de llamada:

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

También puede tener un bloque para el caso en que la operación ha fallado

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

Esta disminución de la complejidad ciclomática en el código de cliente

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

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

Estoy muy tarde para esto, pero hay algunas opciones más que no se han mencionado (sin embargo, no está seguro de si son realmente tan bueno):

Una opción que no he visto antes es la creación de otra interfaz para el controlador de comandos para poner en práctica. Tal vez ICommandResult<TCommand, TResult> que implementa el controlador de comandos. Luego, cuando se ejecuta el comando normales, se 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 la COI, se puede hacer de modo que devuelve la misma instancia que el controlador de comandos para que pueda tirar de la parte posterior resultado cabo. Sin embargo, esto podría romper SRP.

Otra opción es tener algún tipo de tienda compartido que le permite asignar resultados de los comandos de una manera que una consulta a continuación, pudo recuperar. Por ejemplo, diga su comando tenía un montón de información y luego tuvo un Guid OperationId o algo por el estilo. Cuando finalice el comando y obtiene el resultado, empuja la respuesta ya sea a la base de datos con la que OperationId Guid como la clave o algún tipo de diccionario compartida / estática en otra clase. Cuando la persona que llama recibe el control de nuevo, llama a una consulta para tirar hacia atrás en base al resultado basado en el GUID dado.

La respuesta más sencilla es simplemente empujar el resultado en el propio sistema, pero que podría ser confuso para algunas personas. La otra opción que veo es mencionado eventos, que se puede hacer técnicamente, pero si usted está en un entorno web, que hace que sea mucho más difícil de manejar.

Editar

Después de trabajar con esto más, terminé creando un "CommandQuery". Es un híbrido entre el mando y consulta, obviamente. :) Si hay casos donde se necesita esta funcionalidad, a continuación, puede usarlo. Sin embargo, es necesario que haya buena razón para hacerlo. No va a ser repetible y no puede ser almacenado en caché, por lo que hay diferencias en comparación con los otros dos.

Tome un poco de más tiempo para pensar por qué quiere Separación comando de consulta.

"Le permite utilizar consultas a su antojo, sin preocuparse de cambiar el estado del sistema."

Así que está bien para devolver un valor desde un comando para que el llamante sepa que sucedió

porque sería un desperdicio para crear una consulta independiente con el único propósito de

saber si un comando anterior funcionaba correctamente. Algo como esto está bien en

mis libros:

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

Una desventaja de que su ejemplo es que no es obvio lo que es devuelto por su

método.

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

No está claro cuál es 'resultado' es exactamente.

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

similar a continuación:

if(_storeService.PurchaseItem(buyer, item)){

    String receipt = _storeService.getLastPurchaseReciept(buyer);
}

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

CQS se utiliza principalmente cuando la implementación de dominio Driven Design, y por lo tanto se debe (como también afirma Oded) utilizar un evento Driven Architecture 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 código.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top