significado net microsoft example driven domain ddd asp architecture domain-driven-design cqrs business-logic

architecture - net - CQRS: lógica de negocios en el lado de la consulta



domain driven design pdf (3)

Siguiendo el concepto de CQRS (Segregación de Responsabilidad de Consulta de Comando), estoy derivando directamente el DAL en mi aplicación MVC y haciendo todas las lecturas a través de ViewModels. Sin embargo, un colega mío me pregunta qué harás cuando se tenga que aplicar alguna lógica comercial al leer. Por ejemplo, si necesita calcular un valor porcentual en el escenario como se muestra a continuación:

//Employee domain object class Employee { string EmpName; Single Wages; } //Constant declared in some utility class. This could be stored in DB also. const Single Tax = 15; //View Model for the Employee Screen class EmployeeViewModel { string EmpName; Single GrossWages; Single NetWages; } // Read Facade defined in the DAL class ReadModel { List<EmployeeViewModel> GetEmployeeList() { List<EmployeeViewModel> empList = new List<EmployeeViewModel>; string query = "SELECT EMP_NAME, WAGES FROM EMPLOYEE"; ... .. while(reader.Read()) { empList.Add( new EmployeeViewModel { EmpName = reader["EMP_NAME"], GrossWages = reader["WAGES"], NetWages = reader["WAGES"] - (reader["WAGES"]*Tax)/100 /*We could call a function here but since we are not using the business layer, the function will be defined in the DAL layer*/ } ); } } }

En el ejemplo anterior, se produce una calcinación durante la lectura que está ocurriendo en la capa DAL. Podríamos haber creado una función para hacer el cálculo, pero de nuevo, ya que hemos pasado por alto la capa empresarial para nuestra lectura, la función se ubicará en el DAL. Peor aún, alguien podría hacerlo directamente en el DB en un proceso almacenado si el valor de Impuesto se almacena en el DB. Así que tenemos una fuga potencial de lógica de negocios aquí en otras capas.

Puede decir por qué no almacena el valor calculado en una columna mientras realiza el comando. Entonces, cambiemos el escenario un poco. Digamos que está mostrando los posibles salarios netos para el empleado en un informe con la tasa impositiva actual y los salarios aún no se han pagado.
¿Cómo manejarías esto en CQRS?


Para su primer escenario, no veo por qué necesita hacer ese cálculo en el momento de la consulta, ni necesita usar un campo calculado. El dominio podría generar el salario neto calculado cuando la transacción apropiada del empleado se complete en el dominio. Los datos producidos se consumen por el lado de la consulta y se almacenan en un campo de modelo de vista listo para consultas.

Si se cambió la tasa impositiva, al recibir la notificación (evento) el lado de la consulta tendría que volver a calcular el campo de salario neto para todos los modelos de vista de empleado. Esto sucedería como parte del guardado (de forma asincrónica desde la transacción de dominio) y no como parte de una solicitud de consulta. Aunque el lado de la consulta está haciendo este cálculo, lo hace en función de los números proporcionados por el dominio, por lo que no veo ningún problema con eso.

El punto principal: todos los cálculos deben realizarse a través del dominio o por los manejadores de eventos del lado de la consulta antes de cualquier consulta.

EDITAR- Basado en comentario

Entonces, para ese escenario particular de análisis ''qué pasaría si'', suponiendo que los datos requeridos ya están en el lado de la consulta, es decir, hay una tabla ''EmployeeTimesheet'' que contiene las horas trabajadas por los empleados, hay dos opciones:

  1. Tener un componente en el lado de la consulta que sondee periódicamente los datos de los empleados y agregue / sume los datos en una tabla de modelo de vista de ''Salarios potenciales'', listos para que la administración vea el gasto salarial actual. La frecuencia de este sondeo dependerá de la frecuencia con que se requiera la información. Tal vez necesitan que estos datos sean válidos dentro de una hora, o tal vez todos los días sean satisfactorios.

  2. Nuevamente, tenga una tabla de ''PotentialWages'', pero eso se actualiza cada vez que un empleado actualiza su hoja de horarios o cada vez que se cambia el salario de un empleado. Con esta opción, los datos se mantendrían cerca del tiempo real.

De cualquier forma, los datos agregados calculados utilizan cifras producidas por el dominio y se realizan antes de la consulta, de modo que la consulta es súper simple y, lo que es más importante, súper rápida.

EDIT 2 - Solo para resumir

En mi opinión, el dominio debería ser responsable de hacer cálculos por lo que el resultado de dichos cálculos es necesario para tomar decisiones . Está absolutamente bien que el lado de consulta / lectura haga cálculos con el fin de sumar totales y agregar datos para dar a las pantallas / informes los datos que necesitan, siempre que esto no forme parte de la consulta en sí .


Tenga en cuenta que la presentación de informes puede ser un Contexto limitado por derecho propio. Por lo tanto, su arquitectura puede ser completamente diferente de la que eligió para su dominio principal .

Tal vez CQRS es una buena opción para el dominio principal, pero no para el dominio de informes. Especialmente cuando desea aplicar varios cálculos basados ​​en diferentes escenarios antes de la generación del informe. Piensa BI.

Recuerde que CQRS probablemente no debería aplicarse en toda su aplicación. Una vez que su aplicación es lo suficientemente compleja, debe identificar sus contextos delimitados y aplicar un patrón arquitectónico adecuado a cada uno de forma individual, incluso si están utilizando la misma fuente de datos.


Tengo entendido que CQRS combinado con DDD produciría un lado de consulta que agregaría datos a través del contexto delimitado y un comando al lado de los comandos ejecutados estrictamente contra el contexto delimitado para ese comando en particular.

Esto dejaría sus informes para recuperar sus datos como fuera necesario.

A continuación, puede inyectar algún ICalculator en el manejador de consultas del lado de lectura para hacer sus cálculos de lógica de negocios.

P.ej:

public class EmployeeQueryHandler : EmployeeIQueryHandler { private readonly INetWageCalculator _calculator; private readonly IEmployeeRepository _repo; public Repository(INetWageCalculator calculator, IEmployeeRepository repo) { _calculator = calculator; _repo = repo; } public List<EmployeeViewModel> ExecuteQuery() { var employees = _repo.GetEmployeeList(); foreach(var emp in employees) { // You have to get tax from somewhere, perhaps its passed in as // a parameter... emp.NetWages = _calculator.Calculate(emp.GrossWages, Tax); } return employees; } } public class EmployeeRepository : IEmployeeRepository { List<EmployeeViewModel> GetEmployeeList() { List<EmployeeViewModel> empList = new List<EmployeeViewModel>; string query = "SELECT EMP_NAME, WAGES FROM EMPLOYEE"; ... .. while (reader.Read()) { empList.Add( new EmployeeViewModel { EmpName = reader["EMP_NAME"], GrossWages = reader["WAGES"], // This line moves to the query handler. //NetWages = reader["WAGES"] - (reader["WAGES"] * Tax) / 100 /*We could call a function here but since we are not using the business layer, the function will be defined in the DAL layer*/ } ); } } }

Esto le permite reutilizar la lógica comercial de calcular los salarios netos en otro lugar utilizando el mismo servicio de calculadora.

Por motivos de rendimiento, también podría inyectar la calculadora en el repositorio si no desea repetir los resultados dos veces.