En el debate de los modelos de dominio Rich vs.Anemic, Internet está llena de consejos filosóficos pero con pocos ejemplos autorizados. El objetivo de esta pregunta es encontrar pautas definitivas y ejemplos concretos de modelos de diseño impulsados por dominio adecuados. (Idealmente en C #.)
Para un ejemplo del mundo real, esta implementación de DDD parece estar mal:
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. Por Fowler, es anémico .
La capa WorkItemService es aparentemente una percepción errónea común de los Servicios de dominio; contiene todo el comportamiento / lógica de negocios para el WorkItem. Según Yemelyanov y otros, es de procedimiento . (pág. 6)
Entonces, si lo siguiente está mal, ¿cómo puedo corregirlo?
El comportamiento, es decir, AddStatusUpdate o Checkout , debe pertenecer a la clase WorkItem ¿correcto?
¿Qué dependencias debería tener el modelo WorkItem?
public class WorkItemService : IWorkItemService {
private IUnitOfWorkFactory _unitOfWorkFactory;
//using Unity for dependency injection
public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
_unitOfWorkFactory = unitOfWorkFactory;
}
public void AddStatusUpdate(int workItemId, int statusId) {
using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
var workItemRepo = unitOfWork.WorkItemRepository;
var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;
var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
if (workItem == null)
throw new ArgumentException(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 sigue siendo complicado, porque es un intento confuso, pero el comportamiento del dominio fue: actualizar el estado agregando el nuevo estado al historial del archivo. En última instancia, estoy de acuerdo con las otras 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 fue trasladado 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. No dude en 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 una señal de la naciente madurez de DDD que incluso después de estudiarlo durante 2 años, todavía no puedo prometer que sé 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 el abastecimiento de eventos causa confusión, y creo que una filosofía como esa lo detiene de una adopción más amplia. Pero si tuviera que volver a hacer este código, con lo que he aprendido, creo que se vería así:
Todavía agradezco cualquier respuesta a esta publicación (muy activa) que proporcione cualquier código de mejores prácticas para un modelo de dominio válido.
"I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll"
. "Entidades" en la jerga de Entity Framework no son lo mismo que "Entidades" como en "Modelo de dominio"Respuestas:
La respuesta más útil fue dada por Alexey Zimarev y obtuvo al menos 7 votos a favor antes de que un moderador la moviera a un comentario debajo de mi pregunta original ...
Su respuesta:
Tomé algunas notas para resumir el video para beneficio de mi equipo y para proporcionar un poco más de detalle inmediato en esta publicación. (El video dura una hora, pero realmente vale cada minuto si tiene tiempo. Jimmy Bogard merece mucho crédito por su explicación).
Por favor, siéntase libre de comentar con cualquier otro punto que considere que debería incluirse, o si cree que alguna de estas notas está fuera de lugar. Intenté citar directamente o parafrasear tanto como sea posible.
fuente
Su pregunta no puede ser respondida, porque su ejemplo es incorrecto. Específicamente, porque no hay comportamiento. Al menos no en el área de su dominio. El ejemplo del
AddStatusUpdate
método 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 N estados, entonces esa es la lógica del dominio y debe ser parte de uno
WorkItem
o deStatusHistory
un método.La razón de su confusión es porque está tratando de aplicar una directriz al código que no la necesita. Los modelos de dominio solo son relevantes si tiene mucha lógica de dominio compleja. P.ej. lógica que funciona en las propias entidades y se deriva de los requisitos. Si el código trata de manipular entidades de datos externos, entonces eso no es, muy probablemente, una lógica de dominio. Pero en el momento en que obtienes muchos correos
if
electrónicos basados en los datos y entidades con los que estás trabajando, eso es lógica de dominio.Uno de los problemas del modelado de dominio verdadero 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 realmente brille.
Finalmente, algo de OT que mencionaría es que un verdadero modelo de dominio con diseño real de OOP sería realmente difícil de seguir utilizando Entity Framework. Si bien los ORM se diseñaron con el mapeo de la verdadera estructura OOP a las relacionales, todavía hay 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.
fuente
Su suposición de que encapsular su lógica de negocios asociada con WorkItem en un "servicio pesado" es un antipatrón inherente que diría no necesariamente.
Independientemente de lo que piense sobre el modelo de dominio anémico, los patrones y prácticas estándar típicos de una aplicación .NET de Line of Business fomentan un enfoque de capas transaccionales 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 conjuntos de tecnología o niveles físicos.
Un ejemplo de esto sería un servicio web SOAP basado en .NET que se comunica con una aplicación cliente Silverlight que tiene una DLL que contiene tipos de datos simples. Este proyecto de entidad de dominio podría integrarse en un ensamblado .NET o un ensamblaje Silverlight, donde los componentes Silverlight interesados que tengan esta DLL no estarán expuestos a comportamientos de objetos que pueden depender de componentes solo 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 tampoco es necesariamente un antipatrón. Si continúa con este diseño, lo mejor es darse cuenta y comprender algunas de las limitaciones y puntos débiles con los que se puede encontrar si necesita integrarse con otros componentes que necesitan ver su modelo de dominio. En ese caso particular, tal vez 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.
fuente
Me doy cuenta de que esta pregunta es bastante antigua, así 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
WorkItem
clase de la siguiente manera:Ahora su
WorkItem
clase es responsable de mantenerse en un estado legal. Sin embargo, la implementación es bastante débil. El propietario del producto quiere un historial de todas las actualizaciones de estado realizadas enWorkItem
.Lo cambiamos a algo como esto:
La implementación ha cambiado drásticamente, pero la persona que llama del
ChangeStatus
método desconoce los detalles subyacentes de la implementación y no tiene ninguna razón para cambiarse.Este es un ejemplo de una entidad modelo de dominio rico, en mi humilde opinión.
fuente