Todavía estoy buscando las mejores prácticas para la validación del modelo de dominio. ¿Es bueno poner la validación en el constructor del modelo de dominio? mi ejemplo de validación del modelo de dominio de la siguiente manera:
public class Order
{
private readonly List<OrderLine> _lineItems;
public virtual Customer Customer { get; private set; }
public virtual DateTime OrderDate { get; private set; }
public virtual decimal OrderTotal { get; private set; }
public Order (Customer customer)
{
if (customer == null)
throw new ArgumentException("Customer name must be defined");
Customer = customer;
OrderDate = DateTime.Now;
_lineItems = new List<LineItem>();
}
public void AddOderLine //....
public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}
public class OrderLine
{
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal UnitPrice { get; set; }
public OrderLine(Order order, int quantity, Product product)
{
if (order == null)
throw new ArgumentException("Order name must be defined");
if (quantity <= 0)
throw new ArgumentException("Quantity must be greater than zero");
if (product == null)
throw new ArgumentException("Product name must be defined");
Order = order;
Quantity = quantity;
Product = product;
}
}
Gracias por toda su sugerencia.
fuente
A pesar de que esta pregunta es un poco rancia, me gustaría agregar algo que valga la pena:
Me gustaría estar de acuerdo con @MichaelBorgwardt y extender al mencionar la capacidad de prueba. En "Trabajar eficazmente con el código heredado", Michael Feathers habla mucho sobre los obstáculos para las pruebas y uno de esos obstáculos es los objetos "difíciles de construir". La construcción de un objeto no válido debería ser posible y, como sugiere Fowler, las comprobaciones de validez dependientes del contexto deberían poder identificar esas condiciones. Si no puede descubrir cómo construir un objeto en un arnés de prueba, tendrá problemas para evaluar su clase.
En cuanto a la validez, me gusta pensar en los sistemas de control. Los sistemas de control funcionan analizando constantemente el estado de una salida y aplicando acciones correctivas a medida que la salida se desvía del punto de ajuste, esto se denomina control de bucle cerrado. El control de circuito cerrado espera intrínsecamente las desviaciones y actúa para corregirlas, y así es como funciona el mundo real, razón por la cual todos los sistemas de control reales suelen utilizar controladores de circuito cerrado.
Creo que usar una validación dependiente del contexto y objetos fáciles de construir hará que su sistema sea más fácil de trabajar en el futuro.
fuente
Como estoy seguro de que ya sabes ...
Verificar la validez de los datos pasados como parámetros de c'tor es definitivamente válido en el constructor; de lo contrario, posiblemente esté permitiendo la construcción de un objeto no válido.
Sin embargo (y esta es solo mi opinión, no puedo encontrar ningún buen documento en este momento): si la validación de datos requiere operaciones complejas (como operaciones asíncronas, tal vez validación basada en el servidor si se desarrolla una aplicación de escritorio), entonces es mejor coloca una función de inicialización o validación explícita de algún tipo y los miembros establecen los valores predeterminados (como
null
) en el c'tor.Además, solo como nota al margen como lo incluyó en su ejemplo de código ...
A menos que realice una validación adicional (u otra funcionalidad)
AddOrderLine
, lo más probable es que lo expongaList<LineItem>
como una propiedad en lugar deOrder
actuar como una fachada .fuente
AddLineItem
método. De hecho, para DDD, esto es preferido. SiList<LineItem>
se cambia a un objeto de colección personalizado, la propiedad expuesta y todo lo que dependía de unaList<LineItem>
propiedad están sujetos a cambio, error y excepción.La validación debe realizarse lo antes posible.
La validación en cualquier contexto, ya sea el modelo de dominio o cualquier otra forma de escribir software, debe servir para lo que desea validar y en qué nivel se encuentra en este momento.
Según su pregunta, supongo que la respuesta sería dividir la validación.
La validación de la propiedad verifica si el valor de esa propiedad es correcto, por ejemplo, cuando se tiene un rango entre 1-10.
La validación de objetos garantiza que todas las propiedades en el objeto son válidas en conjunto entre sí. Por ejemplo, BeginDate es anterior a EndDate. Suponga que lee un valor del almacén de datos y que BeginDate y EndDate se inicializan en DateTime.Min de forma predeterminada. Al configurar BeginDate, no hay ninguna razón para aplicar la regla "debe ser anterior a EndDate", ya que esto no se aplica AÚN. Esta regla debe verificarse DESPUÉS de que se hayan establecido todas las propiedades. Esto se puede llamar a nivel raíz agregado
La validación también debe realizarse en la entidad agregada (o raíz agregada). Un objeto de pedido puede contener datos válidos y también lo son sus líneas de pedido. Pero luego, una regla comercial establece que ningún pedido puede superar los $ 1,000. ¿Cómo haría cumplir esta regla en algunos casos? no puede simplemente agregar una propiedad "no validar la cantidad" ya que esto conduciría a abuso (tarde o temprano, tal vez incluso usted, solo para eliminar esta "solicitud desagradable").
a continuación hay validación en la capa de presentación. ¿Realmente vas a enviar el objeto a través de la red, sabiendo que fallará? ¿O le ahorrará al usuario este perdón y le informará tan pronto como ingrese un valor no válido? Por ejemplo, la mayoría de las veces su entorno DEV será más lento que la producción. ¿Le gustaría esperar 30 segundos antes de que le informen de "olvidó este campo OTRA VEZ durante OTRA prueba", especialmente cuando hay un error de producción que se debe corregir con su jefe respirando por el cuello?
Se supone que la validación en el nivel de persistencia es lo más cercana posible a la validación del valor de la propiedad. Esto ayudará a evitar excepciones al leer errores "nulos" o de "valor no válido" al usar mapeadores de cualquier tipo o lectores de datos antiguos. El uso de procedimientos almacenados resuelve este problema, pero requiere escribir la misma lógica de valuación OTRA VEZ y ejecutarla OTRA VEZ. Y los procedimientos almacenados son el dominio de administración de la base de datos, por lo que no intente hacer SU trabajo también (o peor aún, molestarlo con esta "elección minuciosa por la que no se le paga").
para decirlo con algunas palabras famosas "depende", pero al menos ahora sabes POR QUÉ depende.
Desearía poder colocar todo esto en un solo lugar, pero desafortunadamente, esto no se puede hacer. Hacer esto colocaría una dependencia en un "objeto de Dios" que contiene TODA la validación para TODAS las capas. No quieres ir por ese camino oscuro.
Por esta razón, solo lanzo excepciones de validación a un nivel de propiedad. Todos los demás niveles utilizo ValidationResult con un método IsValid para recopilar todas las "reglas rotas" y pasarlas al usuario en una única AggregateException.
Al propagar la pila de llamadas, las vuelvo a reunir en AggregateExceptions hasta llegar a la capa de presentación. La capa de servicio puede lanzar esta excepción directamente al cliente en caso de WCF como una FaultException.
Esto me permite tomar la excepción y dividirla para mostrar errores individuales en cada control de entrada o aplanarla y mostrarla en una sola lista. La decisión es tuya.
Es por eso que también mencioné la validación de la presentación, para cortocircuitarlos tanto como sea posible.
En caso de que se pregunte por qué también tengo la validación a nivel de agregación (o nivel de servicio si lo desea), es porque no tengo una bola de cristal que me diga quién usará mis servicios en el futuro. Tendrá suficientes problemas para encontrar sus propios errores para evitar que otros cometan los suyos :) ingresando datos no válidos, por ejemplo, administra la aplicación A, pero la aplicación B alimenta algunos datos utilizando su servicio. ¿Adivina a quién preguntan primero cuando hay un error? El administrador de la aplicación B felizmente informará al usuario "no hay ningún error en mi extremo, solo ingreso los datos".
fuente