sourcing query event español csqr c# validation architecture domain-driven-design cqrs

c# - query - Validación de dominio en una arquitectura CQRS



csqr (11)

Desde mi experiencia OO (no soy un experto DDD) mover su código de la entidad a un nivel de abstracción más alto (en un controlador de comandos) causará la duplicación del código. Esto se debe a que cada vez que un controlador de comando obtiene una dirección de correo electrónico, debe instanciar las reglas de validación de correo electrónico. Este tipo de código se pudrirá después de un tiempo, y olerá muy mal. En el ejemplo actual, podría no ser así, si no tiene otro comando que cambie la dirección de correo electrónico, pero en otras situaciones seguramente lo hará ...

Si no desea volver a mover las reglas a un nivel de abstracción inferior, como la entidad o un objeto de valor de correo electrónico, le sugiero encarecidamente que reduzca el dolor agrupando las reglas. Entonces, en su ejemplo de correo electrónico, las siguientes 3 reglas:

if(string.IsNullOrWhitespace(email)) throw new DomainException(“...”); if(!email.IsEmail()) throw new DomainException(); if(email.Contains(“@mailinator.com”)) throw new DomainException();

puede ser parte de un grupo EmailValidationRule que puede reutilizar más fácilmente.

Desde mi punto de vista no hay una respuesta explícita a la pregunta de dónde poner la lógica de validación. Puede ser parte de cada objeto dependiendo del nivel de abstracción. In you current case the formal checking of the email address can be part of an EmailValueObject and the mailinator rule can be part of a higher abstraction level concept in which you state that your user cannot have an email address pointing on that domain. So for example if somebody wants to contact with your user without registration, then you can check her email against formal validation, but you don''t have to check her email against the mailinator rule. Y así...

So I completely agree with @pjvds who claimed that this kind of awkward placed validation is a sign of a bad design. I don''t think you will have any gain by breaking encapsulation, but it''s your choice and it will be your pain.

Peligro ... Peligro Dr. Smith ... Publicación filosófica por delante

El propósito de esta publicación es determinar si ubicar la lógica de validación fuera de las entidades de mi dominio (en realidad, la raíz agregada) me está otorgando más flexibilidad o es un código kamikaze.

Básicamente, quiero saber si hay una mejor manera de validar mis entidades de dominio. Así es como planeo hacerlo, pero me gustaría tu opinión

El primer enfoque que consideré fue:

class Customer : EntityBase<Customer> { public void ChangeEmail(string email) { if(string.IsNullOrWhitespace(email)) throw new DomainException(“...”); if(!email.IsEmail()) throw new DomainException(); if(email.Contains(“@mailinator.com”)) throw new DomainException(); } }

En realidad no me gusta esta validación porque incluso cuando estoy encapsulando la lógica de validación en la entidad correcta, esto está violando el principio Abrir / Cerrar (Abrir para extensión pero Cerrar para modificación) y he encontrado que violando este principio, el mantenimiento del código se vuelve un verdadero dolor cuando la aplicación crece en complejidad. ¿Por qué? Debido a que las reglas de dominio cambian más a menudo de lo que nos gustaría admitir, y si las reglas están ocultas e incrustadas en una entidad como esta, son difíciles de probar, difíciles de leer, difíciles de mantener, pero la verdadera razón por la que no me gusta esto enfoque es: si las reglas de validación cambian, tengo que venir y editar mi entidad de dominio. Este ha sido un ejemplo realmente simple, pero en RL la validación podría ser más compleja

Entonces, siguiendo la filosofía de Udi Dahan, haciendo los roles explícitos , y la recomendación de Eric Evans en el libro azul, el siguiente intento fue implementar el patrón de especificación, algo como esto

class EmailDomainIsAllowedSpecification : IDomainSpecification<Customer> { private INotAllowedEmailDomainsResolver invalidEmailDomainsResolver; public bool IsSatisfiedBy(Customer customer) { return !this.invalidEmailDomainsResolver.GetInvalidEmailDomains().Contains(customer.Email); } }

Pero luego me doy cuenta de que para seguir este enfoque tuve que mutar mis entidades primero para pasar el valor que se valdría , en este caso el correo electrónico, pero mutarlas causaría que se dispararan mis eventos de dominio que no me gustaría. sucederá hasta que el nuevo correo electrónico sea válido

Entonces, después de considerar estos enfoques, salí con este, ya que voy a implementar una arquitectura CQRS:

class EmailDomainIsAllowedValidator : IDomainInvariantValidator<Customer, ChangeEmailCommand> { public void IsValid(Customer entity, ChangeEmailCommand command) { if(!command.Email.HasValidDomain()) throw new DomainException(“...”); } }

Bueno, esa es la idea principal, la entidad se pasa al validador en caso de que necesitemos algún valor de la entidad para realizar la validación, el comando contiene los datos que provienen del usuario y dado que los validadores se consideran objetos inyectables podrían tener dependencias externas inyectadas si la validación lo requiere

Ahora el dilema , estoy contento con un diseño como este porque mi validación está encapsulada en objetos individuales que ofrece muchas ventajas: fácil prueba de unidad, fácil de mantener, invariantes de dominio expresados ​​explícitamente usando el lenguaje ubicuo, fácil de extender, la lógica de validación es los validadores y los centralizados se pueden usar juntos para hacer cumplir reglas de dominio complejas. E incluso cuando sé que estoy colocando la validación de mis entidades fuera de ellas (podría argumentar un olor a código - Dominio anémico) pero creo que la compensación es aceptable

Pero hay una cosa que no he descubierto cómo implementarla de una manera limpia. ¿Cómo debo usar estos componentes ...

Dado que se inyectarán, no encajarán naturalmente dentro de las entidades de mi dominio, así que básicamente veo dos opciones:

  1. Pasar los validadores a cada método de mi entidad

  2. Validar mis objetos externamente (desde el controlador de comandos)

No estoy satisfecho con la opción 1, así que les explico cómo lo haría con la opción 2

class ChangeEmailCommandHandler : ICommandHandler<ChangeEmailCommand> { // here I would get the validators required for this command injected private IEnumerable<IDomainInvariantValidator> validators; public void Execute(ChangeEmailCommand command) { using (var t = this.unitOfWork.BeginTransaction()) { var customer = this.unitOfWork.Get<Customer>(command.CustomerId); // here I would validate them, something like this this.validators.ForEach(x =. x.IsValid(customer, command)); // here I know the command is valid // the call to ChangeEmail will fire domain events as needed customer.ChangeEmail(command.Email); t.Commit(); } } }

Bueno, esto es todo. ¿Puede darme su opinión sobre esto o compartir sus experiencias con la validación de entidades de dominio?

EDITAR

Creo que no está claro por mi pregunta, pero el verdadero problema es: ocultar las reglas de dominio tiene serias implicaciones en la futura mantenibilidad de la aplicación, y también las reglas de dominio cambian a menudo durante el ciclo de vida de la aplicación. Por lo tanto, implementarlos con esto en mente nos permitiría extenderlos fácilmente. Ahora imagine que en el futuro se implementa un motor de reglas, si las reglas se encapsulan fuera de las entidades de dominio, este cambio sería más fácil de implementar

Soy consciente de que colocar la validación fuera de mis entidades rompe la encapsulación como lo menciona @jgauffin en su respuesta, pero creo que los beneficios de colocar la validación en objetos individuales son mucho más sustanciales que simplemente mantener la encapsulación de una entidad. Ahora creo que la encapsulación tiene más sentido en una arquitectura tradicional de n niveles porque las entidades se usaron en varios lugares de la capa de dominio, pero en una arquitectura CQRS, cuando llega un comando, habrá un controlador de comandos accediendo a una raíz agregada y realizar operaciones contra la raíz agregada solo creando una ventana perfecta para colocar la validación.

Me gustaría hacer una pequeña comparación entre las ventajas de colocar la validación dentro de una entidad versus colocarla en objetos individuales

  • Validación en objetos individuales

    • Pro. Fácil de escribir
    • Pro. Fácil de probar
    • Pro. Se expresa explícitamente
    • Pro. Se convierte en parte del diseño del Dominio, expresado con el Idioma ubicuo actual
    • Pro. Como ahora es parte del diseño, se puede modelar usando diagramas UML
    • Pro. Extremadamente fácil de mantener
    • Pro. Hace que mis entidades y la lógica de validación se acoplen poco
    • Pro. Fácil de extender
    • Pro. Siguiendo el SRP
    • Pro. Siguiendo el principio de Abrir / Cerrar
    • Pro. ¿No está incumpliendo la ley de Demeter (mmm)?
    • Pro. Estoy centralizado
    • Pro. Podría ser reutilizable
    • Pro. Si es necesario, las dependencias externas se pueden inyectar fácilmente
    • Pro. Si se utiliza un modelo de complemento, se pueden agregar nuevos validadores simplemente dejando caer los nuevos ensamblajes sin la necesidad de volver a compilar toda la aplicación
    • Pro. Implementar un motor de reglas sería más fácil
    • Estafa. Breaking encapsulation
    • Estafa. Si la encapsulación es obligatoria, tendríamos que pasar los validadores individuales al método de entidad (agregado)
  • Validación encapsulada dentro de la entidad

    • Pro. Encapsulado?
    • Pro. Reutilizable?

Me encantaría leer tus pensamientos sobre esto


Estoy al comienzo de un proyecto y voy a implementar mi validación fuera de las entidades de mi dominio. Mis entidades de dominio contendrán lógica para proteger cualquier invariante (como argumentos faltantes, valores nulos, cadenas vacías, colecciones, etc.). Pero las reglas de negocio reales vivirán en clases de validador. Soy de la mentalidad de @SonOfPirate ...

Estoy usando FluentValidation que esencialmente me dará un montón de validadores que actúan en las entidades de mi dominio: también conocido como el patrón de especificación. Además, de acuerdo con los patrones descritos en el libro azul de Eric, puedo construir validadores con cualquier información que puedan necesitar para realizar las validaciones (ya sea desde la base de datos u otro repositorio o servicio). También tendría la opción de inyectar cualquier dependencia aquí también. También puedo componer y reutilizar estos validadores (por ejemplo, un validador de direcciones puede reutilizarse tanto en un validador de empleado como en un validador de empresa). Tengo una fábrica Validator que actúa como un "localizador de servicios":

public class ParticipantService : IParticipantService { public void Save(Participant participant) { IValidator<Participant> validator = _validatorFactory.GetValidator<Participant>(); var results = validator.Validate(participant); //if the participant is valid, register the participant with the unit of work if (results.IsValid) { if (participant.IsNew) { _unitOfWork.RegisterNew<Participant>(participant); } else if (participant.HasChanged) { _unitOfWork.RegisterDirty<Participant>(participant); } } else { _unitOfWork.RollBack(); //do some thing here to indicate the errors:generate an exception (or fault) that contains the validation errors. Or return the results } } }

Y el validador contendría código, algo como esto:

public class ParticipantValidator : AbstractValidator<Participant> { public ParticipantValidator(DateTime today, int ageLimit, List<string> validCompanyCodes, /*any other stuff you need*/) {...} public void BuildRules() { RuleFor(participant => participant.DateOfBirth) .NotNull() .LessThan(m_today.AddYears(m_ageLimit*-1)) .WithMessage(string.Format("Participant must be older than {0} years of age.", m_ageLimit)); RuleFor(participant => participant.Address) .NotNull() .SetValidator(new AddressValidator()); RuleFor(participant => participant.Email) .NotEmpty() .EmailAddress(); ... } }

Tenemos que admitir más de un tipo de presentación: sitios web, winforms y carga masiva de datos a través de servicios. Debajo de fijar todo esto hay un conjunto de servicios que exponen la funcionalidad del sistema de una manera única y consistente. No utilizamos Entity Framework ni ORM por razones que no le aburriré.

He aquí por qué me gusta este enfoque:

  • Las reglas comerciales que están contenidas en los validadores son totalmente comprobables por unidad.
  • Puedo componer reglas más complejas a partir de reglas más simples
  • Puedo usar los validadores en más de una ubicación en mi sistema (admitimos sitios web y Winforms, y servicios que exponen la funcionalidad), por lo que si hay una regla ligeramente diferente para un caso de uso en un servicio que difiera de los sitios web, entonces Yo puedo manejar eso.
  • Toda la validación se expresa en una ubicación y puedo elegir cómo / dónde inyectar y componer esto.

Estoy de acuerdo con una serie de conceptos presentados en otras respuestas, pero los puse juntos en mi código.

En primer lugar, estoy de acuerdo en que el uso de Value Objects para valores que incluyen el comportamiento es una gran manera de encapsular reglas comerciales comunes y una dirección de correo electrónico es un candidato perfecto. Sin embargo, tiendo a limitar esto a reglas que son constantes y no cambiarán con frecuencia. Estoy seguro de que está buscando un enfoque más general y el correo electrónico es solo un ejemplo, por lo que no me concentraré en ese único caso de uso.

La clave de mi enfoque es reconocer que la validación tiene diferentes propósitos en diferentes lugares de una aplicación. En pocas palabras, valide solo lo que se requiere para garantizar que la operación actual se pueda ejecutar sin resultados inesperados / no deseados. Eso lleva a la pregunta de qué validación debería ocurrir ¿dónde?

En su ejemplo, me pregunto si a la entidad de dominio realmente le importa que la dirección de correo electrónico cumpla con algún patrón y otras reglas o simplemente nos importa que ''correo electrónico'' no pueda ser nulo o en blanco cuando se llame a ChangeEmail? Si esto último, solo se necesita una simple verificación para asegurar que haya un valor presente, todo se necesita en el método ChangeEmail.

En CQRS, todos los cambios que modifican el estado de la aplicación ocurren como comandos con la implementación en controladores de comandos (como lo ha demostrado). Típicamente colocaré cualquier ''gancho'' en las reglas comerciales, etc. que validan que la operación PUEDE realizarse en el controlador de comandos. De hecho, sigo tu enfoque de inyectar validadores en el controlador de comandos, lo que me permite ampliar / reemplazar el conjunto de reglas sin realizar cambios en el controlador. Estas reglas "dinámicas" me permiten definir las reglas comerciales, como lo que constituye una dirección válida de correo electrónico, antes de cambiar el estado de la entidad, lo que garantiza que no entre en un estado no válido. Pero la "invalidez" en este caso está definida por la lógica comercial y, como usted señaló, es altamente volátil.

Después de pasar por los rangos de CSLA, encontré que este cambio es difícil de adoptar porque parece romper la encapsulación. Pero estoy seguro de que la encapsulación no se rompe si das un paso atrás y preguntas qué función de validación realmente sirve en el modelo.

He encontrado que estos matices son muy importantes para mantener mi mente clara sobre este tema. Existe una validación para evitar datos incorrectos (por ejemplo, argumentos faltantes, valores nulos, cadenas vacías, etc.) que pertenecen al método en sí y existe validación para garantizar el cumplimiento de las reglas comerciales. En el caso de los primeros, si el Cliente debe tener una dirección de correo electrónico, entonces la única regla de la que debo preocuparme para evitar que mi objeto de dominio se vuelva inválido es asegurarme de que se haya proporcionado una dirección de correo electrónico al Método ChangeEmail. Las otras reglas son preocupaciones de mayor nivel con respecto a la validez del valor en sí mismo y realmente no tienen ningún efecto sobre la validez de la propia entidad de dominio.

Esta ha sido la fuente de muchas ''discusiones'' con otros desarrolladores, pero cuando la mayoría tiene una visión más amplia e investiga la función que realmente cumple la validación, tienden a ver la luz.

Finalmente, también hay un lugar para la validación de la interfaz de usuario (y por UI me refiero a todo lo que sirve como interfaz para la aplicación, ya sea una pantalla, punto final de servicio o lo que sea). Me resulta perfectamente razonable duplicar parte de la lógica en la interfaz de usuario para proporcionar una mejor interactividad para el usuario. Pero es porque esta validación cumple ese único propósito por el que permití esa duplicación. Sin embargo, el uso de objetos de validación / especificación inyectados promueve la reutilización de esta manera sin las implicaciones negativas de tener estas reglas definidas en múltiples ubicaciones.

No estoy seguro si eso ayuda o no...


No llamaría a una clase que hereda de EntityBase mi modelo de dominio, ya que lo EntityBase a su capa de persistencia. Pero esa es solo mi opinión.

No movería la lógica de validación de correo electrónico del Customer a cualquier otra cosa para seguir el principio de abierto / cerrado. Para mí, seguir abierto / cerrado significaría que tienes la siguiente jerarquía:

public class User { // some basic validation public virtual void ChangeEmail(string email); } public class Employee : User { // validates internal email public override void ChangeEmail(string email); } public class Customer : User { // validate external email addresses. public override void ChangeEmail(string email); }

Sus sugerencias mueven el control del modelo de dominio a una clase arbitraria, rompiendo así la encapsulación. Prefiero refactorizar mi clase ( Customer ) para cumplir con las nuevas reglas comerciales que hacerlo.

Utilice eventos de dominio para activar otras partes del sistema para obtener una arquitectura más débilmente acoplada, pero no use comandos / eventos para violar la encapsulación.

Excepciones

Me acabo de dar cuenta de que DomainException . Esa es una forma de excepción genérica. ¿Por qué no utilizas las excepciones de argumento o FormatException ? Describieron el error mucho mejor. Y no se olvide de incluir información contextual que lo ayude a evitar la excepción en el futuro.

Actualizar

Colocar la lógica fuera de la clase es pedir problemas. ¿Cómo se controla qué regla de validación se usa? Una parte del código puede usar SomeVeryOldRule al validar mientras que otra usa NewAndVeryStrictRule . Puede que no sea a propósito, pero puede y sucederá cuando la base de código crezca.

Parece que ya ha decidido ignorar uno de los fundamentos de OOP (encapsulación). Siga adelante y use un marco de validación genérico / externo, pero no diga que no lo advertí;)

Actualización2

Gracias por su paciencia y sus respuestas, y esa es la razón por la que publiqué esta pregunta, creo que la entidad debe ser responsable de garantizar que esté en un estado válido (y lo he hecho en proyectos anteriores), pero los beneficios de colocarlo en objetos individuales es enorme y como publiqué incluso hay una forma de usar objetos individuales y mantener la encapsulación, pero personalmente no estoy muy contento con el diseño, pero por otro lado no está fuera de la mesa, considera este ChangeEmail (IEnumerable> validators , string email) No he pensado en detalle la implementación. aunque

Eso le permite al programador especificar cualquier regla, pueden ser o no las reglas comerciales actualmente correctas. El desarrollador podría simplemente escribir

customer.ChangeEmail(new IValidator<Customer>[] { new NonValidatingRule<Customer>() }, "notAnEmail")

que acepta todo. Y las reglas deben especificarse en cada lugar donde se ChangeEmail .

Si desea usar un motor de reglas, cree un proxy singleton:

public class Validator { IValidatorEngine _engine; public static void Assign(IValidatorEngine engine) { _engine = engine; } public static IValidatorEngine Current { get { return _engine; } } }

.. y usarlo desde dentro de los métodos del modelo de dominio como

public class Customer { public void ChangeEmail(string email) { var rules = Validator.GetRulesFor<Customer>("ChangeEmail"); rules.Validate(email); // valid } }

El problema con esa solución es que se convertirá en una pesadilla de mantenimiento ya que las dependencias de reglas están ocultas. Nunca se puede decir si todas las reglas se han especificado y funcionan, a menos que pruebe cada método de modelo de dominio y cada escenario de regla para cada método.

La solución es más flexible, pero tomará mucho más tiempo implementarla que refactorizar el método para cambiar las reglas comerciales.


No puedo decir que lo que hice es lo mejor que puedo hacer, todavía estoy luchando con este problema y peleando una pelea a la vez. Pero he estado haciendo lo siguiente:

Tengo clases básicas para encapsular la validación:

public interface ISpecification<TEntity> where TEntity : class, IAggregate { bool IsSatisfiedBy(TEntity entity); } internal class AndSpecification<TEntity> : ISpecification<TEntity> where TEntity: class, IAggregate { private ISpecification<TEntity> Spec1; private ISpecification<TEntity> Spec2; internal AndSpecification(ISpecification<TEntity> s1, ISpecification<TEntity> s2) { Spec1 = s1; Spec2 = s2; } public bool IsSatisfiedBy(TEntity candidate) { return Spec1.IsSatisfiedBy(candidate) && Spec2.IsSatisfiedBy(candidate); } } internal class OrSpecification<TEntity> : ISpecification<TEntity> where TEntity : class, IAggregate { private ISpecification<TEntity> Spec1; private ISpecification<TEntity> Spec2; internal OrSpecification(ISpecification<TEntity> s1, ISpecification<TEntity> s2) { Spec1 = s1; Spec2 = s2; } public bool IsSatisfiedBy(TEntity candidate) { return Spec1.IsSatisfiedBy(candidate) || Spec2.IsSatisfiedBy(candidate); } } internal class NotSpecification<TEntity> : ISpecification<TEntity> where TEntity : class, IAggregate { private ISpecification<TEntity> Wrapped; internal NotSpecification(ISpecification<TEntity> x) { Wrapped = x; } public bool IsSatisfiedBy(TEntity candidate) { return !Wrapped.IsSatisfiedBy(candidate); } } public static class SpecsExtensionMethods { public static ISpecification<TEntity> And<TEntity>(this ISpecification<TEntity> s1, ISpecification<TEntity> s2) where TEntity : class, IAggregate { return new AndSpecification<TEntity>(s1, s2); } public static ISpecification<TEntity> Or<TEntity>(this ISpecification<TEntity> s1, ISpecification<TEntity> s2) where TEntity : class, IAggregate { return new OrSpecification<TEntity>(s1, s2); } public static ISpecification<TEntity> Not<TEntity>(this ISpecification<TEntity> s) where TEntity : class, IAggregate { return new NotSpecification<TEntity>(s); } }

y para usarlo, hago lo siguiente:

controlador de comando:

public class MyCommandHandler : CommandHandler<MyCommand> { public override CommandValidation Execute(MyCommand cmd) { Contract.Requires<ArgumentNullException>(cmd != null); var existingAR= Repository.GetById<MyAggregate>(cmd.Id); if (existingIntervento.IsNull()) throw new HandlerForDomainEventNotFoundException(); existingIntervento.DoStuff(cmd.Id , cmd.Date ... ); Repository.Save(existingIntervento, cmd.GetCommitId()); return existingIntervento.CommandValidationMessages; }

el agregado :

public void DoStuff(Guid id, DateTime dateX,DateTime start, DateTime end, ...) { var is_date_valid = new Is_dateX_valid(dateX); var has_start_date_greater_than_end_date = new Has_start_date_greater_than_end_date(start, end); ISpecification<MyAggregate> specs = is_date_valid .And(has_start_date_greater_than_end_date ); if (specs.IsSatisfiedBy(this)) { var evt = new AgregateStuffed() { Id = id , DateX = dateX , End = end , Start = start , ... }; RaiseEvent(evt); } }

la especificación ahora está integrada en estas dos clases:

public class Is_dateX_valid : ISpecification<MyAggregate> { private readonly DateTime _dateX; public Is_data_consuntivazione_valid(DateTime dateX) { Contract.Requires<ArgumentNullException>(dateX== DateTime.MinValue); _dateX= dateX; } public bool IsSatisfiedBy(MyAggregate i) { if (_dateX> DateTime.Now) { i.CommandValidationMessages.Add(new ValidationMessage("datex greater than now")); return false; } return true; } } public class Has_start_date_greater_than_end_date : ISpecification<MyAggregate> { private readonly DateTime _start; private readonly DateTime _end; public Has_start_date_greater_than_end_date(DateTime start, DateTime end) { Contract.Requires<ArgumentNullException>(start == DateTime.MinValue); Contract.Requires<ArgumentNullException>(start == DateTime.MinValue); _start = start; _end = end; } public bool IsSatisfiedBy(MyAggregate i) { if (_start > _end) { i.CommandValidationMessages.Add(new ValidationMessage(start date greater then end date")); return false; } return true; } }

Esto me permite reutilizar algunas validaciones para diferentes agregados y es fácil de probar. Si ve algún flujo en él. Me encantaría discutirlo.

tuya,


No recomendaría trowing grandes pedazos de código en su dominio para la validación. Eliminamos la mayoría de nuestras valuaciones incómodas al verlas como un olor a conceptos faltantes en nuestro dominio. En el código de muestra que escribe, veo la validación de una dirección de correo electrónico. Un cliente no tiene nada que ver con la validación de correo electrónico.

¿Por qué no hacer un ValueObject llamado Email que hace esta validación en la construcción?

Mi experiencia es que las validaciones incómodas son indicios de conceptos perdidos en tu dominio. Puede atraparlos en objetos de Validator, pero prefiero el objeto de valor porque hace que el concepto relacionado sea parte de su dominio.


Pones la validación en el lugar equivocado.

Debería usar ValueObjects para tales cosas. Mire esta presentación http://www.infoq.com/presentations/Value-Objects-Dan-Bergh-Johnsson También le enseñará sobre los datos como centros de gravedad.

También hay una muestra de cómo reutilizar la validación de datos, como por ejemplo el uso de métodos de validación estáticos ala Email.IsValid (cadena)


I wrote a blog post on this topic a while back. The premise of the post was that there are different types of validation. I called them Superficial Validation and Domain Based Command Validation.

This simple version is this. Validating things like ''is it a number'' or ''email address'' are more often than not just superficial. These can be done before the command reaches the domain entities.

However, where the validation is more tied to the domain then it''s right place is in the domain. For example, maybe you have some rules about the weight and type of cargo a certain lorry can take. This sounds much more like domain logic.

Then you have the hybrid types. Things like set based validation. These need to happen before the command is issued or injected into the domain (try to avoid that if at all possible - limiting dependencies is a good thing).

Anyway, you can read the full post here: How To Validate Commands in a CQRS Application


I''m still experimenting with this concept but you can try Decorators. If you use SimpleInjector you can easily inject your own validation classes that run ahead of your command handler. Then the command can assume it is valid if it got that far. However, This means all validation should be done on the command and not the entities. The entities won''t go into an invalid state. But each command must implement its own validation fully so similar commands may have duplication of rules but you could either abstract common rules to share or treat different commands as truly separate.


The validation in your example is validation of a value object, not an entity (or aggregate root).

I would separate the validation into distinct areas.

  1. Validate internal characteristics of the Email value object internally.

I adhere to the rule that aggregates should never be in an invalid state. I extend this principal to value objects where practical.

Use createNew() to instantiate an email from user input. This forces it to be valid according to your current rules (the "[email protected]" format, for example).

Use createExisting() to instantiate an email from persistent storage. This performs no validation, which is important - you don''t want an exception to be thrown for a stored email that was valid yesterday but invalid today.

class Email { private String value_; // Error codes const Error E_LENGTH = "An email address must be at least 3 characters long."; const Error E_FORMAT = "An email address must be in the ''[email protected]'' format."; // Private constructor, forcing the use of factory functions private Email(String value) { this.value_ = value; } // Factory functions static public Email createNew(String value) { validateLength(value, E_LENGTH); validateFormat(value, E_FORMAT); } static public Email createExisting(String value) { return new Email(value); } // Static validation methods static public void validateLength(String value, Error error = E_LENGTH) { if (value.length() < 3) { throw new DomainException(error); } } static public void validateFormat(String value, Error error = E_FORMAT) { if (/* regular expression fails */) { throw new DomainException(error); } } }

  1. Validate "external" characteristics of the Email value object externally, eg, in a service.

    class EmailDnsValidator implements IEmailValidator { const E_MX_MISSING = "The domain of your email address does not have an MX record."; private DnsProvider dnsProvider_; EmailDnsValidator(DnsProvider dnsProvider) { dnsProvider_ = dnsProvider; } public void validate(String value, Error error = E_MX_MISSING) { if (!dnsProvider_.hasMxRecord(/* domain part of email address */)) { throw new DomainException(error); } } } class EmailDomainBlacklistValidator implements IEmailValidator { const Error E_DOMAIN_FORBIDDEN = "The domain of your email address is blacklisted."; public void validate(String value, Error error = E_DOMAIN_FORBIDDEN) { if (/* domain of value is on the blacklist */)) { throw new DomainException(error); } } }

Ventajas:

  • Use of the createNew() and createExisting() factory functions allow control over internal validation.

  • It is possible to "opt out" of certain validation routines, eg, skip the length check, using the validation methods directly.

  • It is also possible to "opt out" of external validation (DNS MX records and domain blacklisting). Eg, a project I worked on initially validated the existance of MX records for a domain, but eventually removed this because of the number of customers using "dynamic IP" type solutions.

  • It is easy to query your persistent store for email addresses that do not fit the current validation rules, but running a simple query and treating each email as "new" rather than "existing" - if an exception is thrown, there''s a problem. From there you can issue, for example, a FlagCustomerAsHavingABadEmail command, using the exception error message as guidance for the user when they see the message.

  • Allowing the programmer to supply the error code provides flexibility. For example, when sending a UpdateEmailAddress command, the error of "Your email address must be at least 3 characters long" is self explanatory. However, when updating multiple email addresses (home and work), the above error message does not indicate WHICH email was wrong. Supplying the error code/message allows you to provide richer feedback to the end user.


You can use a message based solution with Domain Events as explained here .

Exceptions are not the right method for all validation errors, is not said that a not valid entity is an exceptional case.

If the validation is not trivial, the logic to validate the aggregate can be executed directly on the server and while you are trying to set new input you can raise a Domain Event to tell to the user (or the application that is using your domain) why the input is not correct.