resumen que por orientado orientada impulsado guiado ejemplo driven dominio diseño ddd arquitectura .net domain-driven-design cqrs domain-model oop

.net - que - Diseño impulsado por dominio, objetos de dominio, actitud sobre Setters



diseño orientado por el dominio (7)

La razón detrás de esto es que la entidad misma debería ser responsable de cambiar su estado. No hay realmente ninguna razón por la que deba establecer una propiedad en cualquier lugar fuera de la entidad misma.

Un ejemplo simple sería una entidad que tiene un nombre. Si tiene un organismo público, podrá cambiar el nombre de una entidad desde cualquier lugar de su aplicación. Si, en cambio, quita ese setter y pone un método como ChangeName(string name) en su entidad, esa será la única manera de cambiar el nombre. De esta forma, puede agregar cualquier clase de lógica que siempre se ejecutará cuando cambie el nombre porque solo hay una forma de cambiarlo. Esto también es mucho más claro que simplemente establecer el nombre a algo.

Básicamente, esto significa que expones el comportamiento de tus entidades públicamente mientras guardas el estado en privado.

He estado viendo algunos videos de Greg Young últimamente y estoy tratando de entender por qué hay una actitud negativa hacia Setters en objetos de Dominio. Pensé que se suponía que los objetos de Dominio eran "pesados" con la lógica en DDD. ¿Hay algún buen ejemplo en línea del mal ejemplo y luego forma de corregirlo? Cualquier ejemplo o explicación es bueno. ¿Esto solo se aplica a los eventos almacenados en una forma CQRS o esto se aplica a todos los DDD?


Un colocador simplemente establece un valor. No se supone que sea "heavy" with logic .

Los métodos en sus objetos que tienen buenos nombres descriptivos deberían ser los "heavy" with logic y tener un análogo en el dominio mismo.



La pregunta original está etiquetada como .net, por lo que presentaré un enfoque pragmático para el contexto en el que desea vincular sus entidades directamente a una vista.

Sé que eso es una mala práctica, y que probablemente debería tener un modelo de vista (como en MVVM) o similar, pero para algunas aplicaciones pequeñas, simplemente tiene sentido no sobre-patronizar en mi humilde opinión.

El uso de propiedades es la forma en que el enlace de datos listo para usar funciona en .net. Tal vez el contexto estipule que el enlace de datos debería funcionar en ambos sentidos y, por lo tanto, implementar INotifyPropertyChanged y subir PropertyChanged como parte de la lógica del setter tiene sentido.

La entidad podría, por ejemplo, agregar un artículo a una colección de reglas rotas o similar (sé que CSLA tuvo ese concepto hace unos años y quizás todavía lo haga) cuando el cliente establece un valor no válido, y esa colección podría mostrarse en la UI. La unidad de trabajo más tarde se negaría a persistir objetos inválidos, si llegara tan lejos.

Intento justificar el desacoplamiento, la inmutabilidad, etc., en gran medida. Solo digo que en algunos contextos se necesita una arquitectura más simple.


Setters no lleva ninguna información semántica.

p.ej

blogpost.PublishDate = DateTime.Now;

¿Eso significa que la publicación ha sido publicada? O simplemente que la fecha de publicación se ha establecido?

Considerar:

blogpost.Publish();

Esto muestra claramente la intención de que el blogpost se publique.

Además, los setters pueden romper las invariantes del objeto. Por ejemplo, si tenemos una entidad "Triángulo", el invariante debería ser que la suma de todos los ángulos debería ser de 180 grados.

Assert.AreEqual (t.A + t.B + t.C ,180);

Ahora si tenemos setters, podemos romper fácilmente las invariantes:

t.A = 0; t.B = 0; t.C = 0;

Entonces tenemos un triángulo donde la suma de todos los ángulos es 0 ... ¿Es realmente un Triángulo? Yo diría que no.

Reemplazar setters con métodos podría obligarnos a mantener invariantes:

t.SetAngles(0,0,0); //should fail

Dicha llamada debe arrojar una excepción que le indique que esto causa un estado inválido para su entidad.

Entonces obtienes semántica e invariantes con métodos en lugar de setters.


Puede que esté fuera de aquí, pero creo que los setters deberían usarse para establecer, a diferencia de los métodos setter. Tengo algunas razones para esto.

a) Tiene sentido en .Net. Todo desarrollador conoce las propiedades. Así es como pones las cosas en un objeto. ¿Por qué desviarse de eso para los objetos de dominio?

b) Setters pueden tener código. Antes de 3.5, creo que establecer un objeto consistía en una variable interna y una firma de propiedad

private object _someProperty = null; public object SomeProperty{ get { return _someProperty; } set { _someProperty = value; } }

Es muy fácil e imo elegante poner validación en el colocador. En IL, getters y setters se convierten en métodos de todos modos. ¿Por qué duplicar el código?

En el ejemplo del método Publish () publicado anteriormente, estoy totalmente de acuerdo. Hay momentos en que no queremos que otros desarrolladores establezcan una propiedad. Eso debería ser manejado por un método. Sin embargo, ¿tiene sentido tener un método setter para cada propiedad cuando .Net ya ofrece toda la funcionalidad que necesitamos en la declaración de propiedad?

Si tiene un objeto Persona, ¿por qué crear métodos para cada propiedad en él si no hay motivo?


Estoy contribuyendo con esta respuesta para complementar la respuesta de Roger Alsing sobre invariantes con otras razones.

Información semántica

Roger explicó claramente que los emisores de propiedades no llevan información semántica. Permitir que un colocador de una propiedad como Post.PublishDate pueda agregar confusión, ya que no podemos estar seguros de si la publicación se ha publicado o solo si se ha establecido la fecha de publicación. No podemos estar seguros de que esto sea todo lo que se necesita para publicar un artículo. El objeto "interfaz" no muestra su "intención" claramente.

Creo, sin embargo, que las propiedades como "Habilitado" tienen suficiente semántica para "obtener" y "establecer". Es algo que debería ser efectivo de inmediato y no puedo ver la necesidad de los métodos SetActive () o Activate () / Deactivate () por la sola razón de perder semántica en el establecimiento de propiedades.

Invariantes

Roger también habló sobre la posibilidad de romper invariantes a través de establecedores de propiedades. Esto es absolutamente correcto, y uno debe hacer propiedades que funcionen en tándem para proporcionar un "valor invariante combinado" (como los ángulos del triángulo para usar el ejemplo de Roger) como propiedades de solo lectura y crear un método para establecerlas todas juntas, lo que puede valide todas las combinaciones en un solo paso.

Inicio de orden de propiedad / configuración de dependencias

Esto es similar al problema con las invariantes, ya que causa problemas con las propiedades que deben validarse / cambiarse juntas. Imagine el siguiente código:

public class Period { DateTime start; public DateTime Start { get { return start; } set { if (value > end && end != default(DateTime)) throw new Exception("Start can''t be later than end!"); start = value; } } DateTime end; public DateTime End { get { return end; } set { if (value < start && start != default(DateTime)) throw new Exception("End can''t be earlier than start!"); end = value; } } }

Este es un ejemplo ingenuo de validación "setter" que causa dependencias de orden de acceso. El siguiente código ilustra este problema:

public void CanChangeStartAndEndInAnyOrder() { Period period = new Period(DateTime.Now, DateTime.Now); period.Start = DateTime.Now.AddDays(1); //--> will throw exception here period.End = DateTime.Now.AddDays(2); // the following may throw an exception depending on the order the C# compiler // assigns the properties. period = new Period() { Start = DateTime.Now.AddDays(1), End = DateTime.Now.AddDays(2), }; // The order is not guaranteed by C#, so either way may throw an exception period = new Period() { End = DateTime.Now.AddDays(2), Start = DateTime.Now.AddDays(1), }; }

Dado que no podemos cambiar la fecha de inicio después de la fecha de finalización en un objeto de período (a menos que sea un período "vacío", con ambas fechas configuradas como predeterminadas (DateTime) - sí, no es un gran diseño, pero obtiene lo que significa ...) tratar de establecer la fecha de inicio primero arrojará una excepción.

Se pone más serio cuando usamos los inicializadores de objetos. Como C # no garantiza ninguna orden de asignación, no podemos hacer suposiciones seguras y el código puede lanzar o no una excepción dependiendo de las elecciones del compilador. ¡MALO!

Esto es en última instancia un problema con el DISEÑO de las clases. Como la propiedad no puede "saber" que está actualizando ambos valores, no puede "desactivar" la validación hasta que ambos valores realmente se cambien. Debe hacer ambas propiedades de solo lectura y proporcionar un método para establecer ambas al mismo tiempo (perder la función de los inicializadores de objetos) o eliminar el código de validación de las propiedades (quizás introduciendo otra propiedad de solo lectura como IsValid o validando siempre que sea necesario).

ORM "hidratación" *

La hidratación, en una visión simplista, significa recuperar los datos persistentes en objetos. Para mí, este es realmente el mayor problema al agregar lógica detrás de los establecedores de propiedades.

Muchos / la mayoría de los ORM mapean el valor persistente en una propiedad. Si tiene una lógica de validación o lógica que cambia el estado del objeto (otros miembros) dentro de los establecedores de propiedades, terminará tratando de validar contra un objeto "incompleto" (uno aún se está cargando). Esto es muy similar al problema de inicialización de objetos ya que no puede controlar el orden en que los campos están "hidratados".

La mayoría de los ORM le permiten asignar la persistencia a campos privados en lugar de propiedades, y esto permitirá que los objetos se hidraten, pero si su lógica de validación se encuentra principalmente dentro de los ajustadores de propiedades, puede que tenga que duplicarlos para verificar que un objeto cargado sea válido. o no.

Dado que muchas herramientas ORM soportan la carga diferida (¡un aspecto fundamental de un ORM!) A través del uso de propiedades virtuales (o métodos) la asignación a los campos hará imposible que el ORM cargue los objetos asignados en campos.

Conclusión

Por lo tanto, para evitar la duplicación de código, permitir que los ORM funcionen de la mejor manera posible, evitar excepciones sorprendentes según el orden de los campos, es conveniente alejar la lógica de los establecedores de propiedades.

Aún estoy averiguando dónde debería estar esta lógica de ''validación''. ¿Dónde validamos los aspectos invariantes de un objeto? ¿Dónde ponemos validaciones de alto nivel? ¿Usamos ganchos en los ORM para realizar la validación (OnSave, OnDelete, ...)? Etc. Etc. Etc. Pero este no es el alcance de esta respuesta.