¿Sigue siendo válido hablar sobre el modelo anémico en el contexto de la programación funcional?

40

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.

Pavel Voronin
fuente
1
Si está diciendo lo que creo que está diciendo aquí, los DTO son anémicos, pero son objetos de primera clase en DDD, y que existe una separación natural entre los DTO y los servicios que los procesan. Estoy de acuerdo en principio. También lo hace esta publicación de blog , aparentemente.
Robert Harvey
2
Muy relacionado, si no un duplicado absoluto: ¿Por qué un modelo de dominio anémico se considera malo en POO, pero muy importante en FP?
Robert Harvey
1
"si el término 'modelo anémico' todavía existe en ese caso" Respuesta corta, el término del modelo anémico fue acuñado en el contexto de OO. Hablar de un modelo anémico en el contexto de FP no tiene ningún sentido. Puede haber equivalentes en el sentido de describir lo que es FP idiomática, pero no tiene nada que ver con modelos anémicos.
plalx
55
Eric Evans una vez se le preguntó qué le dice a las personas que lo acusan de que lo que describe en su libro es solo un buen diseño orientado a objetos, y respondió que no es una acusación, es la verdad, DDD es simplemente bueno OOD, simplemente escribió escriba algunas recetas y patrones y les dio nombres, para que sea más fácil seguirlos y hablar sobre ellos. Por lo tanto, no sorprende que DDD esté vinculado a OOD. La pregunta más amplia sería cuáles son las intersecciones y diferencias entre OOD y FPD, aunque primero tendría que definir qué quiere decir con "programación funcional".
Jörg W Mittag
2
@ JörgWMittag: ¿Te refieres a otra definición que no sea la habitual? Hay muchas plataformas ilustrativas, Haskell es la más obvia.
Robert Harvey

Respuestas:

24

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 Accountclase 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 una AccountManagerclase. 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 UserIDse 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 de newtype:

newtype UserID = UserID String

Esto define una UserIDfunción que cuando se le da un StringConstruye un valor que se trató como un UserIDpor el sistema de tipo, pero que todavía es sólo una Stringen tiempo de ejecución. Ahora las funciones pueden declarar que requieren una en UserIDlugar de una cadena; usando UserIDs donde anteriormente estaba usando cadenas de protección contra el código que concatena dos UserIDs 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 Stringgusta arbitrario "hello"y construir un a UserIDpartir 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 un UserIDsi están satisfechos. A continuación, el "tonto" UserIDconstructor se hace de manera privada, si un cliente quiere una UserIDque debe utilizar el constructor inteligente, evitando así identificadores de usuario con formato incorrecto de venir a la existencia.

Incluso otros pasos definen el UserIDtipo de datos de tal manera que es imposible construir uno que esté mal formado o sea "incorrecto", simplemente por definición. Por ejemplo, definiendo a UserIDcomo una lista de dígitos:

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

Para construir una UserIDlista de dígitos se debe proporcionar. Dada esta definición, es trivial mostrar que es imposible UserIDque 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.

Jack
fuente
2
¿Y qué pasa con los agregados y las raíces de agregados que protegen las invariantes? ¿Los últimos también pueden ser expresados ​​y fácilmente comprendidos por los desarrolladores después? Para mí, la parte más valiosa de DDD es un mapeo directo del modelo de negocio al código. Y tu respuesta es exactamente sobre eso.
Pavel Voronin
2
Buen discurso, pero no hay respuesta a la pregunta de OP.
SerG
10

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.

Karl Bielefeldt
fuente
2
"En gran medida, la inmutabilidad hace innecesario acoplar estrechamente sus funciones con sus datos como defiende OOP": otra razón para acoplar datos y procedimientos es implementar el polimorfismo a través del despacho dinámico.
Giorgio
2
La principal ventaja de acoplar el comportamiento con los datos en el contexto de DDD es proporcionar una interfaz significativa relacionada con el negocio; está siempre a mano. Tenemos una forma natural de auto documentar el código (al menos es a lo que estoy acostumbrado), y esa es la clave para una comunicación exitosa con expertos en negocios. ¿Cómo entonces se logra en FP? Probablemente la tubería ayuda, pero ¿qué más? ¿La naturaleza genérica de FP hace que los requisitos comerciales sean más difíciles de aplicar ingeniería inversa a partir del código?
Pavel Voronin
7

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.RaiseSalaryprobablemente muta el salarycampo de la Employeeinstancia, que no debe ser públicamente configurable.

En FP, se evita la mutación, por lo que implementaría este comportamiento creando una RaiseSalaryfunción que tome una Employeeinstancia existente y devuelva una nueva Employee 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, dicha RaiseSalaryfunción no necesita ser definida como un método en la Employeeclase, pero podría vivir en cualquier lugar.

En este caso, se vuelve natural separar los datos del comportamiento: una estructura representa los Employeedatos 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 Employeeposible 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.

la-yumba
fuente
-1

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.

Michael Borgwardt
fuente
55
Sí, eso es básicamente lo que dice el OP en su pregunta. Ambos parecen haber perdido el punto de que todavía pueden tener una composición funcional.
Robert Harvey
-3

Me gustaría saber cómo DDD encaja en el paradigma FP

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).

y si el término 'modelo anémico' todavía existe en ese caso.

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.

Darien
fuente
1
Se produciría un modelo anémico cuando pasa tipos de datos abstractos, como tuplas, registros o listas, a diferentes funciones para su procesamiento. No necesita nada tan exótico como una "función que no se ejecuta" (sea lo que sea).
Robert Harvey
De ahí las comillas alrededor de "funciones", para enfatizar cuán inapropiada se vuelve la etiqueta cuando están anémicas.
Darien
Si estás siendo irónico, es un poco sutil.
Robert Harvey