7

Esta pregunta es independiente del idioma, pero soy un tipo C#, así que uso el término POCO para referirme a un objeto que solo preforma el almacenamiento de datos, generalmente utilizando los campos getter y setter.¿Cómo separar la validación de datos de mis objetos de dominio simple (POCO)?

Acabo de volver a trabajar mi Modelo de Dominio para ser súper duco y me quedan algunas preocupaciones sobre cómo asegurar que los valores de las propiedades tengan sentido en el dominio.

Por ejemplo, la fecha de finalización de un servicio no debe exceder la fecha de finalización del contrato bajo el que se encuentra el servicio. Sin embargo, parece una violación de SOLID poner el cheque en el Service.EndDate setter, sin mencionar que a medida que aumente la cantidad de validaciones que deben hacerse, mis clases de POCO se llenarán.

Tengo algunas soluciones (se publicarán en las respuestas), pero tienen sus desventajas y me pregunto cuáles son algunos de los enfoques favoritos para resolver este dilema?

Respuesta

6

Creo que estás empezando con una suposición errónea, es decir, que deberías tener objetos que no hacen nada más que almacenar datos, y no tienen más métodos que accesos.El objetivo de tener objetos es encapsular los datos y los comportamientos. Si tiene una cosa que es, básicamente, una estructura, ¿qué comportamientos está encapsulando?

+1

Correcto, estos objetos no encapsulan comportamientos. Ese es todo el punto. Ellos representan mi dominio. Eso es. El comportamiento se proporciona en otros lugares. –

+0

Su dominio no tiene comportamiento? –

+1

Los modelos de dominio no tienen ningún comportamiento. comportamiento se adjunta a ellos por separado. Los servicios hacen cosas usando los modelos. Al menos esa es mi lectura sobre todo lo DDD. –

0

Creo que ese sería probablemente el mejor lugar para la lógica, en realidad, pero así soy yo. Podría tener algún tipo de método IsValid que verifique todas las condiciones y devuelva verdadero/falso, tal vez algún tipo de colección ErrorMessages, pero ese es un tema dudoso ya que los mensajes de error no son realmente parte del Modelo de dominio. Soy un poco parcial ya que he trabajado un poco con RoR y eso es esencialmente lo que hacen sus modelos.

+0

SRP: ¿es su modelo responsable de representar un objeto del mundo real o de validar la entrada? Tengo que ser implacable en este –

3

Siempre escucho a gente discutir por un método "Validate" o "IsValid".

Personalmente, creo que esto puede funcionar, pero con la mayoría de los proyectos DDD generalmente termina con múltiples validaciones que están permitidas dependiendo del estado específico del objeto.

Prefiero "IsValidForNewContract", "IsValidForTermination" o similar, porque creo que la mayoría de los proyectos terminan con múltiples validadores/estados por clase. Eso también significa que no obtengo ninguna interfaz, pero puedo escribir validadores agregados que lean que reflejan muy bien las condiciones comerciales que estoy afirmando.

Realmente creo que las soluciones genéricas en este caso muy a menudo toman foco de lo que es importante - lo que el código está haciendo - con una ganancia muy pequeña en elegancia técnica (la interfaz, delegado o lo que sea). Solo vótame por ello;)

2

Una solución es tener el objeto DataAccessObject de cada objeto en una lista de validadores. Cuando se llama Guardar preformas que un cheque uno contra el validador:

public class ServiceEndDateValidator : IValidator<Service> { 
    public void Check(Service s) { 
    if(s.EndDate > s.Contract.EndDate) 
     throw new InvalidOperationException(); 
    } 
} 

public class ServiceDao : IDao<Service> { 
    IValidator<Service> _validators; 
    public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;} 
    public void Save(Service s) { 
    foreach(var v in _validators) 
     v.Check(service); 
    // Go on to save 
    } 
} 

El beneficio, es muy claro SoC, la desventaja es que no recibimos el cheque hasta Save() se llama.

0

Otra posibilidad es que cada una de mis clases de aplicar

public interface Validatable<T> { 
    public event Action<T> RequiresValidation; 
} 

y haga que cada colocador para cada clase provoca el evento antes de configurar (tal vez podría lograr esto a través de atributos).

La ventaja es la verificación de validación en tiempo real. Pero el código más complicado y no está claro quién debería estar haciendo la conexión.

2

En el pasado generalmente he delegado la validación a un servicio propio, como un ValidationService. Esto, en principio, todavía escucha la filosofía de DDD.

Internamente esto contendría una colección de Validadores y un conjunto muy simple de métodos públicos como Validate() que podría devolver una colección de objetos de error.

Muy simplemente, algo como esto en C#

public class ValidationService<T> 
{ 
    private IList<IValidator> _validators; 

    public IList<Error> Validate(T objectToValidate) 
    { 
    foreach(IValidator validator in _validators) 
    { 
     yield return validator.Validate(objectToValidate); 
    } 
    } 
} 

validadores, o bien podría ser añadido dentro de un constructor por defecto o se inyecta a través de alguna otra clase, tales como ValidationServiceFactory.

+0

No crearía jerarquías paralelas http://stackoverflow.com/questions/778449/code-smell-or-not-validators-and-models-share-same-kind-of-hiereachy – Surya

+0

Surya, ¿podría explicar lo que decir con esto? Trataré de explicar más ... – Xian

0

Aquí hay otra posibilidad. La valoración se realiza a través de un proxy o decorador en el objeto de dominio:

public class ServiceValidationProxy : Service { 
    public override DateTime EndDate { 
    get {return EndDate;} 
    set { 
     if(value > Contract.EndDate) 
     throw new InvalidOperationexception(); 
     base.EndDate = value; 
    } 
    } 
} 

Ventaja: la validación instantánea. Se puede configurar fácilmente a través de un IoC.

Desventaja: Si un proxy, las propiedades validadas deben ser virtuales, si un decorador todos los modelos de dominio deben estar basados ​​en la interfaz. Las clases de validación terminarán un poco pesadas: los proxys tienen que heredar la clase y los decoradores tienen que implementar todos los métodos. El nombramiento y la organización pueden ser confusos.

3

A un colega mío se le ocurrió una idea que funcionó bastante bien. Nunca se nos ocurrió un buen nombre, pero lo llamamos Inspector/Juez.

El inspector mirará un objeto y le dirá todas las reglas que infringió. El juez decidiría qué hacer al respecto. Esta separación nos permite hacer un par de cosas. Nos permitió poner todas las reglas en un solo lugar (inspector) pero podríamos tener varios jueces y elegir el juez según el contexto.

Un ejemplo del uso de varios jueces gira en torno a la regla que dice que un cliente debe tener una dirección. Esta era una aplicación estándar de tres niveles. En el nivel UI, el juez produciría algo que la IU podría usar para indicar los campos que debían rellenarse. El juez de IU no arrojó excepciones. En la capa de servicio había otro juez. Si encontraba un Cliente sin una Dirección durante el Guardar arrojaría una excepción. En ese punto, realmente tienes que evitar que las cosas continúen.

También teníamos jueces que eran más estrictos a medida que cambiaba el estado de los objetos. Era una solicitud de seguro y durante el proceso de cotización se permitió guardar una Política en un estado incompleto. Pero una vez que esa política estuvo lista para hacerse activa, tuvieron que establecerse muchas cosas. Entonces, el Juez de Citaciones del lado del servicio no era tan estricto como el Juez de Activación. Sin embargo, las reglas que se usaban en el Inspector seguían siendo las mismas, por lo que aún se podía decir lo que no estaba completo, incluso si se decidió no hacer nada al respecto.

Cuestiones relacionadas