Pregunta

Esta pregunta es independiente del lenguaje, pero soy un chico de C #, así que uso el término POCO para referirme a un objeto que solo preforma el almacenamiento de datos, usualmente usando los campos de obtención y establecimiento.

Acabo de modificar mi Modelo de Dominio para que sea un POCO super-duper y me quedan algunas dudas sobre cómo garantizar que los valores de las propiedades tengan sentido dentro del dominio.

Por ejemplo, la Fecha de Fin de un Servicio no debe exceder la Fecha de Fin del Contrato bajo el cual se encuentra el Servicio. Sin embargo, parece una infracción de SOLID poner el cheque en el configurador de la Fecha de finalización del servicio, sin mencionar que a medida que aumente el número de validaciones que deben realizarse, mis clases de POCO se desordenarán.

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

¿Fue útil?

Solución

Creo que estás empezando con una suposición errónea, es decir, que debes tener objetos que no hagan nada más que almacenar datos, y que no tengan métodos sino accesores. El objetivo principal de tener objetos es encapsular datos y comportamientos . Si tienes una cosa que es, básicamente, una estructura, ¿qué comportamientos estás encapsulando?

Otros consejos

Siempre escucho argumentos de personas para un " Validar " o " IsValid " método.

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

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

Realmente creo que las soluciones genéricas en este caso a menudo se alejan de lo que es importante, lo que está haciendo el código, para una ganancia muy pequeña en elegancia técnica (la interfaz, el delegado o lo que sea) ). Solo votame por esto;)

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

El inspector observaría un objeto y le diría todas las reglas que infringió. El juez decidiría qué hacer al respecto. Esta separación nos deja hacer un par de cosas. Pongamos todas las reglas en un solo lugar (Inspector) pero podríamos tener varios Jueces y elegir al Juez según el contexto.

Un ejemplo del uso de múltiples Jueces gira en torno a la regla que dice que un Cliente debe tener una Dirección. Esta fue una aplicación estándar de tres niveles. En el nivel de UI, el juez produciría algo que la interfaz de usuario podría usar para indicar los campos que debían completarse. El juez de la interfaz de usuario no lanzó excepciones. En la capa de servicio había otro juez. Si encuentra un Cliente sin una Dirección durante Guardar, se producirá una excepción. En ese momento, realmente tienes que evitar que las cosas continúen.

También tuvimos 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ó que una Póliza se guardara en un estado incompleto. Pero una vez que la Política estuvo lista para activarse, hubo que establecer muchas cosas. Por lo tanto, el juez de cotización en el lado del servicio no era tan estricto como el juez de activación. Sin embargo, las reglas utilizadas en el Inspector seguían siendo las mismas, por lo que aún se podía decir lo que no estaba completo, incluso si decidiera no hacer nada al respecto.

Una solución es hacer que el objeto DataAccessObject tome una lista de validadores. Cuando se llama Guardar, se realiza una comprobación contra cada 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 que se llame a Save ().

En el pasado, por lo general he delegado la validación a un servicio propio, como un ValidationService. Esto, en principio, todavía se oye a 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 Validar () que podrían devolver una colección de objetos de error.

Muy simple, 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);
    }
  }
}

Los validadores pueden agregarse dentro de un constructor predeterminado o inyectarse a través de alguna otra clase como ValidationServiceFactory.

Creo que probablemente ese sería el mejor lugar para la lógica, pero ese es solo yo. Podría tener algún tipo de método IsValid que compruebe 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 una parte del Modelo de Dominio. Estoy un poco parcial ya que he hecho un trabajo con RoR y eso es básicamente lo que hacen sus modelos.

Otra posibilidad es que cada una de mis clases se implemente

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

Y haga que cada organizador de cada clase levante el evento antes de la configuración (tal vez podría lograrlo a través de los atributos).

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

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

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

Ventaja: 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 deben implementar todos los métodos. El nombramiento y la organización pueden ser confusos.

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