Est-il toujours valable de parler de modèle anémique dans le contexte de la programmation fonctionnelle?

40

La plupart des modèles de conception tactiques DDD appartiennent à un paradigme orienté objet, et un modèle anémique décrit la situation dans laquelle toute la logique métier est mise en services plutôt qu'en objets, ce qui en fait une sorte de DTO. En d'autres termes, modèle anémique est synonyme de style procédural, ce qui n'est pas conseillé pour un modèle complexe.

Je n'ai pas beaucoup d'expérience en programmation fonctionnelle pure, mais j'aimerais savoir comment DDD entre dans le paradigme de la PF et si le terme «modèle anémique» existe toujours dans ce cas.

Mise à jour : livre récemment publié et vidéo sur le sujet.

Pavel Voronin
la source
1
Si vous dites ce que je pense que vous dites ici, les DTO sont anémiques, mais sont des objets de première classe dans DDD et qu'il existe une séparation naturelle entre les DTO et les services qui les traitent. Je suis d'accord en principe. Il en va de même pour ce blog .
Robert Harvey
1
"si le terme 'modèle anémique' existe toujours dans ce cas" Réponse courte, le terme de modèle anémique a été inventé dans le contexte de OO. Parler d'un modèle anémique dans le contexte de la PF n'a aucun sens. Il peut y avoir des équivalents dans le sens de décrire ce qu'est la PF idiomatique, mais cela n'a rien à voir avec les modèles anémiques.
Plalx
5
On a demandé à Eric Evans ce qu’il disait aux gens qui l’accusaient que ce qu’il décrit dans son livre était simplement un bon design orienté objet, et il a répondu que ce n’était pas une accusation, mais la vérité, que DDD était simplement bon, dit-il. consignez des recettes et des modèles et donnez-leur des noms, ce qui facilite leur suivi et leur discussion. Il n’est donc pas surprenant que DDD soit liée à OOD. La question plus générale serait de savoir quelles sont les intersections et les différences entre OOD et FPD, bien que vous deviez d'abord définir ce que vous entendez par "programmation fonctionnelle".
Jörg W Mittag
2
@ JörgWMittag: Vous voulez dire autre chose que la définition habituelle? Il existe de nombreuses plateformes illustratives, Haskell étant la plus évidente.
Robert Harvey

Réponses:

24

La façon dont le problème de "modèle anémique" est décrit ne se traduit pas bien en PF en l'état. Tout d'abord, il doit être généralisé de manière appropriée. En son cœur, un modèle anémique est un modèle qui contient des connaissances sur la manière de l’utiliser correctement qui n’est pas encapsulé par le modèle lui-même. Au lieu de cela, ces connaissances sont réparties autour d’une pile de services connexes. Ces services ne devraient être que des clients du modèle, mais en raison de son anémie, ils en sont tenus responsables . Par exemple, considérons une Accountclasse qui ne peut pas être utilisée pour activer ou désactiver des comptes ou même rechercher des informations sur un compte si elle n'est pas gérée via une AccountManagerclasse. Le compte doit être responsable des opérations de base sur celui-ci, et non d'une classe de gestionnaire externe.

En programmation fonctionnelle, un problème similaire existe lorsque les types de données ne représentent pas exactement ce qu'ils sont censés modéliser. Supposons que nous devions définir un type représentant les ID utilisateur. Une définition "anémique" indiquerait que les ID utilisateur sont des chaînes. C'est techniquement faisable, mais pose d'énormes problèmes car les ID utilisateur ne sont pas utilisés comme des chaînes arbitraires. Cela n'a aucun sens de les concaténer ou d'en découper des sous-chaînes. Unicode ne devrait pas avoir d'importance, et ils devraient pouvoir être incorporés facilement dans des URL et d'autres contextes avec des limitations strictes de caractères et de format.

La résolution de ce problème se produit généralement en quelques étapes. Une première coupe simple consiste à dire "Eh bien, a UserIDest représenté de manière équivalente à une chaîne, mais ils sont de types différents et vous ne pouvez pas utiliser l'un de ceux que vous attendez de l'autre." Haskell (et quelques autres langages fonctionnels dactylographiés) fournit cette fonctionnalité via newtype:

newtype UserID = UserID String

Ceci définit une UserIDfonction qui, lorsqu'elle reçoit une Stringvaleur, construit une valeur qui est traitée comme un UserIDsystème de types, mais qui reste juste Stringà l'exécution. Désormais, les fonctions peuvent déclarer qu’elles nécessitent un à la UserIDplace d’une chaîne; en utilisant UserIDs où vous utilisiez auparavant des chaînes, gardes contre le code concaténant deux UserIDs ensemble. Le système de types garantit que cela ne peut pas arriver, aucun test requis.

La faiblesse est que le code peut encore prendre toute arbitraire Stringcomme "hello"et construire un UserIDde lui. La prochaine étape consiste à créer une fonction "constructeur intelligent" qui, lorsqu'elle reçoit une chaîne, vérifie certains invariants et ne les renvoie que UserIDs'ils sont satisfaits. Ensuite, le UserIDconstructeur "dumb" est rendu privé. Par conséquent, si un client le souhaite, UserIDil doit utiliser le constructeur intelligent, empêchant ainsi la création d’identificateurs d’utilisateur mal formés.

Même les étapes suivantes définissent le UserIDtype de données de telle sorte qu'il est impossible d'en construire un qui est mal formé ou "impropre", par simple définition. Par exemple, définir UserIDune liste de chiffres:

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

Pour construire une UserIDliste de chiffres doit être fournie. Compte tenu de cette définition, il est trivial de montrer qu'il est impossible UserIDd'exister sans pouvoir être représenté dans une URL. Définir de tels modèles de données dans Haskell est souvent facilité par des fonctionnalités de système de types avancées telles que les types de données et les types de données algébriques généralisées (GADT) , qui permettent au système de types de définir et de prouver davantage d'invariants dans votre code. Lorsque les données sont découplées du comportement, votre définition de données est le seul moyen dont vous disposez pour appliquer le comportement.

Jack
la source
2
Et que dire des agrégats et des agrégats gardant des invariants, les derniers peuvent-ils aussi être exprimés et facilement compris par les développeurs par la suite? Pour moi, la partie la plus précieuse de DDD est la mise en correspondance directe du modèle d’entreprise avec le code. Et vous répondez est exactement à ce sujet.
Pavel Voronin
2
Beau discours, mais pas de réponse à la question OP.
SerG
10

Dans une large mesure, grâce à l’immuabilité, il n’est pas nécessaire de coupler étroitement vos fonctions avec vos données, comme le préconise la programmation orientée objet. Vous pouvez effectuer autant de copies que vous le souhaitez, même en créant des structures de données dérivées, dans un code très éloigné du code d'origine, sans craindre que la structure de données d'origine ne se modifie de manière inattendue.

Cependant, une meilleure façon de faire cette comparaison est probablement de regarder quelles fonctions vous attribuez à la couche modèle par rapport à la couche services . Même si cela ne ressemble pas à la POO, c’est une erreur assez courante en PF d’essayer de regrouper ce qui devrait être plusieurs niveaux d’abstraction en une seule fonction.

Autant que je sache, personne n’appelle cela un modèle anémique, car c’est un terme OOP, mais l’effet est le même. Vous pouvez et devez réutiliser des fonctions génériques, le cas échéant, mais pour les opérations plus complexes ou spécifiques à une application, vous devez également fournir un ensemble complet de fonctions permettant de travailler avec votre modèle. La création de couches d'abstraction appropriées est une bonne conception, quel que soit le paradigme.

Karl Bielefeldt
la source
2
"Dans une large mesure, l’immuabilité rend inutile le couplage étroit de vos fonctions avec vos données, comme le préconise la programmation orientée objet.": Une autre raison du couplage des données et des procédures est la mise en oeuvre du polymorphisme par le biais de la répartition dynamique.
Giorgio
2
Le principal avantage du comportement de couplage avec les données dans le contexte de DDD est de fournir une interface significative pour les entreprises. c'est juste toujours à portée de main. Nous avons un moyen naturel de code auto-documenté (du moins c'est ce que je suis habitué), et c'est la clé d'une communication réussie avec des experts métier. Comment se fait-il alors en PF? Probablement que la tuyauterie aide, mais quoi d'autre? La nature générique de la PF ne complique-t-elle pas les exigences commerciales en matière d'ingénierie inverse à partir du code?
Pavel Voronin
7

Lors de l'utilisation de DDD dans OOP, l'une des principales raisons de placer la logique métier dans les objets de domaine eux-mêmes est que la logique métier est généralement appliquée en modifiant l'état de l'objet. Cela est lié à l’encapsulation: Employee.RaiseSalaryle salarychamp de l’ Employeeinstance subit probablement une mutation , ce qui ne devrait pas pouvoir être défini publiquement.

En mode FP, la mutation est évitée. Vous devez donc implémenter ce comportement en créant une RaiseSalaryfonction prenant une Employeeinstance existante et renvoyant une nouvelle Employee instance avec le nouveau salaire. Donc, aucune mutation n'est impliquée: seulement lire à partir de l'objet d'origine et créer le nouvel objet. Pour cette raison, une telle RaiseSalaryfonction n'a pas besoin d'être définie comme une méthode sur la Employeeclasse, mais peut vivre n'importe où.

Dans ce cas, il devient naturel de séparer les données du comportement: une structure représente les Employeedonnées as (complètement anémique), tandis qu'un ou plusieurs modules contiennent des fonctions qui agissent sur ces données (en préservant leur immuabilité).

Notez que lorsque vous associez données et comportement comme dans DDD, vous enfreignez généralement le principe de responsabilité unique (SRP): il Employeepeut être nécessaire de changer si les règles de changement de salaire changent; mais il peut également être nécessaire de changer si les règles de calcul du bonus EOY changent. Avec l'approche découplée, ce n'est pas le cas, car vous pouvez avoir plusieurs modules, chacun avec une responsabilité.

Donc, comme d'habitude, l'approche PF offre une plus grande modularité / composabilité.

la-yumba
la source
-1

Je pense que l'essentiel du problème est qu'un modèle anémique avec toute la logique de domaine dans les services qui fonctionnent sur ce modèle est essentiellement une programmation procédurale - par opposition à une programmation "réelle" où des objets sont "intelligents" et ne contiennent pas uniquement des données. mais aussi la logique la plus étroitement liée aux données.

Et le même contraste existe avec la programmation fonctionnelle: «réel» FP signifie utiliser des fonctions en tant qu’entités de première classe, transmises en tant que paramètres, construites à la volée et renvoyées en tant que valeur de retour. Mais lorsque vous ne parvenez pas à utiliser tout ce pouvoir et que vous n'avez que des fonctions qui agissent sur des structures de données échangées entre elles, vous vous retrouvez au même endroit: vous effectuez essentiellement une programmation procédurale.

Michael Borgwardt
la source
5
Oui, c’est essentiellement ce que dit le PO dans sa question. Vous semblez tous les deux avoir manqué le point que vous pouvez toujours avoir une composition fonctionnelle.
Robert Harvey
-3

J'aimerais savoir comment DDD s'inscrit dans le paradigme de la PF

Je pense que oui, mais en grande partie comme une approche tactique de la transition entre des objets de valeur immuables ou comme un moyen de déclencher des méthodes sur des entités. (Où la plupart de la logique habite encore dans l'entité.)

et si le terme «modèle anémique» existe toujours dans ce cas.

Eh bien, si vous voulez dire "d'une manière analogue à la POO traditionnelle", il est utile d'ignorer les détails d'implémentation habituels et de revenir à la base: quelle langue vos experts de domaine utilisent-ils? Quelle intention est votre capturer de vos utilisateurs?

En supposant qu'ils parlent de l'enchaînement de processus et de fonctions, il semble alors que les fonctions (ou au moins les objets "do-er") sont fondamentalement vos objets de domaine!

Ainsi, dans ce scénario, un "modèle anémique" se produirait probablement lorsque vos "fonctions" ne sont pas réellement exécutables et ne sont que des constellations de métadonnées interprétées par un service qui effectue le travail réel.

Darien
la source
1
Un modèle anémique se produirait lorsque vous passeriez des types de données abstraits, tels que des nuplets, des enregistrements ou des listes, à différentes fonctions à des fins de traitement. Vous n'avez besoin de rien d'aussi exotique qu'une "fonction qui ne s'exécute pas" (quoi que ce soit).
Robert Harvey
D'où les guillemets autour des "fonctions", pour souligner à quel point l'étiquette devient inappropriée lorsqu'elles sont anémiques.
Darien
Si vous êtes ironique, c'est un peu subtil.
Robert Harvey