Validación en un Diseño Impulsado por Dominio


¿Cómo se maneja la validación en agregados complejos en un diseño impulsado por dominio? ¿Consolida sus reglas de negocio/lógica de validación?

Entiendo la validación de argumentos. Y entiendo la validación de propiedades que se puede adjuntar a los modelos mismos y hacer cosas como verificar que una dirección de correo electrónico o código postal sea válido o que un nombre tenga una longitud mínima y máxima.

Pero, ¿qué pasa con la validación compleja que involucra múltiples modelos? ¿Dónde lo haces normalmente coloque estas reglas y métodos dentro de su arquitectura? ¿Y qué patrones, si los hay, usas para implementarlos?

Author: Viktor Dahl, 2009-02-05

5 answers

Me gusta la solución de Jimmy Bogard a este problema. Tiene un post en su blog titulado "Entity validation with visitors and extension methods" en el que presenta un enfoque muy elegante para la validación de entidades que sugiere la implementación de una clase separada para almacenar código de validación.

public interface IValidator<T>
{
    bool IsValid(T entity);
    IEnumerable<string> BrokenRules(T entity);
}

public class OrderPersistenceValidator : IValidator<Order>
{
    public bool IsValid(Order entity)
    {
        return BrokenRules(entity).Count() == 0;
    }

    public IEnumerable<string> BrokenRules(Order entity)
    {
        if (entity.Id < 0)
            yield return "Id cannot be less than 0.";

        if (string.IsNullOrEmpty(entity.Customer))
            yield return "Must include a customer.";

        yield break;
    }
}
 36
Author: David Negron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-12 11:50:39

En lugar de confiar en IsValid(xx) llamadas en toda su solicitud, considere tomar algunos consejos de Greg Young:

Nunca dejes que tus entidades se metan en un estado inválido.

Lo que esto básicamente significa es que la transición de pensar en las entidades como contenedores de datos puros y más acerca de los objetos con comportamientos.

Considere el ejemplo de la dirección de una persona:

 person.Address = "123 my street";
 person.City = "Houston";
 person.State = "TX";
 person.Zip = 12345;

Entre cualquiera de esas llamadas, su entidad no es válida (porque tendría propiedades que no están de acuerdo entre sí. Ahora considere esto:

person.ChangeAddress(.......); 

Todas las llamadas relacionadas con el comportamiento de cambiar una dirección son ahora una unidad atómica. Su entidad nunca es inválida aquí.

Si toma esta idea de modelar comportamientos en lugar de estados, entonces puede llegar a un modelo que no permita entidades inválidas.

Para una buena discusión sobre esto, echa un vistazo a esta entrevista de infoq: http://www.infoq.com/interviews/greg-young-ddd

 55
Author: Ben Scheirman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-06 22:29:04

Normalmente uso una clase de especificación, proporciona un método (esto es C#, pero se puede traducir en cualquier idioma) :

bool IsVerifiedBy(TEntity candidate)

Este método realiza una comprobación completa del candidato y sus relaciones. Puede usar argumentos en la clase specification para parametrizarla, como un nivel de comprobación...

También puede agregar un método para saber por qué el candidato no verificó la especificación :

IEnumerable<string> BrokenRules(TEntity canditate) 

Simplemente puede decidir implementar el primer método de esta manera :

bool IsVerifiedBy(TEntity candidate)
{
  return BrokenRules(candidate).IsEmpty();
}

Para reglas rotas, normalmente escribo un iterador:

IEnumerable<string> BrokenRules(TEntity candidate)
{
  if (someComplexCondition)
      yield return "Message describing cleary what is wrong...";
  if (someOtherCondition) 
      yield return
   string.Format("The amount should not be {0} when the state is {1}",
        amount, state);
}

Para la localización, debe usar recursos, y por qué no pasar una cultura al método BrokenRules. Coloco estas clases en el espacio de nombres del modelo con nombres que sugieren su uso.

 6
Author: thinkbeforecoding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-06 22:21:02

La validación de modelos múltiples debe pasar por la raíz agregada. Si tiene que validar a través de raíces agregadas, probablemente tenga un defecto de diseño.

La forma en que hago la validación de agregados es devolver una interfaz de respuesta que me dice si la validación pasa/falla y cualquier mensaje sobre por qué falló.

Puede validar todos los submodelos en la raíz agregada para que permanezcan consistentes.

// Command Response class to return from public methods that change your model
public interface ICommandResponse
{
    CommandResult Result { get; }
    IEnumerable<string> Messages { get; }
}

// The result options
public enum CommandResult
{
    Success = 0,
    Fail = 1
}

// My default implementation
public class CommandResponse : ICommandResponse
{
    public CommandResponse(CommandResult result)
    {
        Result = result;
    }

    public CommandResponse(CommandResult result, params string[] messages) : this(result)
    {
        Messages = messages;
    }

    public CommandResponse(CommandResult result, IEnumerable<string> messages) : this(result)
    {
        Messages = messages;
    }

    public CommandResult Result { get; private set; }

    public IEnumerable<string> Messages { get; private set; }
}

// usage
public class SomeAggregateRoot
{
    public string SomeProperty { get; private set; }


    public ICommandResponse ChangeSomeProperty(string newProperty)
    {
        if(newProperty == null)
        {
            return new CommandResponse(CommandResult.Fail, "Some property cannot be changed to null");
        }

        SomeProperty = newProperty;

        return new CommandResponse(CommandResult.Success);
    }
}
 0
Author: Todd Skelton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-12 21:14:23

Estas preguntas son un poco antiguas ahora, pero en caso de que alguien esté interesado, aquí está cómo implemento la validación en mis clases de servicio.

Tengo un método privado Validate en cada una de mis clases de servicio que toma una instancia de entidad y una acción que se está realizando, si la validación falla, se lanza una excepción personalizada con los detalles de las reglas rotas.

Ejemplo DocumentService con validación integrada

public class DocumentService : IDocumentService
{
    private IRepository<Document> _documentRepository;

    public DocumentService(IRepository<Document> documentRepository)
    {
        _documentRepository = documentRepository;
    }

    public void Create(Document document)
    {
        Validate(document, Action.Create);

        document.CreatedDate = DateTime.Now;

        _documentRepository.Create(document);
    }

    public void Update(Document document)
    {
        Validate(document, Action.Update);

        _documentRepository.Update(document);
    }

    public void Delete(int id)
    {
        Validate(_documentRepository.GetById(id), Action.Delete);

        _documentRepository.Delete(id);
    }

    public IList<Document> GetAll()
    {
        return _documentRepository
            .GetAll()
            .OrderByDescending(x => x.PublishDate)
            .ToList();
    }

    public int GetAllCount()
    {
        return _documentRepository
            .GetAll()
            .Count();
    }

    public Document GetById(int id)
    {
        return _documentRepository.GetById(id);
    }

    // validation 

    private void Validate(Document document, Action action)
    {
        var brokenRules = new List<string>();

        if (action == Action.Create || action == Action.Update)
        {
            if (string.IsNullOrWhiteSpace(document.Title))
                brokenRules.Add("Title is required");

            if (document.PublishDate == null)
                brokenRules.Add("Publish Date is required");
        }

        if (brokenRules.Any())
            throw new EntityException(string.Join("\r\n", brokenRules));
    }

    private enum Action
    {
        Create,
        Update,
        Delete
    }
}

Me gusta este enfoque porque me permite poner todos mis lógica de validación central en un solo lugar que mantiene las cosas simples.

 -1
Author: Jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-23 01:12:12