usar reglas regla poner mover medicion lineas inteligentes illustrator herramienta guias funcionan corte como c# java domain-driven-design

c# - reglas - herramienta medicion illustrator



Dónde poner la validación de reglas globales en DDD (9)

Soy nuevo en DDD, y estoy tratando de aplicarlo en la vida real. No hay preguntas sobre dicha lógica de validación, como comprobación nula, comprobación de cadenas vacías, etc., que va directamente al constructor / propiedad de la entidad. ¿Pero dónde poner la validación de algunas reglas globales como ''Nombre de usuario único''?

Entonces, tenemos una entidad Usuario

public class User : IAggregateRoot { private string _name; public string Name { get { return _name; } set { _name = value; } } // other data and behavior }

Y repositorio para usuarios

public interface IUserRepository : IRepository<User> { User FindByName(string name); }

Las opciones son:

  1. Inyectar el repositorio a la entidad
  2. Inyectar el repositorio a la fábrica
  3. Crear operación en el servicio de dominio
  4. ???

Y cada opción más detallada:

1. Inyectar repositorio en la entidad

Puedo consultar el repositorio en las entidades constructor / property. Pero creo que mantener la referencia al repositorio en la entidad es un mal olor.

public User(IUserRepository repository) { _repository = repository; } public string Name { get { return _name; } set { if (_repository.FindByName(value) != null) throw new UserAlreadyExistsException(); _name = value; } }

Actualización: podemos usar DI para ocultar la dependencia entre el usuario y IUserRepository a través del objeto de especificación.

2. Inyectar el repositorio a la fábrica

Puedo poner esta lógica de verificación en UserFactory. Pero, ¿y si queremos cambiar el nombre del usuario ya existente?

3. Crear operación en el servicio de dominio

Puedo crear un servicio de dominio para crear y editar usuarios. Pero alguien puede editar directamente el nombre del usuario sin llamar a ese servicio ...

public class AdministrationService { private IUserRepository _userRepository; public AdministrationService(IUserRepository userRepository) { _userRepository = userRepository; } public void RenameUser(string oldName, string newName) { if (_userRepository.FindByName(newName) != null) throw new UserAlreadyExistException(); User user = _userRepository.FindByName(oldName); user.Name = newName; _userRepository.Save(user); } }

4. ???

¿Dónde colocas la lógica de validación global para las entidades?

¡Gracias!


Crear servicio de dominio

O puedo crear un servicio de dominio para crear y editar usuarios. Pero alguien puede editar directamente el nombre del usuario sin llamar a ese servicio ...

Si diseñó adecuadamente sus entidades, esto no debería ser un problema.


Cree un método, por ejemplo, llamado IsUserNameValid () y haga que sea accesible desde cualquier lugar. Yo mismo lo pondría en el servicio al usuario. Hacer esto no lo limitará cuando surjan cambios futuros. Mantiene el código de validación en un solo lugar (implementación), y el otro código que depende de él no tendrá que cambiar si la validación cambia. Puede encontrar que necesita llamar esto desde múltiples lugares más adelante, como la interfaz de usuario para indicación visual sin tener que recurrir al manejo de excepciones. La capa de servicio para las operaciones correctas y la capa de repositorio (caché, db, etc.) para garantizar que los elementos almacenados sean válidos.


En mi Framework CQRS, cada clase de Command Handler también contiene un método ValidateCommand, que luego llama a la lógica de negocio / validación apropiada en el Dominio (principalmente implementado como métodos de Entidad o métodos estáticos de Entidad).

Entonces la persona que llama haría así:

if (cmdService.ValidateCommand(myCommand) == ValidationResult.OK) { // Now we can assume there will be no business reason to reject // the command cmdService.ExecuteCommand(myCommand); // Async }

Cada controlador de comando especializado contiene la lógica de contenedor, por ejemplo:

public ValidationResult ValidateCommand(MakeCustomerGold command) { var result = new ValidationResult(); if (Customer.CanMakeGold(command.CustomerId)) { // "OK" logic here } else { // "Not OK" logic here } }

El método ExecuteCommand del controlador de comando llamará de nuevo a ValidateCommand (), por lo que incluso si el cliente no se molestó, no sucederá nada en el dominio que no se supone que deba hacerlo.


La mayoría de las veces, es mejor colocar este tipo de reglas en los objetos de Specification . Puede colocar estas Specification en sus paquetes de dominio, para que cualquiera que use su paquete de dominio tenga acceso a ellas. Usando una especificación, puede agrupar sus reglas comerciales con sus entidades, sin crear entidades difíciles de leer con dependencias no deseadas en servicios y repositorios. Si es necesario, puede inyectar dependencias en servicios o repositorios en una especificación.

Dependiendo del contexto, puede construir diferentes validadores utilizando los objetos de especificación.

La principal preocupación de las entidades debería ser hacer un seguimiento del estado de la empresa, eso es suficiente responsabilidad y no deberían preocuparse por la validación.

Ejemplo

public class User { public string Id { get; set; } public string Name { get; set; } }

Dos especificaciones:

public class IdNotEmptySpecification : ISpecification<User> { public bool IsSatisfiedBy(User subject) { return !string.IsNullOrEmpty(subject.Id); } } public class NameNotTakenSpecification : ISpecification<User> { // omitted code to set service; better use DI private Service.IUserNameService UserNameService { get; set; } public bool IsSatisfiedBy(User subject) { return UserNameService.NameIsAvailable(subject.Name); } }

Y un validador:

public class UserPersistenceValidator : IValidator<User> { private readonly IList<ISpecification<User>> Rules = new List<ISpecification<User>> { new IdNotEmptySpecification(), new NameNotEmptySpecification(), new NameNotTakenSpecification() // and more ... better use DI to fill this list }; public bool IsValid(User entity) { return BrokenRules(entity).Count() > 0; } public IEnumerable<string> BrokenRules(User entity) { return Rules.Where(rule => !rule.IsSatisfiedBy(entity)) .Select(rule => GetMessageForBrokenRule(rule)); } // ... }

Para completar, las interfaces:

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

Notas

Creo que la respuesta anterior de Vijay Patel va en la dirección correcta, pero siento que está un poco mal. Sugiere que la entidad de usuario depende de la especificación, donde creo que esto debería ser al revés. De esta forma, puede permitir que la especificación dependa de los servicios, los repositorios y el contexto en general, sin que su entidad dependa de ellos a través de una dependencia de especificación.

Referencias

Una pregunta relacionada con una buena respuesta con un ejemplo: Validación en un Diseño Dirigido por Dominio .

Eric Evans describe el uso del patrón de especificación para la validación, selección y construcción de objetos en el capítulo 9, página 145 .

Este artículo sobre el patrón de especificación con una aplicación en .Net podría ser de su interés.


Me gusta la opción 3. La implementación más simple podría verse así:

public interface IUser { string Name { get; } bool IsNew { get; } } public class User : IUser { public string Name { get; private set; } public bool IsNew { get; private set; } } public class UserService : IUserService { public void ValidateUser(IUser user) { var repository = RepositoryFactory.GetUserRepository(); // use IoC if needed if (user.IsNew && repository.UserExists(user.Name)) throw new ValidationException("Username already exists"); } }


No recomendaría no permitir el cambio de propiedades en la entidad, si es una entrada del usuario. Por ejemplo, si la validación no se aprueba, aún puede usar la instancia para mostrarla en la interfaz de usuario con los resultados de validación, lo que permite al usuario corregir el error.

Jimmy Nilsson en su "Aplicación de patrones y diseño impulsados ​​por el dominio" recomienda validar para una operación en particular, no solo para persistir. Si bien una entidad puede persistir con éxito, la validación real ocurre cuando una entidad está a punto de cambiar su estado, por ejemplo, el estado ''Pedido'' cambia a ''Comprado''.

Al crear, la instancia debe ser válida para guardar, lo que implica verificar la exclusividad. Es diferente de la validación de pedidos, donde no solo debe verificarse la exclusividad, sino también, por ejemplo, la credibilidad de un cliente y la disponibilidad en la tienda.

Por lo tanto, la lógica de validación no debe invocarse en una asignación de propiedades, sino que debe invocarse en operaciones de nivel agregado, ya sean persistentes o no.


No soy un experto en DDD, pero me he hecho las mismas preguntas y esto es lo que se me ocurrió: la lógica de validación normalmente debe ir al constructor / fábrica y los instaladores. De esta manera, garantiza que siempre tiene objetos de dominio válidos. Pero si la validación involucra consultas de bases de datos que afectan su desempeño, una implementación eficiente requiere un diseño diferente.

(1) Entidades Inyectantes: Las entidades de inyección pueden ser difíciles desde el punto de vista técnico y también dificultan la gestión del rendimiento de las aplicaciones debido a la fragmentación de la lógica de la base de datos. Las operaciones aparentemente simples ahora pueden tener un impacto inesperado en el rendimiento. También hace que sea imposible optimizar su objeto de dominio para operaciones en grupos del mismo tipo de entidades, ya no puede escribir una sola consulta de grupo, y en su lugar siempre tiene consultas individuales para cada entidad.

(2) Repositorio de inyección: no debe colocar ninguna lógica comercial en los repositorios. Mantenga los repositorios simples y enfocados. Deben actuar como si fueran colecciones y solo contienen lógica para agregar, eliminar y encontrar objetos (algunos incluso derivan los métodos de búsqueda a otros objetos).

(3) Servicio de dominio Este parece ser el lugar más lógico para manejar la validación que requiere consultas en la base de datos. Una buena implementación haría que el constructor / fábrica y los instaladores envuelvan el paquete en privado, de modo que las entidades solo puedan ser creadas / modificadas con el servicio de dominio.


Yo usaría una especificación para encapsular la regla. A continuación, puede llamar cuando se actualice la propiedad UserName (o desde cualquier otro lugar que pueda necesitarla):

public class UniqueUserNameSpecification : ISpecification { public bool IsSatisifiedBy(User user) { // Check if the username is unique here } } public class User { string _Name; UniqueUserNameSpecification _UniqueUserNameSpecification; // You decide how this is injected public string Name { get { return _Name; } set { if (_UniqueUserNameSpecification.IsSatisifiedBy(this)) { _Name = value; } else { // Execute your custom warning here } } } }

No importará si otro desarrollador intenta modificar User.Name directamente, porque la regla siempre se ejecutará.

Descubre más aquí


Editar: a juzgar por las otras respuestas, el nombre correcto para dicho ''servicio de dominio'' es la especificación . He actualizado mi respuesta para reflejar esto, incluida una muestra de código más detallada.

Me gustaría ir con la opción 3; crear una especificación de servicio de dominio que encapsula la lógica real que realiza la validación. Por ejemplo, la especificación inicialmente llama a un repositorio, pero puede reemplazarlo con una llamada de servicio web en una etapa posterior. Tener toda esa lógica detrás de una especificación abstracta mantendrá el diseño general más flexible.

Para evitar que alguien edite el nombre sin validarlo, convierta la especificación en un aspecto requerido para editar el nombre. Puede lograr esto cambiando la API de su entidad a algo como esto:

public class User { public string Name { get; private set; } public void SetName(string name, ISpecification<User, string> specification) { // Insert basic null validation here. if (!specification.IsSatisfiedBy(this, name)) { // Throw some validation exception. } this.Name = name; } } public interface ISpecification<TType, TValue> { bool IsSatisfiedBy(TType obj, TValue value); } public class UniqueUserNameSpecification : ISpecification<User, string> { private IUserRepository repository; public UniqueUserNameSpecification(IUserRepository repository) { this.repository = repository; } public bool IsSatisfiedBy(User obj, string value) { if (value == obj.Name) { return true; } // Use this.repository for further validation of the name. } }

Su código de llamada se vería así:

var userRepository = IoC.Resolve<IUserRepository>(); var specification = new UniqueUserNameSpecification(userRepository); user.SetName("John", specification);

Y, por supuesto, puede ISpecification de ISpecification en las pruebas de su unidad para una prueba más sencilla.