Quelle est la précision de la «logique métier doit être dans un service, pas dans un modèle»?


398

Situation

Plus tôt dans la soirée, j'ai répondu à une question sur StackOverflow.

La question:

La modification d'un objet existant doit être effectuée dans la couche référentiel ou dans le service?

Par exemple, si j'ai un utilisateur qui a des dettes. Je veux changer sa dette. Dois-je le faire dans UserRepository ou dans le service par exemple BuyingService en récupérant un objet, en le modifiant et en le sauvegardant?

Ma réponse:

Vous devez laisser la responsabilité de la mutation d'un objet sur ce même objet et utiliser le référentiel pour récupérer cet objet.

Exemple de situation:

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

Un commentaire que j'ai reçu:

La logique métier doit vraiment être dans un service. Pas dans un modèle.

Que dit Internet?

Cela m'a donc incité à chercher, car je n'ai jamais vraiment (consciemment) utilisé une couche de service. J'ai commencé à lire sur le modèle de couche de service et le modèle d'unité de travail, mais jusqu'à présent, je ne peux pas dire que je suis convaincu qu'une couche de service doit être utilisée.

Prenons par exemple cet article de Martin Fowler sur l'anti-pattern d'un modèle de domaine anémique:

Il existe des objets, dont beaucoup portent le nom des noms de l'espace de domaine, et ces objets sont liés aux relations riches et à la structure des vrais modèles de domaine. Le problème vient lorsque vous observez le comportement et vous vous rendez compte qu'il n'y a pratiquement aucun comportement sur ces objets, ce qui en fait un peu plus que des sacs d'accesseurs et de passeurs. En effet, ces modèles sont souvent accompagnés de règles de conception stipulant que vous ne devez mettre aucune logique de domaine dans les objets de domaine. Au lieu de cela, il existe un ensemble d'objets de service qui capturent toute la logique du domaine. Ces services vivent sur le modèle de domaine et utilisent le modèle de domaine pour les données.

(...) La logique qui devrait figurer dans un objet de domaine est la logique de domaine - validations, calculs, règles de gestion - comme vous voulez l'appeler.

Pour moi, cela semblait correspondre exactement à la situation: je préconisais la manipulation des données d'un objet en introduisant des méthodes à l'intérieur de cette classe. Cependant, je me rends compte que cela devrait être une donnée dans les deux sens, et cela a probablement plus à voir avec la façon dont ces méthodes sont appelées (en utilisant un référentiel).

J'ai également eu le sentiment que, dans cet article (voir ci-dessous), une couche de services est davantage considérée comme une façade qui délègue le travail au modèle sous-jacent, plutôt qu'une couche à forte intensité de travail.

Couche d'application [son nom pour la couche de service]: définit les tâches que le logiciel est censé effectuer et demande aux objets du domaine expressif de résoudre les problèmes. Les tâches dont cette couche est responsable ont une signification pour l'entreprise ou sont nécessaires pour une interaction avec les couches d'application d'autres systèmes. Cette couche est mince. Il ne contient pas de règles commerciales ni de connaissances, mais coordonne uniquement les tâches et délègue le travail aux collaborations d'objets de domaine dans la couche suivante. Son état ne reflète pas la situation de l'entreprise, mais il peut en être de même pour la progression d'une tâche pour l'utilisateur ou le programme.

Ce qui est renforcé ici :

Interfaces de service. Les services exposent une interface de service à laquelle tous les messages entrants sont envoyés. Vous pouvez considérer une interface de service comme une façade qui expose la logique métier implémentée dans l'application (généralement la logique de la couche de gestion) aux consommateurs potentiels.

Et ici :

La couche de service doit être dépourvue de toute logique applicative ou commerciale et se concentrer principalement sur quelques préoccupations. Il doit encapsuler les appels de la couche de gestion, traduire votre domaine dans un langage commun que vos clients peuvent comprendre et gérer le support de communication entre le serveur et le client demandeur.

C'est un contraste sérieux avec d' autres ressources qui parlent de la couche de service:

La couche service doit être composée de classes avec des méthodes qui sont des unités de travail avec des actions appartenant à la même transaction.

Ou la deuxième réponse à une question que j'ai déjà liée:

À un moment donné, votre application voudra une logique métier. En outre, vous pouvez valider l’entrée pour vous assurer qu’il n’ya rien de mal ou de non performant qui est demandé. Cette logique appartient à votre couche de service.

"Solution"?

En suivant les directives de cette réponse , j'ai proposé l'approche suivante qui utilise une couche de service:

class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

Conclusion

Dans l'ensemble, peu de choses ont changé ici: le code du contrôleur a été transféré à la couche service (ce qui est une bonne chose, donc cette approche présente des avantages.) Cependant, cela ne semble pas avoir un rapport avec ma réponse initiale.

Je me rends compte que les modèles de conception sont des directives et non des règles immuables à appliquer autant que possible. Pourtant, je n'ai pas trouvé d'explication définitive sur la couche de service et sur la manière dont elle devrait être considérée.

  • Est-ce un moyen d'extraire simplement la logique du contrôleur et de la placer dans un service?

  • Est-il censé former un contrat entre le contrôleur et le domaine?

  • Devrait-il y avoir une couche entre le domaine et le service?

Et, dernier point mais non le moindre: suivre le commentaire original

La logique métier doit vraiment être dans un service. Pas dans un modèle.

  • Est-ce correct?

    • Comment pourrais-je introduire ma logique métier dans un service au lieu du modèle?

6
Je traite la couche service comme le lieu où placer la partie inévitable du script de transaction agissant sur les racines agrégées. Si ma couche de service devient trop complexe, cela signifie que je vais dans le sens du modèle anémique et que mon modèle de domaine nécessite une attention et une révision. J'essaie aussi de mettre la logique dans SL qui ne va pas être dupliquée de par sa nature.
Pavel Voronin

Je pensais que les services faisaient partie de la couche Modèle. Est-ce que je me trompe en pensant cela?
Florian Margaine

J'utilise une règle de base: ne comptez pas sur ce dont vous n'avez pas besoin; sinon je pourrais être (généralement négativement) affecté par des modifications de la partie dont je n'ai pas besoin. En conséquence, je me retrouve avec beaucoup de rôles clairement définis. Mes objets de données ont un comportement aussi longtemps que tous les clients l’utilisent. Sinon, je déplace le comportement dans des classes implémentant le rôle requis.
Beluchin

1
La logique de contrôle de flux d'application appartient à un contrôleur. La logique d'accès aux données appartient à un référentiel. La logique de validation appartient à une couche de service. Une couche de service est une couche supplémentaire dans une application ASP.NET MVC qui assure la communication entre une couche de contrôleur et une couche de référentiel. La couche de service contient une logique de validation commerciale. dépôt. asp.net/mvc/overview/older-versions-1/models-data/…
Kbdavis07

3
IMHO, le bon modèle de domaine de type POO devrait en effet éviter les "services métier" et conserver la logique métier dans le modèle. Mais la pratique montre qu'il est si tentant de créer une couche métier énorme avec des méthodes distinctement nommées et de placer une transaction (ou une unité de travail) autour d'une méthode entière. Il est beaucoup plus facile de traiter plusieurs classes de modèle dans une même méthode de service métier que de planifier les relations entre ces classes de modèle, car vous auriez alors des questions difficiles à répondre: où est la racine d'agrégat? Où dois-je commencer / valider une transaction de base de données? Etc.
JustAMartin

Réponses:


369

Afin de définir les responsabilités d' un service , vous devez d'abord définir ce qu'est un service .

Service n'est pas un terme logiciel canonique ou générique. En fait, le suffixe Servicesur un nom de classe est un peu comme le tant décrié directeur : Il vous dit presque rien de ce que l'objet réellement fait .

En réalité, ce qu'un service doit faire est hautement spécifique à l'architecture:

  1. Dans une architecture en couches traditionnelle, service est littéralement synonyme de couche de logique métier . C'est la couche entre l'interface utilisateur et les données. Par conséquent, toutes les règles de gestion entrent dans les services. La couche de données ne doit comprendre que les opérations CRUD de base et la couche d'interface utilisateur doit uniquement traiter du mappage des DTO de présentation vers et depuis les objets métier.

  2. Dans une architecture distribuée de style RPC (SOAP, UDDI, BPEL, etc.), le service est la version logique d'un noeud final physique . Il s'agit essentiellement d'un ensemble d'opérations que le responsable souhaite fournir en tant qu'API publique. Divers guides de bonnes pratiques expliquent qu’une opération de service doit en réalité être une opération au niveau de l’entreprise et non pas CRUD, et j’ai tendance à être d’accord.

    Toutefois, étant donné que tout acheminer vers un service distant distant peut nuire gravement aux performances, il est généralement préférable de ne pas laisser ces services implémenter la logique métier eux-mêmes. au lieu de cela, ils doivent envelopper un ensemble "interne" d'objets métier. Un seul service peut impliquer un ou plusieurs objets métier.

  3. Dans une architecture MVP / MVC / MVVM / MV *, les services n'existent pas du tout. Si tel est le cas, le terme est utilisé pour désigner tout objet générique pouvant être injecté dans un contrôleur ou un modèle de vue. La logique métier est dans votre modèle . Si vous souhaitez créer des "objets de service" pour orchestrer des opérations complexes, cela est considéré comme un détail d'implémentation. Malheureusement, beaucoup de gens implémentent MVC de cette manière, mais cela est considéré comme un anti-modèle ( modèle de domaine anémique ) car le modèle lui-même ne fait rien, il s'agit simplement d'un ensemble de propriétés pour l'interface utilisateur.

    Certaines personnes pensent, à tort, que l’utilisation d’une méthode de contrôleur à 100 lignes et son intégration dans un service constituent en quelque sorte une meilleure architecture. Ce n'est vraiment pas; tout ce qu'il fait est d'ajouter une autre couche d'indirection, probablement inutile. En pratique , le contrôleur fait toujours le travail, il le fait simplement via un objet "helper" mal nommé. Je recommande vivement la présentation de Wicked Domain Models de Jimmy Bogard pour un exemple clair de la façon de transformer un modèle de domaine anémique en un modèle utile. Cela implique un examen minutieux des modèles que vous exposez et des opérations réellement valables dans un contexte commercial .

    Par exemple, si votre base de données contient des commandes et que vous avez une colonne pour Montant total, votre application ne devrait probablement pas être autorisée à modifier ce champ en une valeur arbitraire, car (a) c'est l'historique et (b) c'est supposé être déterminé par ce qui est dans l'ordre, ainsi que peut - être d'autres données sensibles au temps / règles. Créer un service pour gérer les commandes ne résout pas nécessairement ce problème, car le code utilisateur peut toujours récupérer l'objet de commande réel et en modifier le montant. Au lieu de cela, la commande elle-même devrait être responsable de s'assurer qu'elle ne peut être modifiée que de manière sûre et cohérente.

  4. Dans DDD, les services sont spécifiquement conçus pour la situation dans laquelle une opération n'appartient pas correctement à une racine d'agrégat . Vous devez faire attention ici, car souvent le besoin d'un service peut impliquer que vous n'utilisiez pas les racines correctes. Toutefois, à supposer que ce soit le cas, un service est utilisé pour coordonner des opérations sur plusieurs racines, ou parfois pour traiter des problèmes n’impliquant pas du tout le modèle de domaine (comme, par exemple, l’écriture d’informations dans une base de données BI / OLAP).

    Un aspect notable du service DDD est qu’il est autorisé à utiliser des scripts de transaction . Lorsque vous travaillez sur des applications volumineuses, il est très probable que vous rencontriez des situations dans lesquelles il est beaucoup plus facile d'accomplir quelque chose avec une procédure T-SQL ou PL / SQL plutôt qu'avec le modèle de domaine. C'est OK, et cela appartient à un service.

    C'est une rupture radicale avec la définition des services à architecture en couches. Une couche de service encapsule des objets de domaine; un service DDD encapsule tout ce qui ne se trouve pas dans les objets du domaine et n'a aucun sens.

  5. Dans une architecture orientée services, un service est considéré comme l'autorité technique d'une capacité métier. Cela signifie qu'il est le propriétaire exclusif d'un certain sous-ensemble des données de l'entreprise et que rien d'autre n'est autorisé à toucher ces données - pas même à les lire .

    Par nécessité, les services sont en réalité une proposition de bout en bout dans une SOA. Cela signifie qu'un service n'est pas tant un composant spécifique qu'une pile entière , et votre application entière (ou votre entreprise tout entière) est un ensemble de ces services fonctionnant côte à côte sans intersection, sauf au niveau des couches de messagerie et d'interface utilisateur. Chaque service a ses propres données, ses propres règles commerciales et sa propre interface utilisateur. Ils n'ont pas besoin d'orchestrer les uns avec les autres, car ils sont censés être alignés sur les affaires - et, à l'image de l'entreprise elle-même, chaque service a ses propres responsabilités et opère plus ou moins indépendamment des autres.

    Ainsi, la définition de la SOA, chaque morceau de logique métier partout est contenu dans le service, mais là encore, est donc l'ensemble du système . Les services dans une SOA peuvent avoir des composants et des terminaux , mais il est assez dangereux d'appeler un morceau de code un service car il entre en conflit avec ce que le "S" original est censé signifier.

    Comme la SOA est généralement très friande de la messagerie, les opérations que vous auriez peut-être empaquetées auparavant dans un service sont généralement encapsulées dans des gestionnaires , mais leur multiplicité est différente. Chaque gestionnaire gère un type de message, une opération. C'est une interprétation stricte du principe de responsabilité unique , mais elle permet une grande facilité d'entretien, car chaque opération possible appartient à sa propre classe. Vous n'avez donc pas vraiment besoin d' une logique métier centralisée, car les commandes représentent les opérations commerciales plutôt que les opérations techniques.

En fin de compte, quelle que soit l'architecture que vous choisissez, il y aura un composant ou une couche possédant la majeure partie de la logique métier. Après tout, si la logique métier est dispersée partout, il ne reste plus que du code spaghetti. Mais si vous appelez ce composant un service et comment il est conçu en termes de nombre ou de taille des opérations, cela dépend de vos objectifs architecturaux.

Il n'y a pas de bonne ou de mauvaise réponse, seulement ce qui s'applique à votre situation.


12
Merci pour la réponse très élaborée, vous avez clarifié tout ce à quoi je peux penser. Alors que les autres réponses sont bonnes à excellentes, je pense que cette réponse les surpasse toutes et que je vais donc accepter celle-ci. Je vais l'ajouter ici pour les autres réponses: qualité et informations exquises, mais malheureusement, je ne pourrai que vous donner un vote positif.
Jeroen Vannevel

2
Je ne suis pas d'accord avec le fait que dans une architecture en couches traditionnelle, service est synonyme de couche de logique métier.
CodeART

1
@CodeART: Il s'agit d'une architecture à 3 niveaux. Je l' ai vu des architectures à 4 étages où il y a une « couche d'application » entre les couches présentation et affaires, ce qui est parfois aussi appelé une couche « service », mais honnêtement, les seuls endroits que j'ai jamais vu cette mise en œuvre avec succès sont énormes tentaculaire des produits infiniment configurables exécutant votre entreprise à part entière, tels que SAP ou Oracle, et je ne pensais pas que cela méritait vraiment d'être mentionné ici. Je peux ajouter une clarification si vous le souhaitez.
Aaronaught

1
Mais si nous prenons plus de 100 contrôleurs de ligne (par exemple, ce message accepte le contrôleur - puis désérialise l'objet JSON, valide la validation, applique les règles métier, enregistre dans la base de données, renvoie le résultat) et déplace une partie de la logique vers l'une des méthodes de service appelées donn ' Cela nous aide-t-il à tester séparément chaque partie séparément?
artjom

2
@Aaronaught Je voulais clarifier une chose si nous avons des objets de domaine mappés sur la base de données via ORM et qu'il n'y a pas de logique commerciale dans ce modèle de domaine anémique ou non?
artjom

40

En ce qui concerne votre titre , je ne pense pas que la question ait un sens. Le modèle MVC comprend des données et une logique métier. Dire que la logique devrait être dans le service et non dans le modèle, c'est comme dire: "Le passager devrait s'asseoir sur le siège, pas dans la voiture".

Là encore, le terme "Modèle" est un terme surchargé. Vous ne vouliez peut-être pas parler de modèle MVC, mais de modèle au sens de l'objet de transfert de données (DTO). AKA une entité. C’est ce dont parle Martin Fowler.

À mon avis, Martin Fowler parle de choses dans un monde idéal. Dans le monde réel de Hibernate et JPA (en Java), les DTO sont une abstraction super fuyante. J'aimerais mettre ma logique métier dans mon entité. Cela rendrait les choses plus propres. Le problème est que ces entités peuvent exister dans un état géré / mis en cache très difficile à comprendre et empêchant constamment vos efforts. Pour résumer mon opinion: Martin Fowler recommande la bonne façon, mais les ORM vous en empêchent.

Je pense que Bob Martin a une suggestion plus réaliste et il la donne dans cette vidéo qui n’est pas gratuite . Il parle de garder votre DTO libre de logique. Ils conservent simplement les données et les transfèrent vers une autre couche beaucoup plus orientée objet et n'utilisant pas directement les DTO. Cela évite l'abstraction qui fuit de vous mordre. La couche avec les DTO et les DTO eux-mêmes ne sont pas OO. Mais une fois que vous avez quitté cette couche, vous devenez aussi OO que le préconise Martin Fowler.

L'avantage de cette séparation est qu'elle supprime la couche de persistance. Vous pouvez passer de JPA à JDBC (ou inversement) et aucune logique métier ne doit être modifiée. Cela dépend des DTO, peu importe la façon dont ces DTO sont peuplés.

Pour modifier légèrement les sujets, vous devez tenir compte du fait que les bases de données SQL ne sont pas orientées objet. Mais les ORM ont généralement une entité - qui est un objet - par table. Donc, depuis le début, vous avez déjà perdu une bataille. D'après mon expérience, vous ne pouvez jamais représenter l'entité exactement de la manière que vous souhaitez orientée objet.

En ce qui concerne " un service", Bob Martin s'opposerait à ce qu'une classe soit nommée FooBarService. Ce n'est pas orienté objet. Que fait un service? Tout ce qui concerne FooBars. Il peut aussi être étiqueté FooBarUtils. Je pense qu'il préconiserait une couche de service (un meilleur nom serait la couche de logique applicative) mais chaque classe de cette couche aurait un nom significatif.


2
D'accord avec votre point sur les ORM; ils propagent un mensonge selon lequel vous mappez votre entité directement sur la base de données avec eux, alors qu'en réalité une entité peut être stockée sur plusieurs tables.
Andy

@ Daniel Kaplan, connaissez-vous le lien mis à jour pour la vidéo de Bob Martin?
Brian Morearty

25

Je travaille sur le projet greenfield en ce moment et nous avons dû prendre quelques décisions architecturales pas plus tard qu'hier. Curieusement, j'ai dû revoir quelques chapitres de «Patterns of Enterprise Application Architecture».

Voici ce que nous avons trouvé:

  • Couche de données. Requêtes et mises à jour de la base de données. La couche est exposée à travers des référentiels injectables.
  • Couche de domaine. C'est là que réside la logique métier. Cette couche utilise des référentiels injectables et est responsable de la majorité de la logique métier. C’est le cœur de l’application que nous allons tester à fond.
  • Couche de service. Cette couche communique avec la couche de domaine et traite les demandes du client. Dans notre cas, la couche de service est assez simple: elle relaie les demandes à la couche de domaine, gère la sécurité et quelques autres problèmes transversaux. Ce n’est pas très différent d’un contrôleur dans une application MVC - les contrôleurs sont petits et simples.
  • Couche client. Entretiens avec la couche service via SOAP.

Nous nous retrouvons avec ce qui suit:

Client -> Service -> Domaine -> Données

Nous pouvons remplacer la couche client, service ou data par une quantité de travail raisonnable. Si votre logique de domaine résidait dans le service et que vous aviez décidé de remplacer ou même de supprimer votre couche de service, vous auriez alors à déplacer toute la logique métier ailleurs. Une telle exigence est rare, mais cela pourrait arriver.

Cela dit, je pense que cela est assez proche de ce que Martin Fowler voulait dire par

Ces services vivent sur le modèle de domaine et utilisent le modèle de domaine pour les données.

Le diagramme ci-dessous illustre assez bien ceci:

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif


2
Vous avez choisi SOAP hier? Est-ce une exigence ou n'avez-vous pas eu une meilleure idée?
JensG

1
REST ne le coupera pas pour nos besoins. SOAP ou REST, cela ne change rien à la réponse. De ma compréhension, le service est une passerelle vers la logique de domaine.
CodeART

Absolument d'accord avec la passerelle. SOAP est un bloatware (standardisé), donc je devais demander. Et oui, aucun impact sur la question / réponse non plus.
JensG

6
Faites-vous une faveur et tuez votre couche de service. Votre interface utilisateur doit utiliser votre domaine directement. J'ai déjà vu cela auparavant et votre domaine devient invariablement un groupe de dtos anémiques, pas de modèles riches.
Andy

Ces diapositives couvrent l'explication concernant la couche de service et ce graphique ci-dessus plutôt sympa
Marc Juchli

9

C'est l'une de ces choses qui dépend vraiment du cas d'utilisation. L'intérêt général d'une couche de services est de consolider ensemble la logique métier. Cela signifie que plusieurs contrôleurs peuvent appeler le même UserService.MakeHimPay () sans se soucier de la façon dont le paiement est effectué. Ce qui se passe dans le service peut être aussi simple que de modifier une propriété d'objet ou de faire une logique complexe traitant d'autres services (par exemple, appeler des services tiers, appeler une logique de validation ou même simplement sauvegarder quelque chose dans la base de données. )

Cela ne signifie pas que vous devez retirer TOUTE la logique des objets du domaine. Parfois, il est plus logique de demander à une méthode sur l’objet domaine d’effectuer des calculs sur elle-même. Dans votre dernier exemple, le service est une couche redondante sur l'objet de référentiel / domaine. Il fournit un bon tampon contre les changements d’exigences, mais ce n’est vraiment pas nécessaire. Si vous pensez avoir besoin d'un service, essayez de faire en sorte que la simple logique "Modifiez la propriété X sur l'objet Y" au lieu de l'objet Domaine. La logique sur les classes de domaine a tendance à tomber dans le champ "calcule cette valeur à partir de champs" plutôt que d'exposer tous les champs via des getters / setters.


2
Votre position en faveur d’une couche de service avec une logique d’affaires a beaucoup de sens, mais cela laisse encore quelques questions. Dans mon message, j'ai cité plusieurs sources respectables qui parlent de la couche de services comme d'une façade vide de toute logique métier. Ceci est en contraste direct avec votre réponse, pourriez-vous clarifier cette différence?
Jeroen Vannevel

5
Je trouve que cela dépend vraiment du type de logique métier et d'autres facteurs, tels que le langage utilisé. Certaines logiques métier ne s'adaptent pas très bien aux objets du domaine. Un exemple est le filtrage / tri des résultats après les avoir extraits de la base de données. C’est la logique commerciale, mais cela n’a aucun sens pour l’objet du domaine. Je trouve que les services sont mieux utilisés pour la logique simple ou la transformation des résultats et que la logique sur le domaine est plus utile lorsqu'il s'agit de sauvegarder des données ou de calculer des données à partir de l'objet.
Firelore

8

Le moyen le plus simple d'illustrer pourquoi les programmeurs évitent de placer la logique de domaine dans les objets de domaine est qu'ils sont généralement confrontés à une situation de "où placer la logique de validation?" Prenez cet objet de domaine par exemple:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

Nous avons donc une logique de validation de base dans le setter (ne peut pas être négatif). Le problème est que vous ne pouvez pas vraiment réutiliser cette logique. Quelque part, un écran, un ViewModel ou un contrôleur doit être validé avant de valider la modification de l'objet de domaine, car il doit informer l'utilisateur avant ou lorsqu'il clique sur le bouton Enregistrer qu'ils ne peuvent pas le faire. et pourquoi . Tester une exception lorsque vous appelez le poseur est un vilain piratage, car vous auriez dû faire toute la validation avant même d'avoir démarré la transaction.

C'est pourquoi les gens déplacent un type de service dans la logique de validation, tel que MyEntityValidator. Ensuite, l'entité et la logique d'appel peuvent à la fois obtenir une référence au service de validation et le réutiliser.

Si vous ne le faites pas et que vous souhaitez toujours réutiliser la logique de validation, vous finissez par la placer dans les méthodes statiques de la classe d'entité:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

Cela rendrait votre modèle de domaine moins "anémique" et conserverait la logique de validation à côté de la propriété, ce qui est génial, mais je ne pense pas que quiconque aime vraiment les méthodes statiques.


1
Alors, quelle est la meilleure solution de validation à votre avis?
Flashrunner

3
@Flashrunner - ma logique de validation est définitivement dans la couche de logique applicative, mais est également dupliquée dans les couches d'entité et de base de données dans certains cas. La couche de gestion le gère bien en informant l'utilisateur, etc., mais les autres couches ne font que générer des erreurs / exceptions et empêchent le programmeur (moi-même) de commettre des erreurs qui corrompent les données.
Scott Whitlock

6

Je pense que la réponse est claire si vous lisez l'article de Martin Fowler sur le modèle de domaine anémique .

Supprimer la logique métier, qui est le domaine, du modèle de domaine est en train de casser la conception orientée objet.

Passons en revue le concept de base orienté objet: un objet encapsule des données et des opérations. Par exemple, la fermeture d'un compte est une opération qu'un objet de compte doit effectuer sur lui-même. par conséquent, le fait qu'une couche de service effectue cette opération n'est pas une solution orientée objet. C'est une procédure et c'est ce à quoi Martin Fowler fait référence lorsqu'il parle d'un modèle de domaine anémique.

Si vous avez une couche de service qui ferme le compte plutôt que de laisser l'objet de compte se fermer elle-même, vous n'avez pas d'objet de compte réel. Votre compte "objet" est simplement une structure de données. Comme le suggère Martin Fowler, vous vous retrouvez avec un tas de sacs avec des accesseurs et des passeurs.


1
Édité. En fait, j'ai trouvé cette explication assez utile et je ne pense pas qu'elle mérite un vote négatif.
BadHorsie

1
Il y a un inconvénient largement négligé des modèles riches. Et c’est-à-dire que les développeurs insèrent tout ce qui est légèrement lié au modèle. L'état d'ouverture / fermeture est-il un attribut du compte? Qu'en est-il du propriétaire? Et la banque? Doivent-ils tous être référencés par le compte? Est-ce que je fermerais un compte en parlant à une banque ou directement via le compte? Avec les modèles anémiques, ces connexions ne font pas partie intégrante des modèles, mais sont plutôt créées lorsque vous travaillez avec ces modèles dans d'autres classes (appelez-les services ou gestionnaires).
Hubert Grzeskowiak

4

Comment implémenteriez-vous votre logique métier dans la couche service? Lorsque vous effectuez un paiement à partir d'un utilisateur, vous créez un paiement, pas seulement en déduisant une valeur d'une propriété.

Votre méthode de paiement doit créer un enregistrement de paiement, augmenter la dette de cet utilisateur et conserver tout cela dans vos référentiels. Faire cela dans une méthode de service est incroyablement simple, et vous pouvez aussi encapsuler toute l'opération dans une transaction. Faire la même chose dans un modèle de domaine agrégé est beaucoup plus problématique.


2

La version tl; dr:
Mes expériences et opinions indiquent que tout objet ayant une logique métier doit faire partie du modèle de domaine. Le modèle de données ne devrait probablement pas avoir de logique que ce soit. Les services devraient probablement relier les deux ensemble et traiter de problèmes transversaux (bases de données, journalisation, etc.). Cependant, la réponse acceptée est la plus pratique.

La version plus longue, à laquelle d’autres ont fait allusion, c’est qu’il existe une équivoque sur le mot "modèle". La publication bascule entre le modèle de données et le modèle de domaine comme si elles étaient identiques, ce qui est une erreur très courante. Il peut également y avoir une légère équivoque sur le mot "service".

En termes pratiques, vous ne devriez pas avoir de service qui modifie des objets de domaine. La raison en est que votre service aura probablement une méthode pour chaque propriété de votre objet afin de changer la valeur de cette propriété. C'est un problème car si vous avez une interface pour votre objet (ou même si ce n'est pas le cas), le service ne suit plus le principe Open-Closed; Au lieu de cela, chaque fois que vous ajoutez plus de données à votre modèle (indépendamment du domaine par rapport aux données), vous finissez par devoir ajouter plus de fonctions à votre service. Il existe certaines façons de contourner le problème, mais c’est la raison la plus courante pour laquelle les applications «entreprises» ont échoué, en particulier lorsque ces entreprises pensent que «entreprise» signifie «avoir une interface pour chaque objet du système». Pouvez-vous imaginer ajouter de nouvelles méthodes à une interface, puis à deux ou trois implémentations différentes (la première dans l'application, l'implémentation fictive et la première à déboguer, celle en mémoire?), juste pour une propriété unique sur votre modèle? Cela me semble une idée terrible.

Il y a un problème plus long dans lequel je n'entrerai pas dans les détails, mais l'essentiel est le suivant: la programmation orientée objet de type Hardcore indique que personne en dehors de l'objet concerné ne doit être en mesure de modifier la valeur d'une propriété dans l'objet, ni même " voir "la valeur de la propriété dans l'objet. Cela peut être atténué en rendant les données en lecture seule. Vous pouvez toujours rencontrer des problèmes, par exemple lorsque de nombreuses personnes utilisent les données même en lecture seule et que vous devez modifier le type de ces données. Il est possible que tous les consommateurs doivent changer pour s'adapter à cela. C'est pourquoi, lorsque vous faites en sorte que les API soient utilisées par tout le monde, il vous est conseillé de ne pas disposer de propriétés / données publiques ni même protégées; c'est la raison même pour laquelle la POO a été inventée, finalement.

Je pense que la majorité des réponses ici, mis à part celle qui est marquée comme acceptée, obscurcissent tout le problème. Celui qui est marqué comme accepté est bon, mais j’ai toujours ressenti le besoin de répondre et de convenir que le point 4 est la voie à suivre en général.

Dans DDD, les services sont spécifiquement conçus pour la situation dans laquelle une opération n'appartient pas correctement à une racine d'agrégat. Vous devez faire attention ici, car souvent le besoin d'un service peut impliquer que vous n'utilisiez pas les racines correctes. Mais en supposant que ce soit le cas, un service est utilisé pour coordonner des opérations sur plusieurs racines, ou parfois pour traiter des problèmes n’impliquant pas du tout le modèle de domaine ...


1

La réponse est que cela dépend du cas d'utilisation. Mais dans la plupart des scénarios génériques, j’adhérerais à la logique d’entreprise reposant sur la couche service. L'exemple que vous avez fourni est très simple. Cependant, lorsque vous commencez à penser à des systèmes ou à des services découplés et que vous ajoutez en plus un comportement transactionnel, vous voulez vraiment que cela se produise dans la couche de service.

Les sources que vous avez citées pour la couche de service dépourvue de toute logique métier introduit une autre couche qui est la couche de gestion. Dans de nombreux scénarios, la couche service et la couche métier sont compressées en un. Cela dépend vraiment de la façon dont vous voulez concevoir votre système. Vous pouvez faire le travail en trois couches et continuer à décorer et à ajouter du bruit.

Ce que vous pouvez idéalement faire, ce sont des services de modèle qui englobent une logique métier pour travailler sur des modèles de domaine dans lesquels l’état persiste . Vous devriez essayer de découpler les services autant que possible.


0

Dans MVC, le modèle est défini en tant que logique métier. Il est incorrect de prétendre que ce soit ailleurs, sauf s'il n'utilise pas MVC. Je considère les couches de service comme similaires à un système de module. Il vous permet de regrouper un ensemble de fonctionnalités connexes dans un package intéressant. Les internes de cette couche de service auront un modèle effectuant le même travail que le vôtre.

Le modèle comprend des données d'application, des règles métier, une logique et des fonctions. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller


0

Le concept de couche de service peut être visualisé du point de vue de DDD. Aaronaught l'a mentionné dans sa réponse, je viens juste de développer un peu.

Une approche courante consiste à avoir un contrôleur spécifique à un type de client. Disons que cela pourrait être un navigateur Web, une autre application, un test fonctionnel. Les formats de demande et de réponse peuvent varier. J'utilise donc le service d'application comme un outil pour utiliser une architecture hexagonale . J'y injecte des classes d'infrastructure spécifiques à une demande concrète. Par exemple, voici à quoi pourrait ressembler mon contrôleur répondant aux requêtes du navigateur Web:

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

Si j'écris un test fonctionnel, je souhaite utiliser un faux client de paiement et je n'aurais probablement pas besoin d'une réponse HTML. Donc, mon contrôleur pourrait ressembler à ça:

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

Le service d’application est donc un environnement que j’ai configuré pour exécuter la logique applicative. C'est là que les classes de modèle sont appelées - de manière agnostique d'implémentation d'infrastructure.

Alors, répondant à vos questions de cette perspective:

Est-ce un moyen d'extraire simplement la logique du contrôleur et de la placer dans un service?

Non.

Est-il censé former un contrat entre le contrôleur et le domaine?

Eh bien, on peut appeler ça comme ça.

Devrait-il y avoir une couche entre le domaine et le service?

Nan.


Il existe une approche radicalement différente qui interdit totalement l'utilisation de tout type de service. Par exemple, David West, dans son livre Object Thinking, affirme que tout objet devrait disposer de toutes les ressources nécessaires pour faire son travail. Cette approche entraîne, par exemple, le rejet de tout ORM .


-2

Pour le compte rendu.

SRP:

  1. Model = Data, voici le setter et les getters.
  2. Logique / Services = voici les décisions.
  3. Repository / DAO = ici nous stockons ou récupérons les informations de manière permanente.

Dans ce cas, vous pouvez effectuer les étapes suivantes:

Si la dette ne nécessite pas de calcul:

userObject.Debt = 9999;

Cependant, si cela nécessite des calculs, alors:

userObject.Debt= UserService.CalculateDebt(userObject)

ou aussi

UserService.UpdateDebt(userObject)

Mais aussi, si le calcul est fait dans la couche de persistance, une telle procédure de stockage alors

UserRepository.UpdateDebt(userObject)

Dans ce cas, nous voulons extraire l'utilisateur de la base de données et mettre à jour la dette, nous devons le faire en plusieurs étapes (en fait, deux) et il n'est pas nécessaire de l'envelopper / de l'encapsuler dans la fonction d'un service.

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

Et si cela nécessite de le stocker, nous pouvons ajouter une troisième étape

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

A propos de la solution proposée

a) Nous ne devrions pas avoir peur de laisser le développeur final en écrire quelques-uns au lieu de les encapsuler dans une fonction.

b) Et à propos d'Interface, certains développeurs adorent l'interface et ils vont bien, mais dans plusieurs cas, ils ne sont pas du tout nécessaires.

c) Le but d'un service est de créer un sans attributs, principalement parce que nous pouvons utiliser des fonctions partagées / statiques. Il est également facile d'effectuer des tests unitaires.


comment cela répond-il à la question posée: quelle est la précision de la «logique métier doit être dans un service, pas dans un modèle»?
moucher

3
Quel genre de phrase est "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function."? Je ne peux que citer Lewis Black" Si ce n'était pour mon cheval, je n'aurais pas passé cette année à l'université ".
Malachi
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.