Aujourd'hui, je suis entré dans un débat animé avec un autre développeur de mon organisation sur où et comment ajouter des méthodes aux classes mappées de base de données. Nous utilisons sqlalchemy
, et une grande partie de la base de code existante dans nos modèles de base de données n'est guère plus qu'un sac de propriétés mappées avec un nom de classe, une traduction presque mécanique des tables de base de données en objets python.
Dans l'argument, ma position était que la valeur principale de l'utilisation d'un ORM était que vous pouvez attacher des comportements et des algorithmes de bas niveau aux classes mappées. Les modèles sont d'abord des classes et secondairement persistants (ils peuvent être persistants en utilisant xml dans un système de fichiers, vous n'avez pas besoin de vous en soucier). Son point de vue était que tout comportement est une "logique métier" et appartient nécessairement n'importe où sauf dans le modèle persistant, qui ne doit être utilisé que pour la persistance de la base de données.
Je ne pense qu'il ya une distinction entre ce qui est la logique métier, et doit être séparé, car il a un certain isolement par rapport au niveau inférieur de la façon qui soit mis en oeuvre, et la logique de domaine, que je crois est l'abstraction fournie par les classes de modèle discuté dans le paragraphe précédent, mais j'ai du mal à mettre le doigt sur ce que c'est. J'ai une meilleure idée de ce que pourrait être l'API (qui, dans notre cas, est HTTP "ReSTful"), en ce sens que les utilisateurs invoquent l'API avec ce qu'ils veulent faire , distinct de ce qu'ils sont autorisés à faire et comment cela se fait.
tl; dr: Quels types de choses peuvent ou devraient aller dans une méthode dans une classe mappée lors de l'utilisation d'un ORM, et ce qui devrait être laissé de côté, pour vivre dans une autre couche d'abstraction?
la source
Réponses:
Je suis surtout avec toi; votre collègue semble plaider soit pour l' anti-modèle du modèle de domaine anémique, soit pour dupliquer le modèle dans un "modèle de persistance" sans aucun avantage évident (je travaille sur un projet Java où cela a été fait, et c'est un énorme casse-tête de maintenabilité, comme cela signifie trois fois le travail chaque fois que quelque chose change dans le modèle).
Règle générale: la classe doit contenir une logique décrivant des faits de base sur les données qui sont vrais en toutes circonstances. La logique spécifique à un cas d'utilisation devrait être ailleurs. Un exemple est la validation, il y a un article intéressant de Martin Fowler où il fait valoir qu'il doit être considéré comme dépendant du contexte.
la source
Il s'agit d'un jugement qui dépend vraiment de la taille et de l'échelle prévues de ce que vous développez. L'approche la plus rigide consiste à limiter les types ORM à un composant d'accès aux données et à utiliser des POCO dans une bibliothèque commune comme types référencés et utilisés par toutes les couches. Cela permettrait une séparation physique future ainsi qu'une séparation logique. Vous pouvez également décider qu'une couche supplémentaire doit exister entre l'interface utilisateur et la couche de logique métier. Ceci est généralement appelé couche de façade ou d'interface métier. Cette couche supplémentaire est l'endroit où réside votre "code de cas d'utilisation". Le code individuel faiblement couplé est appelé par la couche Facade / BI (par exemple, Facade a une fonction ProcessOrder () qui appelle la logique métier 1: M fois pour effectuer toutes les étapes nécessaires au traitement de la commande).
Cependant, tout cela étant dit: souvent, cette quantité d'architecture est simplement une surpuissance inutile. Par exemple, codez spécifiquement pour un site Web simple où vous n'avez pas l'intention d'empaqueter ses composants pour les réutiliser. Il est parfaitement valable de créer un site Web MVC et d'utiliser des objets EF pour ce type de solution. Si le site doit évoluer plus tard, vous pouvez envisager un clustering ou un processus souvent perdu, appelé «refactoring».
la source
Rappelez simplement à votre collègue que vous n'avez pas besoin de surarchitecturer les modèles comme s'il s'agissait d'un projet Java. Je veux dire, comparer deux objets persistants est un comportement, mais ce n'est pas spécifié par la couche de persistance. Donc, la question des 6 bières est la suivante: pourquoi avoir des classes complètement indépendantes décrivant quelque chose à propos de la même chose? Bien sûr, la persistance est un aspect assez important d'un modèle pour être traité séparément, mais pas assez pour justifier qu'il soit traité indépendamment de tout le reste. Si vous conduisez votre voiture, lavez-la ou la cassez, vous manipulez votre voiture tout le temps.
Alors pourquoi ne pas simplement composer tous ces différents aspects en une seule classe de modèle? Vous avez besoin d'un tas de méthodes de classe traitant des objets persistants - mettez-les dans une classe; vous avez un tas de méthodes d'instance traitant de la validation - mettez-les dans une autre. Enfin, mélangez les deux et le tour est joué! Vous avez obtenu une représentation de modèle intelligente, consciente de vous-même et entièrement contenue ici.
la source
En plus des autres réponses, faites attention aux cavehats cachés lorsque vous utilisez des modèles de domaine riches avec un ORM.
J'ai eu des problèmes pour injecter des services polymorphes dans les classes de modèles persistants lorsque j'essayais de réaliser quelque chose comme le pseudocode suivant:
Dans ce cas, une organisation peut exiger un
HRService
dépendance en tant que constructeur (par exemple). Vous ne pouvez généralement pas contrôler facilement l'instanciation de vos classes de modèle lorsque vous utilisez un ORM.J'utilisais Doctrine ORM et le conteneur de service de Symfony. J'ai dû monkeypatch l'ORM d'une manière pas si élégante et n'avais pas d'autre choix que de séparer la persistance et les modèles commerciaux. Je n'ai pas encore essayé avec sqlachemy, pensai-je. Python pourrait s'avérer plus flexible que PHP pour ce genre de choses.
la source