Modelos de dominio enriquecidos: ¿cómo encaja exactamente el comportamiento?

79

En el debate de los modelos de dominio Rich vs. Anemic, Internet está lleno de consejos filosóficos, pero carece de ejemplos autorizados. El objetivo de esta pregunta es encontrar pautas definitivas y ejemplos concretos de modelos adecuados de diseño impulsado por dominio. (Idealmente en C #.)

Para un ejemplo del mundo real, esta implementación de DDD parece ser incorrecta:

Los siguientes modelos de dominio de WorkItem no son más que bolsas de propiedades, utilizadas por Entity Framework para una base de datos de código primero. Según Fowler, es anemic .

La capa WorkItemService es aparentemente una percepción errónea común de los Servicios de Dominio; contiene toda la lógica de comportamiento / negocio para el artículo de trabajo. Según Yemelyanov y otros, es de procedimiento . (pág. 6)

Entonces, si lo que sigue es incorrecto, ¿cómo puedo corregirlo?
El comportamiento, es decir, AddStatusUpdate o Checkout , debería pertenecer a la clase WorkItem ¿correcto?
¿Qué dependencias debería tener el modelo WorkItem?

publicclassWorkItemService:IWorkItemService{privateIUnitOfWorkFactory_unitOfWorkFactory;//usingUnityfordependencyinjectionpublicWorkItemService(IUnitOfWorkFactoryunitOfWorkFactory){_unitOfWorkFactory=unitOfWorkFactory;}publicvoidAddStatusUpdate(intworkItemId,intstatusId){using(varunitOfWork=_unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()){varworkItemRepo=unitOfWork.WorkItemRepository;varworkItemStatusRepo=unitOfWork.WorkItemStatusRepository;varworkItem=workItemRepo.Read(wi=>wi.Id==workItemId).FirstOrDefault();if(workItem==null)thrownewArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(Este ejemplo se simplificó para que sea más legible. El código definitivamente es torpe, porque es un intento confuso, pero el comportamiento del dominio fue: actualizar el estado agregando el nuevo estado al archivo histórico. En última instancia, estoy de acuerdo con el otro respuestas, esto podría ser manejado por CRUD.)

Actualizar

@AlexeyZimarev dio la mejor respuesta, un video perfecto sobre el tema en C # por Jimmy Bogard, pero aparentemente se trasladó a un comentario a continuación porque no proporcionó suficiente información más allá del enlace. Tengo un borrador de mis notas que resumen el video en mi respuesta a continuación. Por favor, siéntase libre de comentar la respuesta con cualquier corrección. El video dura una hora pero vale la pena verlo.

Actualización - 2 años después

Creo que es un signo de la madurez naciente de DDD que incluso después de estudiarlo durante 2 años, todavía no puedo prometer que conozco la "forma correcta" de hacerlo. El lenguaje ubicuo, las raíces agregadas y su enfoque del diseño basado en el comportamiento son las valiosas contribuciones de DDD a la industria. La ignorancia de la persistencia y la fuente de eventos causa confusión, y creo que una filosofía como esa lo retiene de una adopción más amplia. Pero si tuviera que hacer este código otra vez, con lo que he aprendido, creo que se vería algo como esto:

Todavía recibo con agrado cualquier respuesta a esta publicación (muy activa) que proporcione cualquier código de mejores prácticas para un modelo de dominio válido.

    
pregunta RJB 06.10.2013 - 20:49

4 respuestas

51

La respuesta más útil la dio Alexey Zimarev y obtuvo al menos 7 votos positivos antes de que un moderador la moviera a un comentario debajo de mi pregunta original ...

Su respuesta:

  

Te recomendaría que vieras la sesión de Jimmy Bogard en la NDC 2012 "Crafting Wicked Domain Models" en Vimeo. Explica qué dominio rico debe ser y cómo implementarlos en la vida real al tener un comportamiento en sus entidades. Los ejemplos son muy prácticos y todos en C #.

     

enlace

Tomé algunas notas para resumir el video para beneficio de mi equipo y para proporcionar un poco más de detalle en esta publicación. (El video dura una hora, pero realmente vale la pena cada minuto si tiene tiempo. Jimmy Bogard merece un gran reconocimiento por su explicación).

  • "Para la mayoría de las aplicaciones ... no sabemos que van a ser complejas cuando comenzamos. Simplemente se vuelven así".
    • La complejidad crece naturalmente a medida que se agregan el código y los requisitos. Las aplicaciones pueden comenzar de manera muy simple, como CRUD, pero las conductas / reglas pueden ser integradas.
    • "Lo bueno es que no tenemos que comenzar por complejos. Podemos comenzar con el modelo de dominio anémico, eso es solo bolsas de propiedades, y con técnicas de refactorización estándar podemos avanzar hacia un verdadero modelo de dominio". li>
  • Modelos de dominio = objetos de negocio. Comportamiento del dominio = reglas de negocio.
  • El comportamiento a menudo está oculto en una aplicación, puede estar en PageLoad, Button1_Click o, a menudo, en clases auxiliares como 'FooManager' o 'FooService'.
  • Las reglas de negocios que están separadas de los objetos del dominio "requieren que recordemos" esas reglas.
    • En mi ejemplo personal anterior, una regla de negocio es WorkItem.StatusHistory.Add (). No solo estamos cambiando el estado, lo estamos archivando para su auditoría.
  • Los comportamientos de dominio "eliminan errores en una aplicación mucho más fácilmente que simplemente escribiendo un montón de pruebas". Las pruebas requieren que usted sepa para escribir esas pruebas. Los comportamientos de dominio te ofrecen las rutas correctas para probar .
  • Los servicios de dominio son "clases auxiliares para coordinar actividades entre diferentes entidades modelo de dominio".
    • ¡Servicios de dominio! = comportamiento del dominio. Las entidades tienen un comportamiento, los servicios de dominio son solo intermediarios entre las entidades.
  • Los objetos de dominio no deben poseer la infraestructura que necesitan (es decir, IOfferCalculatorService). El servicio de infraestructura se debe pasar al modelo de dominio que lo utiliza.
  • Los modelos de dominio deberían ofrecerte para decirte lo que pueden hacer, y solo deberían poder hacer esas cosas.
  • Las propiedades de los modelos de dominio deben protegerse con establecedores privados, de modo que solo el modelo pueda establecer sus propias propiedades, a través de sus propios comportamientos . De lo contrario es "promiscuo".
  • Los objetos del modelo de dominio anémico, que son solo bolsas de propiedades para un ORM, son solo "una chapa fina - una versión fuertemente tipada sobre la base de datos".
    • "Por más fácil que sea obtener una fila de base de datos en un objeto, eso es lo que tenemos".
    • 'La mayoría de los modelos de objetos persistentes son solo eso. Lo que diferencia un modelo de dominio anémico frente a una aplicación que realmente no tiene un comportamiento, es si un objeto tiene reglas de negocios, pero esas reglas no se encuentran en un modelo de dominio. '
  • "Para muchas aplicaciones, no hay necesidad real de construir ningún tipo de capa lógica de aplicación empresarial real, es algo que puede comunicarse con la base de datos y tal vez una forma fácil de representar los datos que se encuentran allí".
    • Entonces, en otras palabras, si todo lo que está haciendo es CRUD sin objetos comerciales especiales ni reglas de comportamiento, no necesita DDD.

Por favor, siéntase libre de comentar con cualquier otro punto que considere que debe incluir, o si cree que alguna de estas notas está fuera de lugar. Intenté citar directamente o parafrasear tanto como sea posible.

    
respondido por el RJB 13.12.2013 - 04:22
5

Tu pregunta no puede ser respondida, porque tu ejemplo es incorrecto. Concretamente, porque no hay comportamiento. Al menos no en el área de su dominio. El ejemplo del método AddStatusUpdate no es una lógica de dominio, sino una lógica que usa ese dominio. Ese tipo de lógica tiene sentido estar dentro de algún tipo de servicio, que maneja solicitudes externas.

Por ejemplo, si existía el requisito de que un elemento de trabajo específico solo puede tener estados específicos, o que solo puede tener estados N, entonces esa es la lógica del dominio y debería formar parte de WorkItem o StatusHistory como método.

El motivo de su confusión es porque está tratando de aplicar una guía al código que no lo necesita. Los modelos de dominio solo son relevantes si tiene mucha lógica de dominio compleja. P.ej. Lógica que funciona sobre las entidades mismas y se deriva de los requisitos. Si el código se trata de manipular entidades de datos externos, entonces eso no es, probablemente, una lógica de dominio. Pero en el momento en que obtiene un montón de if s basado en los datos y las entidades con las que está trabajando, entonces esa es la lógica del dominio.

Uno de los problemas del modelado de dominios verdaderos es que se trata de administrar requisitos complejos. Y como tal, su verdadero poder y beneficios no se pueden mostrar en un código simple. Necesita docenas de entidades con toneladas de requisitos a su alrededor para ver realmente los beneficios. Nuevamente, su ejemplo es demasiado simple para que el modelo de dominio brille realmente.

Finalmente, algo de OT que mencionaría es que un verdadero modelo de dominio con un diseño de OOP real sería muy difícil de persistir con el Entity Framework. Si bien los ORM se diseñaron con una estructura de OOP verdadera y mapeada, aún existen muchos problemas, y el modelo relacional a menudo se filtrará en el modelo OOP. Incluso con nHibernate, que considero mucho más poderoso que EF, esto puede ser un problema.

    
respondido por el Euphoric 07.10.2013 - 08:13
4

Su suposición de que encapsular la lógica de su negocio asociada con WorkItem en un "servicio pesado" es un antipatrón inherente que diría que no es necesariamente.

Independientemente de sus pensamientos sobre el modelo de dominio anémico, los patrones y prácticas estándar típicos de una aplicación Line of Business .NET fomentan un enfoque transaccional en capas compuesto por varios componentes. Fomentan la separación de la lógica empresarial del modelo de dominio específicamente para facilitar la comunicación de un modelo de dominio común a través de otros componentes .NET, así como componentes en diferentes pilas de tecnología o en niveles físicos.

Un ejemplo de esto sería un servicio web SOAP basado en .NET que se comunica con una aplicación cliente de Silverlight que tiene una DLL que contiene tipos de datos simples. Este proyecto de entidad de dominio podría estar integrado en un ensamblado .NET o un ensamblaje de Silverlight, donde los componentes de Silverlight interesados que tienen esta DLL no estarán expuestos a comportamientos de objetos que pueden depender de componentes que solo están disponibles para el servicio.

Independientemente de su postura en este debate, este es el patrón adoptado y aceptado presentado por Microsoft y, en mi opinión profesional, no es un enfoque incorrecto, pero un modelo de objeto que define su propio comportamiento no es necesariamente un antipatrón ya sea. Si continúa con este diseño, es mejor darse cuenta y comprender algunas de las limitaciones y los puntos problemáticos con los que podría encontrarse si necesita integrarse con otros componentes que necesitan ver su modelo de dominio. En ese caso particular, quizás desee que un traductor convierta su modelo de dominio de estilo orientado a objetos en objetos de datos simples que no expongan ciertos métodos de comportamiento.

    
respondido por el maple_shaft 07.10.2013 - 02:27
3

Me doy cuenta de que esta pregunta es bastante antigua, por lo que esta respuesta es para la posteridad. Quiero responder con un ejemplo concreto en lugar de uno basado en la teoría.

Encapsule el "cambio del estado del elemento de trabajo" en la clase WorkItem , así:

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

Ahora su clase WorkItem es responsable de mantenerse en un estado legal. Sin embargo, la implementación es bastante débil. El propietario del producto desea un historial de todas las actualizaciones de estado realizadas en WorkItem .

Lo cambiamos a algo como esto:

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

La implementación ha cambiado drásticamente, pero la persona que llama al método ChangeStatus desconoce los detalles de la implementación subyacente y no tiene ninguna razón para cambiarse.

Este es un ejemplo de una entidad de modelo de dominio enriquecido, IMHO.

    
respondido por el Don 07.05.2018 - 18:45

Lea otras preguntas en las etiquetas