La mayoría de los patrones de diseño táctico DDD pertenecen al paradigma orientado a objetos, y el modelo anémico describe la situación en la que toda la lógica empresarial se pone en los servicios en lugar de en los objetos, lo que los convierte en una especie de DTO. En otras palabras, el modelo anémico es sinónimo de estilo de procedimiento, que no se recomienda para el modelo complejo.
No tengo mucha experiencia en programación funcional pura, pero me gustaría saber cómo DDD se ajusta al paradigma FP y si el término 'modelo anémico' todavía existe en ese caso.
Actualización : Recentlry publicó libro y video sobre el tema.
functional-programming
domain-driven-design
Pavel Voronin
fuente
fuente
Respuestas:
La forma en que se describe el problema del "modelo anémico" no se traduce bien en FP como es. Primero necesita ser adecuadamente generalizado. En esencia, un modelo anémico es un modelo que contiene conocimiento sobre cómo usarlo adecuadamente que no está encapsulado por el modelo en sí. En cambio, ese conocimiento se extiende alrededor de una pila de servicios relacionados. Esos servicios solo deben ser clientes del modelo, pero debido a su anemia, son responsables de ello. Por ejemplo, considere una
Account
clase que no se puede usar para activar o desactivar cuentas o incluso buscar información sobre una cuenta a menos que se maneje a través de unaAccountManager
clase. La cuenta debe ser responsable de las operaciones básicas en ella, no de una clase de administrador externo.En la programación funcional, existe un problema similar cuando los tipos de datos no representan con precisión lo que se supone que deben modelar. Supongamos que necesitamos definir un tipo que represente ID de usuario. Una definición "anémica" indicaría que las ID de usuario son cadenas. Eso es técnicamente factible, pero se encuentra con grandes problemas porque las ID de usuario no se usan como cadenas arbitrarias. No tiene sentido concatenarlos o cortar subcadenas de ellos, Unicode realmente no debería importar, y deberían ser fácilmente incrustables en URL y otros contextos con limitaciones estrictas de caracteres y formatos.
La solución de este problema generalmente ocurre en algunas etapas. Un primer corte simple es decir "Bueno, a
UserID
se representa de manera equivalente a una cadena, pero son de diferentes tipos y no se puede usar uno donde se espera el otro". Haskell (y algunos otros lenguajes funcionales escritos) proporciona esta característica a través denewtype
:Esto define una
UserID
función que cuando se le da unString
Construye un valor que se trató como unUserID
por el sistema de tipo, pero que todavía es sólo unaString
en tiempo de ejecución. Ahora las funciones pueden declarar que requieren una enUserID
lugar de una cadena; usandoUserID
s donde anteriormente estaba usando cadenas de protección contra el código que concatena dosUserID
s juntos. El sistema de tipos garantiza que no puede ocurrir, no se requieren pruebas.La debilidad aquí es que el código todavía puede tomar cualquier me
String
gusta arbitrario"hello"
y construir un aUserID
partir de él. Otros pasos incluyen la creación de una función de "constructor inteligente" que cuando se le da una cadena verifica algunos invariantes y solo devuelve unUserID
si están satisfechos. A continuación, el "tonto"UserID
constructor se hace de manera privada, si un cliente quiere unaUserID
que debe utilizar el constructor inteligente, evitando así identificadores de usuario con formato incorrecto de venir a la existencia.Incluso otros pasos definen el
UserID
tipo de datos de tal manera que es imposible construir uno que esté mal formado o sea "incorrecto", simplemente por definición. Por ejemplo, definiendo aUserID
como una lista de dígitos:Para construir una
UserID
lista de dígitos se debe proporcionar. Dada esta definición, es trivial mostrar que es imposibleUserID
que exista un elemento que no pueda representarse en una URL. La definición de modelos de datos como este en Haskell a menudo se ve favorecida por características avanzadas del sistema de tipos, como Tipos de datos y Tipos de datos algebraicos generalizados (GADT) , que permiten que el sistema de tipos defina y demuestre más invariantes sobre su código. Cuando los datos se desacoplan del comportamiento, su definición de datos es el único medio que tiene para imponer el comportamiento.fuente
En gran medida, la inmutabilidad hace que sea innecesario acoplar estrechamente sus funciones con sus datos como defiende OOP. Puede hacer tantas copias como desee, incluso haciendo estructuras de datos derivados, en un código muy alejado del código original, sin temor a que la estructura de datos original cambie inesperadamente debajo de usted.
Sin embargo, una mejor manera de hacer esta comparación es probablemente ver qué funciones está asignando a la capa del modelo frente a la capa de servicios . Aunque no se ve igual que en OOP, es un error bastante común en FP tratar de agrupar lo que deberían ser múltiples niveles de abstracción en una sola función.
Hasta donde yo sé, nadie lo llama un modelo anémico, ya que es un término OOP, pero el efecto es el mismo. Puede y debe reutilizar funciones genéricas cuando corresponda, pero para operaciones más complejas o específicas de la aplicación, también debe proporcionar un amplio conjunto de funciones solo para trabajar con su modelo. Crear capas de abstracción apropiadas es un buen diseño en cualquier paradigma.
fuente
Cuando se usa DDD en OOP, una de las razones principales para poner la lógica de negocios en los objetos de dominio es que la lógica de negocios generalmente se aplica al mutar el estado del objeto. Esto está relacionado con la encapsulación:
Employee.RaiseSalary
probablemente muta elsalary
campo de laEmployee
instancia, que no debe ser públicamente configurable.En FP, se evita la mutación, por lo que implementaría este comportamiento creando una
RaiseSalary
función que tome unaEmployee
instancia existente y devuelva una nuevaEmployee
instancia con el nuevo salario. Por lo tanto, no hay mutación: solo lee del objeto original y crea el nuevo objeto. Por esta razón, dichaRaiseSalary
función no necesita ser definida como un método en laEmployee
clase, pero podría vivir en cualquier lugar.En este caso, se vuelve natural separar los datos del comportamiento: una estructura representa los
Employee
datos como datos (completamente anémicos), mientras que uno (o varios) módulos contienen funciones que operan en esos datos (preservando la inmutabilidad).Tenga en cuenta que cuando combina datos y comportamiento como en DDD, generalmente viola el Principio de responsabilidad única (SRP): es
Employee
posible que deba cambiar si cambian las reglas para los cambios salariales; pero también puede necesitar cambiar si cambian las reglas para calcular el bono EOY. Con el enfoque desacoplado, este no es el caso, ya que puede tener varios módulos, cada uno con una responsabilidad.Entonces, como de costumbre, el enfoque FP proporciona una mayor modularidad / compostibilidad.
fuente
Creo que la esencia del asunto es que un modelo anémico con toda la lógica de dominio en los servicios que operan en el modelo es básicamente una programación procedimental, en oposición a la programación "real" OO donde tienes objetos que son "inteligentes" y contienen no solo datos pero también la lógica que está más estrechamente vinculada a los datos.
Y existe el mismo contraste con la programación funcional: FP "real" significa usar funciones como entidades de primera clase, que se pasan como parámetros, así como se construyen sobre la marcha y se devuelven como valor de retorno. Pero cuando no usa todo ese poder y solo tiene funciones que operan en estructuras de datos que se pasan entre ellas, entonces termina en el mismo lugar: básicamente está haciendo programación de procedimientos.
fuente
Creo que sí, pero en gran medida como un enfoque táctico para la transición entre objetos de valor inmutables o como una forma de activar métodos en entidades. (Donde la mayor parte de la lógica aún vive en la entidad).
Bueno, si te refieres a "de una manera análoga a la OOP tradicional", entonces ayuda ignorar los detalles de implementación habituales y volver a lo básico: ¿Qué idioma usan los expertos de tu dominio? ¿Qué intención estás capturando de tus usuarios?
Supongamos que hablan sobre encadenar procesos y funciones juntas, ¡entonces parece que las funciones (o al menos los objetos "do-er") son básicamente tus objetos de dominio!
Entonces, en ese escenario, un "modelo anémico" probablemente ocurriría cuando sus "funciones" no sean realmente ejecutables, y en su lugar sean solo constelaciones de metadatos que son interpretadas por un Servicio que hace el trabajo real.
fuente