Business Objects - Conteneurs ou fonctionnels?


19

C'est une question que j'ai posée il y a quelque temps sur SO, mais elle pourrait être mieux discutée ici ...

Là où je travaille, nous avons fait plusieurs allers-retours sur ce sujet à plusieurs reprises et recherchons un bilan de santé mentale. Voici la question: les objets métier doivent-ils être des conteneurs de données (plus comme des DTO ) ou doivent-ils également contenir une logique pouvant exécuter certaines fonctionnalités sur cet objet.

Exemple - Prenez un objet client, il contient probablement des propriétés communes (Nom, Id, etc.), cet objet client doit-il également inclure des fonctions (Enregistrer, Calc, etc.)?

Un raisonnement dit de séparer l'objet de la fonctionnalité (principe de responsabilité unique) et de placer la fonctionnalité dans une couche ou un objet Business Logic.

L'autre raisonnement dit, non, si j'ai un objet client, je veux juste appeler Customer.Save et en finir. Pourquoi dois-je connaître une autre classe pour sauver un client si je consomme l'objet?

Nos deux derniers projets ont eu des objets séparés de la fonctionnalité, mais le débat a de nouveau été soulevé sur un nouveau projet.

Qu'est-ce qui a plus de sens et pourquoi ??


1
Pourriez-vous nous en dire plus sur votre projet? La nature de votre projet déterminera quelle solution est la meilleure.

Réponses:


5

Si vous considérez qu'un client fait partie du modèle de domaine, il est logique (en particulier dans le contexte de DDD mais sans s'y limiter) d'avoir à la fois des propriétés et des opérations pour cet objet.

Cela dit, cependant, je pense que l'exemple que vous avez utilisé est mauvais et est la cause de l'argument. Si vous parlez de persistance, les clients ne se «sauvegardent» généralement pas; tout ce que vous utilisez pour la persistance le sera. Il est logique que tout type de persistance appartienne à la couche / partition de persistance. C'est généralement l'idée derrière le modèle de référentiel. ** Une méthode comme Customer.UpgradeWarranty () ou Customer.PromoteToPreferred () rend l'argument plus clair.

Cela ne supprime pas non plus la possibilité d'avoir des DTO . Considérez la situation où vous allez transmettre des informations client à un service distant par exemple. Il peut ne pas avoir de sens pour un client de créer lui-même un DTO pour le transport, c'est une préoccupation architecturale, mais un argument pourrait en être fait dans la persistance ou la couche réseau / partition / code / what-have-you. Dans ce cas, un tel objet pourrait avoir des méthodes qui ressemblent à ceci

public static CustomerDTO GetDTO(Customer c) { /* ... */ }

public static Customer GetCustomer(CustomerDTO cdto) { /* ... */ }

Donc, en résumé, il est parfaitement logique d'avoir des opérations sur un objet de domaine qui sont congruentes avec les opérations logiques dans le domaine.

Google pour «Persistence Ignorance» pour un certain nombre de bonnes discussions sur le sujet ( cette question SO , et sa réponse acceptée est un bon point de départ).

** Cela devient un peu confus avec certains logiciels OR / M où vous êtes obligé d'hériter d'une base persistante qui a une méthode 'Save'.


3

En fait, j'ai récemment retravaillé du code pour séparer les objets des données. La raison en est que les données sont obtenues via un service séparé, et il est beaucoup plus facile de simplement passer les données brutes vers / depuis le serveur au lieu de passer tout l'objet en arrière et d'avoir à traiter les erreurs de validation sur le serveur.

Pour les petits projets, le fait que les objets contiennent leur logique métier et leurs données est probablement correct, mais pour les grands projets ou les projets qui pourraient s'étendre au fil du temps, je séparerais certainement les couches.


3

Je pense que les deux façons de le faire peuvent avoir leurs avantages, mais je le regarderais d'un point de vue orienté objet ou domaine: quelles sont les responsabilités des différentes classes et objets.

Je me pose donc la question suivante dans votre cas concret: un client doit-il savoir comment se sauver?

Pour moi, la réponse serait non: logiquement, cela n'a pas de sens et un client ne devrait absolument rien savoir d'un quelconque cadre de persistance (séparation des responsabilités).

Quant aux exemples de SnOrfus avec Customer.UpgradeWarranty () ou Customer.PromoteToPreferred (), ils sont évidemment plus orientés vers la logique métier que Customer.Save (). Il existe différentes approches, mais encore une fois, si vous vous demandez si un client est censé pouvoir mettre à niveau sa garantie, la réponse peut être à la fois oui ou non, selon la façon dont vous le voyez:

  • oui: un client peut bien entendu étendre sa garantie
  • non: un client peut demander à être mis à niveau, mais la mise à niveau est effectuée par quelqu'un d'autre

Retour à votre question d'origine; La manière la plus logique dépendra probablement de la personne à qui vous posez la question et de sa méthode préférée, mais la chose la plus importante est probablement de s'en tenir à une façon de le faire.

D'après mon expérience, cependant, la séparation des données et de la logique (métier) permet une architecture plus simple, mais pas aussi excitante.


2

Je pense que vous parlez spécifiquement de la différence entre le ActiveRecordmotif et le Repositorymotif. Dans le premier cas, les entités savent se maintenir et dans le second, le référentiel connaît la persistance. Je pense que ce dernier offre une meilleure séparation des préoccupations.

Dans un sens plus large, si les entités agissent davantage comme une structure de données, elles ne devraient pas avoir de comportements, mais si elles ont des comportements, elles ne devraient pas être utilisées comme une structure de données. Exemple:

Structure de données:

class CustomerController
{
    public int MostRecentOrderLines(Customer customer)
    {
        var o = (from order in customer.Orders
                orderby order.OrderDate desc
                select order).First();
        return o.OrderLines.ToList().Count;
    }
}

Structure non liée aux données:

class Customer
{
    public int MostRecentOrderLines()
    {
        // ... same ...
    }
}

Dans le premier cas, vous pouvez parcourir l'arborescence de la structure de données de votre modèle sans problème, de n'importe où dans votre code de travail, et c'est correct, car vous le traitez comme une structure de données. Dans ce dernier cas, le client ressemble plus à un service pour un client donné, vous ne devez donc pas appeler de méthodes sur le résultat. Toutes les choses que vous voulez savoir sur le client doivent être disponibles en appelant une méthode sur l'objet client.

Voulez-vous donc que vos entités soient des structures de données ou des services? Par souci de cohérence, il semble préférable de s'en tenir à un. Dans le premier cas, placez votre logique de type "service" ailleurs, pas dans l'entité.


1

Je ne peux pas vraiment répondre à votre question, mais je trouve ça drôle que nous ayons également cette discussion dans l'un de nos projets à l'école. Je voudrais moi-même séparer la logique des données. Mais beaucoup de mes camarades de classe disent que l'objet doit contenir toute la logique ET les données.

Je vais essayer de résumer les faits qu'ils évoquent:

  • Un objet de classe affaires représente une chose, donc toutes les données ET la logique doivent être contenues. (par exemple, si vous voulez ouvrir une porte, vous le faites vous-même, vous ne demandez pas à quelqu'un d'autre. mauvais exemple je sais)

  • Plus facile à comprendre le code et les fonctionnalités d'un objet bu.

  • Moins de complexité dans la conception

Je leur dis qu'ils sont paresseux et je dirais ceci:

  • Oui, les classes commerciales représentent des choses, donc elles contiennent des données. Mais ce n'est pas bien de se sauver ou même de copier. Vous ne pouvez pas faire ça à rl.

  • Rendre l'objet responsable de tout ne le rend pas bon pour la durabilité et la maintenance à l'avenir. Si vous avez une classe séparée qui est responsable de la sauvegarde, si vous ajoutez un nouvel objet à la conception, vous pouvez facilement implémenter sa fonction de sauvegarde. au lieu de tout coder à nouveau dans cette classe.

  • En ayant un objet capable de conserver les données, cet objet peut contenir une connexion à la base de données et tout le trafic de base de données est guidé vers cet objet. (en gros, c'est la couche d'accès aux données), sinon tous les objets Business devraient avoir une connexion. (ce qui ajoute de la complexité)

Eh bien d'une manière ou d'une autre, je vais demander à un professeur. Lorsque j'aurai fait cela, je posterai sa réponse ici aussi si vous le souhaitez. ;)

Éditer:

J'ai oublié de mentionner que ce projet concernait une librairie.


Vos camarades de classe ont raison, sauf qu'ils confondent la logique métier avec d'autres formes de logique (dans ce cas, la logique architecturale ou de persistance).
Steven Evers

1

La réponse dépendra vraiment de votre architecture / conception - une architecture DDD avec un modèle de domaine sera très différente d'un modèle CRUD centré sur les données en matière de conception d'objet.

En général cependant, gardez à l'esprit que dans la conception orientée objet, vous essayez d'encapsuler l'état et d'exposer le comportement. Cela signifie que vous essayez généralement d'obtenir l'état associé à un comportement aussi proche que possible de ce comportement - lorsque vous ne le faites pas, vous êtes obligé d'exposer l'état sous une forme ou une autre.

Dans un modèle de domaine, vous voulez séparer le modèle commercial des problèmes d'infrastructure - vous devez donc absolument éviter les méthodes comme «.Save». Je ne me souviens pas de la dernière fois où j'ai "sauvé" un client dans la vraie vie!

Dans une application CRUD, les données sont le citoyen de première classe, donc un ".Save" est tout à fait approprié.

La plupart des applications réelles du monde réel auront un mélange de ces paradigmes - modèle de domaine où il existe des règles métier complexes ou changeantes rapidement, DTO pour le transfert de données au-delà des frontières, CRUD (ou quelque chose entre CRUD et un modèle de domaine, comme activerecord) ailleurs. Il n'y a pas de règle universelle.


0

Je réfléchis à la même question depuis un certain temps - je pense que le composant de données et le composant logique doivent être séparés. Je crois cela parce que cela vous met dans le bon état d'esprit en ce qui concerne la logique métier en tant qu'interface avec les données qui donnent du sens.

Je modifie également le point de Scott Whitlock d'en haut (sauf que je n'ai aucun point en tant que nouveau membre), les classes de données ou de logique métier ne devraient pas vraiment avoir à se soucier de la façon dont l'objet est stocké de manière persistante.

Cela étant dit, si vous avez affaire à un produit existant, tant que vous avez des interfaces contractuelles propres - c'est OK et maintenable aussi ...

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.