Me gustaría saber cuáles son los pros y los contras de usar un modelo de dominio anémico (ver enlace a continuación).
Me gustaría saber cuáles son los pros y los contras de usar un modelo de dominio anémico (ver enlace a continuación).
Respuestas:
Los profesionales:
Los contras:
Con el "Modelo de dominio anémico" siendo anti-patrón, ¿por qué hay tantos sistemas que implementan esto?
Creo que hay varias razones
1. Complejidad del sistema
En un sistema simple (que son casi todos los ejemplos y código de muestra que encuentra en Internet) si quiero implementar:
Agregar producto al pedido
Pongo esta función en la Orden
public void Order.AddOrderLine(Product product)
{
OrderLines.Add(new OrderLine(product));
}
Agradable y super orientado a objetos.
Ahora digamos que necesito asegurarme de que necesito validar que el producto existe en el inventario y lanzar una excepción si no es así.
Realmente ya no puedo ponerlo en Pedido, ya que no quiero que mi pedido dependa del Inventario, por lo que ahora debe ingresar al servicio.
public void OrderService.AddOrderLine(Order order, Product product)
{
if (!InventoryService.Has(product)
throw new AddProductException
order.AddOrderLine(product);
}
También podría pasar IInventoryService a Order.AddOrderLine, que es otra opción, pero eso aún hace que Order dependa de InventoryService.
Todavía hay alguna funcionalidad en Order.AddOrderLine, pero por lo general se limita al alcance del Pedido, mientras que en mi experiencia hay mucha más Lógica Comercial fuera del alcance del Pedido.
Cuando el sistema es más que CRUD básico, terminará con la mayor parte de su lógica en OrderService y muy poca en Order.
2. Visión del desarrollador de la programación orientada a objetos
Hay muchas discusiones acaloradas en Internet sobre qué lógica debería aplicarse a las entidades.
Algo como
Order.Save
¿Debería el Orden saber salvarse o no? Digamos que tenemos repositorios para eso.
Ahora, ¿puede Order agregar líneas de pedido? Si trato de darle sentido usando un inglés simple, tampoco tiene sentido. El usuario agrega un producto al pedido, entonces, ¿deberíamos hacerlo User.AddOrderLineToOrder ()? Eso parece una exageración.
¿Qué hay de OrderService.AddOrderLine (). ¡Ahora tiene algo de sentido!
Mi comprensión de OOP es que para la encapsulación se colocan funciones en clases donde la función necesitará acceder al estado interno de la clase. Si necesito acceder a la colección Order.OrderLines, pongo Order.AddOrderLine () en Order. De esta forma, el estado interno de la clase no queda expuesto.
3. Contenedores de IoC
Los sistemas que utilizan contenedores de IoC suelen ser completamente anémicos.
Se debe a que puede probar sus servicios / repositorios que tienen interfaces, pero no puede probar objetos de dominio (fácilmente), a menos que ponga interfaces en todos ellos.
Dado que "IoC" se alaba actualmente como una solución para todos sus problemas de programación, mucha gente lo sigue ciegamente y de esta manera terminan con modelos de dominio anémicos.
4. OOP es difícil, el procedimiento es fácil
Tengo un poco de " Maldición del conocimiento " en este caso, pero he descubierto que para los desarrolladores más nuevos tener DTO y servicios es mucho más fácil que Rich Domain.
Posiblemente sea porque con Rich Domain es más difícil saber en qué clases poner la lógica. ¿Cuándo crear nuevas clases? ¿Qué patrones usar? etc ..
Con los servicios apátridas, simplemente lo abofetea en el servicio con el nombre más cercano.
Después de esto, ha habido un pensamiento en mi cabeza durante mucho tiempo. Creo que el término "OOP" ha adquirido un significado que no está realmente destinado a él. El anagrama significa "programación orientada a objetos", como todos sabemos. El enfoque, por supuesto, está en la palabra "orientado". No es "OMP", que significa "programación ordenada por objetos". Tanto ADM como RDM son ejemplos de POO. Hacen uso de interfaces de objetos, propiedades, métodos, etc. Sin embargo, hay una diferencia entre ADM y RDM en cómo elegimos encapular las cosas. Son dos cosas diferentes. Decir que ADM es malo OOP no es una afirmación precisa. Quizás necesitemos términos diferentes para distintos niveles de encapsulación. Además, nunca me gustó el término anti-patrón. Por lo general, los miembros de un grupo opuesto lo asignan a algo. Tanto ADM como RDM son patrones válidos, simplemente tienen diferentes objetivos en mente y están destinados a resolver diferentes necesidades comerciales. Aquellos de nosotros que practicamos DDD deberíamos, como mínimo, apreciar esto, y no caer al nivel de otros atacando a aquellos que eligen implementar ADM. Solo mis pensamientos.
"Es un anti-patrón, por lo que otros desarrolladores le preguntarán si comprende los conceptos del diseño orientado a objetos".
"Un modelo de dominio anémico es un anti-patrón. Los anti-patrones no tienen ventajas".
Si el modelo de dominio anémico es un antipatrón es una cuestión de opinión. Martin Fowler dice que lo es, varios desarrolladores que conocen OO de adentro hacia afuera dicen que no lo es. Declarar la opinión como un hecho rara vez es útil.
Y, incluso si se aceptara universalmente como un anti-patrón, lo más probable es que todavía tenga alguna ventaja (aunque relativamente pequeña).
the chances are it would still have some (though relatively little) upside.
¡Entonces por favor nombre algunos! ¡Al menos uno, en lugar de hacer hipótesis!
Me parece que la principal objeción de Fowler es que los ADM no son OO, en el siguiente sentido. Si uno diseña un sistema "desde cero" alrededor de estructuras de datos pasivas que son manipuladas por otras piezas de código, entonces esto ciertamente huele más a diseño procedimental que a diseño orientado a objetos.
Sugiero que hay al menos dos fuerzas que pueden producir este tipo de diseño:
Diseñadores / programadores que todavía piensan que deben trabajar en un entorno orientado a objetos (o suponiendo que pueden ...) producir un nuevo sistema, y
Desarrolladores que trabajan para poner una "cara" similar a un servicio en un sistema heredado diseñado de manera no orientada a objetos (independientemente del idioma)
Si, por ejemplo, se estuviera construyendo un conjunto de servicios para exponer la funcionalidad de una aplicación de mainframe COBOL existente, se podrían definir servicios e interfaces en términos de un modelo conceptual que no refleja las estructuras de datos internas de COBOL. Sin embargo, si el servicio asigna el nuevo modelo a los datos heredados para usar la implementación existente pero oculta, entonces el nuevo modelo podría ser "anémico" en el sentido del artículo de Fowler, por ejemplo, un conjunto de definiciones de estilo TransferObject. y relaciones sin comportamiento real.
Este tipo de compromiso puede muy bien ser común para los límites en los que los sistemas OO idealistamente puros deben interactuar con un entorno no OO existente.
El modelo de dominio anémico (ADM) puede ser una buena opción si su equipo no puede o no quiere construir un modelo de dominio rico (RDM) y mantenerlo a lo largo del tiempo. Ganar con un RDM requiere una cuidadosa atención a las abstracciones dominantes utilizadas en el sistema. Imagínese que, en cualquier grupo de desarrollo, no más de la mitad y quizás solo una décima parte de sus miembros son competentes con las abstracciones. A menos que este cuadro (quizás solo un desarrollador) sea capaz de mantener la influencia sobre las actividades de todo el grupo, el RDM sucumbirá a la entropía.
Y el RDM entrópico duele, de formas particulares. Sus desarrolladores aprenderán duras lecciones. Al principio, podrán cumplir con las expectativas de sus grupos de interés, porque no tendrán un historial que cumplir. Pero a medida que su sistema se vuelve más complicado (no complejo) , se volverá frágil; los desarrolladores intentarán reutilizar el código, pero tienden a inducir nuevos errores o retroceder en el desarrollo (y así sobrepasar sus estimaciones).
Por el contrario, los desarrolladores de ADM establecerán expectativas más bajas para sí mismos, porque no esperarán reutilizar tanto código para nuevas funciones. Con el tiempo, tendrán un sistema con muchas inconsistencias, pero probablemente no se romperá inesperadamente. Su tiempo de comercialización será más largo que con un RDM exitoso, pero es poco probable que sus partes interesadas perciban esta posibilidad.
"Desarrolladores que trabajan para poner una" cara "similar a un servicio en un sistema heredado diseñado de manera no orientada a objetos (independientemente del idioma)".
Si piensa en muchas aplicaciones LOB, estos sistemas heredados a menudo no usarán el mismo modelo de dominio que usted. El modelo de dominio anémico resuelve esto con el uso de lógica empresarial en clases de servicio. Podría poner todo este código de interfaz dentro de su modelo (en el sentido tradicional de OO), pero normalmente termina perdiendo la modularidad.
Cuando me encontré por primera vez con el artículo del Modelo de dominio anémico pensé "mierda sagrada, eso es lo que hago. ¡Horror!" Perseveré y seguí las referencias al libro de Eric Evan, considerado un buen ejemplo, y descargué la fuente. Resulta que "no usar un modelo de dominio anémico" no significa "no usar clases de servicio, no usar mediadores, no usar estrategias" o incluso "poner lógica en la clase que está siendo manipulada".
Los ejemplos de DDD tienen clases de servicio, XyzUpdaters, singletons e IoC.
Sigo confundido por lo que es exactamente un modelo de dominio anémico. Espero "Lo sabré cuando lo vea". Por ahora me contento con un ejemplo positivo de buen diseño.
Habiendo trabajado con un sistema 'maduro' con un ADM y siento que puedo proporcionar, al menos, comentarios anecdóticos a esta pregunta.
1) Falta de encapsulación
En un sistema en vivo con un ADM, existe la posibilidad de escribir, por ejemplo, 'obj.x = 100; obj.save ', incluso si infringe la lógica empresarial. Esto conduce a una serie de errores que no se encontrarían si las invariantes se modelaran en el objeto. Es la gravedad y la omnipresencia de estos errores lo que creo que es el aspecto negativo más grave para un ADM.
Creo que es importante señalar aquí que aquí es donde una solución funcional y las soluciones de procedimiento de un ADM difieren significativamente y cualquier similitud que otros puedan haber extraído de las similitudes superficiales entre un ADM y una solución funcional son incidentales.
2) Código hinchado
Calculo que la cantidad de código producido en un ADM es 5-10 veces mayor que la que crearía una solución OOP / RDM. Esto se explica porque quizás el 50% sea la repetición de código, el 30% sea el código de la placa de la caldera y el 20% sea la solución o resolución de problemas que surgen de la falta de un RDM.
3) Escasa comprensión de los problemas del dominio
Un ADM y una comprensión deficiente de los problemas del dominio van de la mano. Surgen soluciones ingenuas, los requisitos están mal considerados debido a la dificultad de soportarlos con el DM existente, y el ADM se convierte en una barrera importante para la innovación empresarial dados los tiempos de desarrollo más largos y la falta de flexibilidad.
4) Dificultad de mantenimiento
Se requiere cierto nivel de rigor para asegurar que un concepto de dominio se cambie en todos los lugares en los que se expresa, dado que el concepto puede no ser solo una reimplementación de copiar y pegar. Esto a menudo lleva a que se investiguen y corrijan los mismos errores en múltiples ocasiones.
5) Mayor dificultad con la incorporación
Creo que uno de los beneficios de un RDM es la cohesión de conceptos que permiten una comprensión más rápida del dominio. Con un ADM, los conceptos pueden estar fragmentados y carecer de claridad, por lo que es más difícil de adquirir para los nuevos desarrolladores.
También estuve tentado de incluir que los costos de soporte operativo para un ADM eran más altos que para un RDM, pero esto dependería de varios factores.
Como otros han señalado, mire a DDD (Greg Evans, Vince Vaughn y Scott Millett) para conocer los beneficios de un RDM.
Es el mismo profesional que con la mayoría de los anti-patrones: te permite mantener a muchas personas ocupadas durante mucho tiempo. Como los gerentes tienden a cobrar más cuando manejan a más personas, existe un fuerte incentivo para no mejorar.
De acuerdo con la respuesta de Eric P, así como con lo que algunos otros escribieron anteriormente, parece que la principal desventaja de un ADM es la pérdida de OOD, específicamente de mantener la lógica y los datos de un concepto de dominio juntos para que los detalles de implementación estén ocultos mientras la API puede ser rica.
Eric continúa señalando que a menudo hay información fuera de una clase de dominio que es necesaria para la lógica de actuar en esa clase, como verificar un inventario antes de agregar un artículo a un pedido. Sin embargo, me pregunto si la respuesta es una capa de servicio que mantiene esta lógica general, o si se maneja mejor como parte del diseño del objeto. Alguien tiene que conocer el objeto Inventario, el objeto Producto y el objeto Pedido. Quizás sea simplemente un objeto OrderSystem, que tiene un miembro de Inventario, una lista de Pedidos, etc. Esto no se verá muy diferente de un Servicio, pero creo que es conceptualmente más coherente.
O mírelo de esta manera: puede tener un usuario con un saldo de crédito interno, y cada vez que se llama a ese User.addItemToOrder (artículo), obtiene el precio del artículo y verifica el crédito antes de agregarlo, etc. Eso parece un OO razonable diseño. No estoy seguro de qué se pierde al reemplazarlo con Service.addItemToUserOrder (usuario, elemento), pero tampoco estoy seguro de qué se ha ganado. Supongo que una pérdida sería la capa adicional de código, más el estilo de escritura más tosco y la ignorancia forzada del modelo de dominio subyacente.
Cabe señalar que a medida que los sistemas aumentan en complejidad y granularidad de variación, la encapsulación y consolidación de los puntos de interfaz que ofrece un modelo de objetos de paso de mensajes bien diseñado hace que sea mucho más seguro cambiar y mantener el código crítico sin una refactorización generalizada.
Las capas de servicio creadas por el ADM, aunque ciertamente son más fáciles de implementar (ya que requieren relativamente poca reflexión y tienen muchos puntos de interfaz descentralizados) probablemente crearán problemas en el futuro cuando sea el momento de modificar un sistema en vivo y en crecimiento.
Debo agregar también que no todos los casos requieren un modelo de dominio (y mucho menos el ADM). A veces es mejor utilizar un estilo más procedimental / funcional de la tarea que se basa en datos y no depende de las reglas de negocio / lógica de toda la aplicación.
Si está tratando de decidir los pros y los contras de una aplicación completa, creo que es importante diseñar primero cómo se vería cada una para su aplicación determinada ANTES de comenzar a escribir una sola línea de código. Una vez que haya CRC o estructurado su aplicación en ambos estilos, dé un paso atrás y decida cuál tiene más sentido y se adapta mejor a la aplicación.
También piense en cuál será más fácil de mantener ...
Da una mejor previsibilidad. A los gerentes les gusta eso, especialmente si el proyecto es tiempo y materiales pagados. Cada cambio significa mucho trabajo, por lo que el trabajo difícil puede esconderse detrás de mucho trabajo repetitivo. En un sistema DRY bien diseñado, la previsibilidad es muy mala, ya que constantemente está haciendo cosas nuevas.
Después de leer por primera vez el libro de Eric Evans sobre el diseño impulsado por dominios, realmente no entendí que no se trata solo de un montón de patrones tácticos para diseñar buenas clases de modelos de dominio.
Después de aprender más sobre el tema y usar los patrones estratégicos también, finalmente comencé a comprender que al principio se trata de obtener una comprensión profunda de los problemas comerciales que está tratando de resolver.
Y solo después de eso, puede decidir qué partes del sistema encajarán para aplicar patrones tácticos como agregados, entidades , repositorios, etc. junto con los llamados modelos de dominio rico (a diferencia de los anémicos ). Pero para beneficiarse de estos patrones, tiene que haber suficiente complejidad con respecto a la lógica empresarial para esa parte del sistema.
Entonces, cuando se trata de implementar la solución al problema en cuestión, primero se debe determinar si este problema específico se aborda mejor con un enfoque basado en CRUD o invirtiendo en un modelo de dominio rico junto con los patrones tácticos mencionados.
Si CRUD tiene más sentido, por ejemplo, si no hay una lógica de negocios compleja y la mayor parte de la lógica está relacionada con la transformación, transferencia y persistencia de datos, la implementación de un modelo de dominio puede ser una exageración innecesaria. Esto no significa que no habrá mucho trabajo por hacer, sino simplemente que no son las reglas de negocio las que producen el mayor esfuerzo de implementación. Pero en este caso no existe un modelo de dominio anémico , simplemente porque no existe ningún modelo de dominio . Lo que preferirá ver son cosas como DTO (objetos de transferencia de datos) o DAO(Objetos de acceso a datos) y clases de servicio que operarán en los datos. Y las operaciones correspondientes se relacionan en gran medida con la transformación de datos de una representación a otra y con la transferencia de datos con muy poca o casi ninguna lógica empresarial.
Si determinó que hay una gran cantidad de lógica empresarial compleja que también cambiará con el tiempo, invertir en un modelo de dominio es, según mi experiencia, una buena idea. La razón es que es más fácil representar la perspectiva empresarial mediante código y facilitar la comprensión de las operaciones correspondientes que reflejan el dominio empresarial y sus reglas. Esto no significa que tenga que haber clases de modelo de dominio en cada caso de uso. Por ejemplo, si no hay un estado para mutar y persistir, también puede haber solo servicios de dominio que contengan la lógica del dominio y se implementen más como funciones puras.
Pero si también hay un estado para mutar y persistir que también tiene un propósito y significado en el dominio empresarial, el estado y el comportamiento que cambia este estado deben encapsularse . Con eso, nadie puede eludir las reglas comerciales tan fácilmente y, por lo tanto, conducir a estados inválidos junto con fallas graves. Los llamados modelos de dominio anémicos a menudo son fuentes de tales problemas . Este suele ser el caso si ve un código donde diferentes componentes operan en la misma clase de modelo de dominio "anémico" verificando alguna parte de su estado y cambiando alguna parte de su estado sin preocuparse o conocer las invariantes generales de esa entidad comercial. No es necesario llamar a esto un anti-patrón, pero es importante entender que, pierde muchos beneficios de los modelos de dominio enriquecidosen un enfoque basado en DDD junto con los problemas mencionados. Cuando se usa un modelo de dominio donde el comportamiento y sus datos se colocan en la misma clase, también puede haber muchos "clientes" diferentes que llaman a operaciones de esa clase, pero no necesitan preocuparse de que los invariantes comerciales de la entidad comercial se adhieran como la clase de modelo de dominio siempre se encargará de eso y también puede informar al "cliente" sobre operaciones no válidas o incluso lanzar excepciones como red de seguridad.
En resumen , creo que es importante no confundir las clases de estructura de datos (como DTO o DAO) con clases de modelo de dominio anémico . En un enfoque basado en CRUD elegido de manera cuidadosa e intencional, no hay ninguna ventaja en tratar de utilizar un modelo de dominio porque hay una lógica empresarial demasiado menos compleja.
Por modelo de dominio anémico, me referiría al código a partir del cual puedo ver que hay una gran cantidad de lógica y reglas comerciales complejas que se distribuyen en diferentes componentes que deberían estar más cerca de los datos que esta lógica está cambiando.
También hay otra lección que aprendí a lo largo del camino: si intentas usar el mismo lenguaje comercial (también conocido como el lenguaje ubicuo ) en tu código que los dueños de bistecs están usando en su vida laboral diaria, ya obtendrás muchas ventajas en cuanto a la comprensión de el dominio empresarial y mejorar la legibilidad de su código que le ayudará mucho sin importar si utiliza un enfoque basado en CRUD o modelo de dominio.
Para extender la respuesta de Michael, habría pensado que está (bastante) claro dónde debería ir ese código: en un Mediador dedicado que maneja la interacción entre la Orden y el Inventario.
Desde mi punto de vista, la clave del dominio es que DEBE contener el comportamiento de prueba simple, los isInThisState()
métodos, etc. En mi experiencia, estos también están esparcidos por las lágrimas del servicio (sic :)) en la mayoría de las empresas, y se copian o se reescriben sin cesar. Todo lo cual rompe las reglas estándar de adhesión.
En mi opinión, el enfoque debería ser apuntar a un DM que mantenga tanto del comportamiento empresarial como sea práctico, colocar el resto en áreas claramente designadas (es decir, no en los servicios)
Mi equipo prefiere personalmente el ADM. tenemos un conjunto de objetos comerciales que representan partes específicas de nuestro dominio. Usamos servicios para guardar estos objetos en la base de datos. Nuestros objetos comerciales tienen métodos, sin embargo, estos métodos solo manipulan su estado interno.
El beneficio para nosotros de usar ADM sobre RDM se puede ver en cómo mantenemos los objetos en db. Los desarrolladores que trabajan en nuestros sistemas de código heredados pueden usar nuestros objetos comerciales (del nuevo sistema) y continuar usando su capa de acceso a datos actual para conservar estos objetos en la base de datos. El uso de RDM obligaría a los desarrolladores de nuestro sistema heredado a inyectar objetos de Repositorio en nuestro modelo comercial ... lo que no sería consistente con su capa de acceso a datos actual.
Un modelo de dominio anémico es un antipatrón. Los antipatrones no tienen ventajas.