patterns framework driven domain ddd validation domain-driven-design

validation - framework - Validación en un diseño impulsado por dominio



infrastructure layer c# (6)

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

Comprendo validación de argumento Y entiendo la validación de la propiedad que se puede adjuntar a los modelos en sí mismos y hacer cosas como verificar que una dirección de correo electrónico o código postal sea válida 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 colocas estas reglas y métodos dentro de tu arquitectura? ¿Y qué patrones usa alguno para implementarlos?


En java world deberías considerar el uso de la validación de Hibernate

Es muy legible para validaciones simples

public class Car { @NotNull private String manufacturer; @NotNull @Size(min = 2, max = 14) private String licensePlate; @Min(2) private int seatCount; // ... }

En cuanto a validaciones complejas. Hay un mechanism para escribir sus propias validaciones.

package org.hibernate.validator.referenceguide.chapter02.classlevel; @ValidPassengerCount public class Car { private int seatCount; private List<Person> passengers; //... }


En lugar de confiar en las IsValid(xx) de IsValid(xx) toda su aplicación, considere tomar algunos consejos de Greg Young:

Nunca permita que sus entidades entren en un estado inválido.

Lo que básicamente significa que pasas de pensar en entidades como contenedores de datos puros y más en 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 ahora son una unidad atómica. Tu entidad nunca es inválida aquí.

Si toma esta idea de comportamientos de modelado en lugar de estado, puede alcanzar un modelo que no permite entidades no válidas.

Para una buena discusión sobre esto, revise esta entrevista infoq: http://www.infoq.com/interviews/greg-young-ddd


Esto pregunta un poco viejo ahora, pero en caso de que alguien esté interesado, así es cómo implemento la validación en mis clases de servicio.

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

Ejemplo DocumentService con validación incorporada

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 toda mi lógica de validación central en un solo lugar que lo hace simple.


La validación de modelos múltiples debería pasar por su raíz de agregado. Si tiene que validar a través de raíces agregadas, es probable que tenga un defecto de diseño.

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

Puede validar todos los submodelos en la raíz agregada para que sean 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); } }


Me gusta la solución de Jimmy Bogard a este problema. Tiene una publicación en su blog titulada "Validación de entidades con visitantes y métodos de extensión" en la que presenta un enfoque muy elegante para la validación de entidades que sugiere la implementación de un código de validación de clases para almacenar.

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; } }


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

bool IsVerifiedBy(TEntity candidate)

Este método realiza una verificación completa del candidato y sus relaciones. Puede usar argumentos en la clase de especificación para hacerlo parametrizado, como un nivel de verificació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, generalmente 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 los 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.