web-dev-qa-db-fra.com

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

Situation

Plus tôt ce soir, j'ai donné une réponse à une question sur StackOverflow.

La question:

L'édition d'un objet existant doit se faire dans la couche référentiel ou en service?

Par exemple, si j'ai un utilisateur endetté. Je veux changer sa dette. Dois-je le faire dans UserRepository ou dans le service par exemple BuyingService en obtenant un objet, en le modifiant et en l'enregistrant?

Ma réponse:

Vous devez laisser la responsabilité de muter un objet en 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?

Donc, cela m'a poussé à 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 par Martin Fowler sur l'anti-modèle d'un modèle de domaine anémique:

Il existe des objets, dont plusieurs portent le nom des noms dans l'espace du domaine, et ces objets sont liés aux relations et à la structure riches des vrais modèles de domaine. Le hic vient quand vous regardez 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 de getters et de setters. En effet, souvent ces modèles sont livrés avec des règles de conception qui disent que vous ne devez pas mettre de 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 en haut du modèle de domaine et utilisent le modèle de domaine pour les données.

(...) La logique qui devrait être dans un objet de domaine est la logique de domaine - validations, calculs, règles métier - comme vous voulez l'appeler.

Pour moi, cela semblait être exactement la situation: j'ai préconisé la manipulation des données d'un objet en introduisant dans cette classe des méthodes qui font exactement cela. Cependant, je me rends compte que cela devrait être donné d'une manière ou d'une autre, 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 service est davantage considérée comme un façade qui délègue le travail au modèle sous-jacent qu'une véritable couche à forte intensité de travail.

Application Layer [son nom pour Service Layer]: définit les tâches que le logiciel est censé effectuer et dirige les objets du domaine expressif pour résoudre les problèmes. Les tâches dont cette couche est responsable sont significatives pour l'entreprise ou nécessaires pour l'interaction avec les couches d'application d'autres systèmes. Cette couche reste mince. Il ne contient pas de règles métier 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. Il n'a pas d'état reflétant la situation de l'entreprise, mais il peut avoir un état qui reflète 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 dans la couche métier) aux consommateurs potentiels.

Et ici :

La couche de service doit être dépourvue de toute application ou logique métier et doit se concentrer principalement sur quelques préoccupations. Il doit encapsuler les appels Business Layer, 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 autres ressources qui parlent de la couche de service:

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

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

À un moment donné, votre application aura besoin d'une logique métier. En outre, vous souhaiterez peut-être valider l'entrée pour vous assurer qu'il n'y a rien de mal ou de non-performant demandé. Cette logique appartient à votre couche de service.

"Solution"?

En suivant les instructions de cette réponse , j'ai trouvé 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 est passé à la couche service (ce qui est une bonne chose, il y a donc un avantage à cette approche). Cependant, cela ne semble pas avoir quelque chose à voir avec ma réponse d'origine.

Je me rends compte que les modèles de conception sont des lignes directrices, pas des règles fixées dans la pierre à mettre en œuvre chaque fois que possible. Pourtant, je n'ai pas trouvé d'explication définitive de la couche de service et de la façon dont elle devrait être considérée.

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

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

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

Et, last but not least: suite au 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?
408
Jeroen Vannevel

Afin de définir quelles sont 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 Service sur un nom de classe ressemble beaucoup au très décrié Manager : Il ne vous dit presque rien sur ce que l'objet en fait fait.

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

  1. Dans une architecture en couches traditionnelle, service est littéralement synonyme de couche logique métier. C'est la couche entre l'interface utilisateur et les données. Par conséquent, toutes les règles métier vont 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 le 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 physique point final. Il s'agit essentiellement d'un ensemble d'opérations que le mainteneur souhaite fournir en tant qu'API publique. Divers guides de bonnes pratiques expliquent qu'un service opération devrait en fait être une opération de niveau métier et non CRUD, et j'ai tendance à être d'accord.

    Cependant, comme le routage tout via un service distant réel peut sérieusement nuire aux performances, il est généralement préférable pas que ces services implémentent eux-mêmes la logique métier; au lieu de cela, ils doivent envelopper un ensemble "interne" d'objets métier. Un service unique peut impliquer un ou plusieurs objets métier.

  3. Dans une architecture MVP/MVC/MVVM/MV *, services n'existe pas du tout. Ou s'ils le font, le terme est utilisé pour faire référence à tout objet générique qui peut être injecté dans un modèle de contrôleur ou 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. Beaucoup de gens, malheureusement, implémentent MVC comme ça, mais c'est considéré comme un anti-modèle ( modèle de domaine anémique ) parce que le modèle lui-même ne fait rien, c'est juste un tas de propriétés pour l'interface utilisateur.

    Certaines personnes pensent à tort que le fait de prendre une méthode de contrôleur à 100 lignes et de l'intégrer dans un service permet en quelque sorte une meilleure architecture. Ce n'est vraiment pas le cas; il ne fait qu'ajouter une autre couche d'indirection, probablement inutile. Pratiquement parlant, le contrôleur fait toujours le travail, il le fait juste à travers un objet "helper" mal nommé. Je recommande fortement la présentation de Jimmy Bogard Wicked Domain Models 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 attentif des modèles que vous exposez et des opérations réellement valides dans un contexte business.

    Par exemple, si votre base de données contient des commandes et que vous avez une colonne pour le montant total, votre application probablement ne devrait pas être autorisée à modifier réellement ce champ en une valeur arbitraire, car (a) il est historique et (b) il est censé être déterminé par ce qui est in l'ordre ainsi que peut-être d'autres données/règles sensibles au temps. La création d'un service pour gérer les commandes ne résout pas nécessairement ce problème, car le code utilisateur peut encore saisir l'objet Order réel et modifier le montant dessus. Au lieu de cela, l'ordre lui-même devrait être chargé de s'assurer qu'il ne peut être modifié que de manière sûre et cohérente.

  4. Dans DDD, les services sont destinés spécifiquement à la situation lorsque vous avez une opération qui n'appartient pas correctement à une racine agrégée . Vous devez être prudent ici, car souvent le besoin d'un service peut impliquer que vous n'avez pas utilisé les bonnes racines. Mais en supposant que vous l'ayez fait, un service est utilisé pour coordonner les opérations sur plusieurs racines, ou parfois pour gérer des problèmes qui n'impliquent 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 scripts de transaction . Lorsque vous travaillez sur des applications volumineuses, vous risquez éventuellement de rencontrer des instances où il est beaucoup plus facile d'accomplir quelque chose avec une procédure T-SQL ou PL/SQL que de s'embêter avec le modèle de domaine. C'est OK, et il appartient à un service.

    Il s'agit d'un changement radical par rapport à la définition de l'architecture en couches des services. Une couche de service encapsule les objets de domaine; un service DDD encapsule tout ce qui n'est 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 pour 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 rien d'autre n'est autorisé à toucher ces données - pas même juste lire .

    Par nécessité, les services sont en fait une proposition de bout en bout dans une architecture SOA. Ce qui signifie qu'un service n'est pas tant un composant spécifique qu'un ensemble complet - pile, et votre application entière (ou toute votre entreprise) en est un ensemble 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 métier et sa propre interface utilisateur. Ils n'ont pas besoin de s'orchestrer les uns avec les autres car ils sont censés être alignés sur les affaires - et, comme l'entreprise elle-même, chaque service a son propre ensemble de responsabilités et fonctionne plus ou moins indépendamment des autres.

    Ainsi, selon la définition SOA, chaque élément de la logique métier n'importe où est contenu dans le service, mais là encore, tout le système . Les services dans un SOA peut avoir composants, et ils peuvent avoir endpoints, mais il est assez dangereux d'appeler n'importe quel morceau de code a service car il entre en conflit avec ce que le "S" original est censé signifier.

    Étant donné que SOA est généralement très intéressé par la messagerie, les opérations que vous pourriez avoir empaquetées dans un service avant sont généralement encapsulées dans gestionnaires, mais la multiplicité est différente. Chaque gestionnaire gère un type de message, un opération. C'est une interprétation stricte du principe de responsabilité unique , mais permet une grande maintenabilité car chaque opération possible est dans sa propre classe. Vous n'avez donc pas vraiment besoin de logique métier centralisée, car les commandes représentent les opérations métier plutôt que les opérations techniques.

En fin de compte, dans toute architecture que vous choisirez, il y aura un composant ou une couche qui aura la majeure partie de la logique métier. Après tout, si la logique métier est dispersée partout, vous n'avez que du code spaghetti. Mais que vous appeliez ou non ce composant a 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.

381
Aaronaught

Quant à 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 doit être dans le Service et non le Modèle, c'est comme dire: "Le passager doit s'asseoir sur le siège, pas dans la voiture".

Là encore, le terme "modèle" est un terme surchargé. Peut-être que vous ne vouliez pas dire modèle MVC mais vous vouliez dire modèle au sens de l'objet de transfert de données (DTO). AKA une entité. C'est de cela que parle Martin Fowler.

Selon moi, Martin Fowler parle des choses dans un monde idéal. Dans le monde réel d'Hibernate et de JPA (en Java land), les DTO sont une abstraction super fuite. J'adorerais 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 qui est très difficile à comprendre et empêche constamment vos efforts. Pour résumer mon opinion: Martin Fowler recommande la bonne façon, mais les ORM vous empêchent de le faire.

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 vos DTO libres de toute 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 sortez de cette couche, vous devenez aussi OO comme le préconise Martin Fowler.

L'avantage de cette séparation est qu'elle éloigne la couche de persistance. Vous pouvez passer de JPA à JDBC (ou vice versa) et aucune logique métier ne devrait changer. Cela dépend juste des DTO, peu importe comment ces DTO sont remplis.

Pour légèrement changer de sujet, 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, dès 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 comme vous le souhaitez d'une manière orientée objet.

Quant à " un service ", Bob Martin serait contre avoir une classe nommée FooBarService. Ce n'est pas orienté objet. Que fait un service? N'importe quoi lié à FooBars. Il peut également être appelé FooBarUtils. Je pense qu'il préconiserait une couche de service (un meilleur nom serait la couche de logique métier) mais chaque classe de cette couche aurait un nom significatif.

41
Daniel Kaplan

Je travaille actuellement sur le projet greenfield et nous avons dû prendre peu de décisions architecturales 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 via 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 testerons en profondeur.
  • Couche de service. Cette couche communique avec la couche de domaine et répond aux demandes des clients. Dans notre cas, la couche service est assez simple - elle relaie les demandes à la couche 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. Parle à la couche service via SOAP.

On se retrouve avec ce qui suit:

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

Nous pouvons remplacer le client, le service ou la couche de données par une quantité raisonnable de travail. Si votre logique de domaine vivait dans le service et que vous avez décidé de remplacer ou même de supprimer votre couche de service, vous devez déplacer toute la logique métier ailleurs. Une telle exigence est rare, mais elle pourrait se produire.

Cela dit, je pense que c'est assez proche de ce que Martin Fowler voulait dire en disant

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

Le diagramme ci-dessous illustre assez bien cela:

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

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

26
CodeART

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

La suppression de la logique métier, qui est le domaine, du modèle de domaine est essentiellement une rupture de la conception orientée objet.

Passons en revue le concept orienté objet le plus basique: un objet encapsule des données et des opérations. Par exemple, la fermeture d'un compte est une opération qu'un objet 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 procédural, et c'est à cela que Martin Fowler fait référence lorsqu'il parle d'un modèle de domaine anémique.

Si vous avez une couche de service fermer le compte, plutôt que d'avoir l'objet de compte se fermer lui-même, vous n'avez pas un véritable objet de compte. Votre compte "objet" n'est qu'une structure de données. Ce que vous vous retrouvez, comme le suggère Martin Fowler, c'est un tas de sacs avec des getters et des setters.

C'est l'une de ces choses qui dépend vraiment du cas d'utilisation. Le point global d'une couche de service est de consolider la logique métier ensemble. Cela signifie que plusieurs contrôleurs peuvent appeler le même UserService.MakeHimPay () sans se soucier réellement 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 il peut s'agir d'une logique complexe traitant d'autres services (par exemple, appeler des services tiers, appeler une logique de validation ou même simplement enregistrer quelque chose dans la base de données. )

Cela ne signifie pas que vous devez supprimer TOUTE la logique des objets du domaine. Parfois, il est plus logique qu'une méthode sur l'objet domaine effectue des calculs sur elle-même. Dans votre dernier exemple, le service est une couche redondante sur l'objet référentiel/domaine. Il fournit un tampon Nice contre les changements d'exigences, mais ce n'est vraiment pas nécessaire. Si vous pensez avoir besoin d'un service, essayez de faire faire la simple logique "modifier 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 "calculer cette valeur à partir des champs" plutôt que d'exposer tous les champs via des getters/setters.

9
firelore

La façon la plus simple d'illustrer pourquoi les programmeurs hésitent à mettre la logique de domaine dans les objets de domaine est qu'ils sont généralement confrontés à une situation "où dois-je 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, il y a un écran ou un ViewModel ou un contrôleur qui doit faire la validation avant il valide réellement la modification de l'objet de domaine, car il doit informer l'utilisateur avant ou lorsqu'il clique sur le bouton Enregistrer que ils ne peuvent pas faire ça, et pourquoi. Tester une exception lorsque vous appelez le setter est un vilain hack car vous devriez vraiment avoir fait toute la validation avant même de commencer la transaction.

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

Si vous ne pas faites cela et que vous souhaitez toujours réutiliser la logique de validation, vous finissez par la mettre dans des 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 garderait 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.

8
Scott Whitlock

Comment implémenteriez-vous votre logique métier dans la couche service? Lorsque vous effectuez un paiement 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, ajouter à 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 également envelopper toute l'opération dans une transaction. Faire de même dans un modèle de domaine agrégé est beaucoup plus problématique.

4
Mr Cochese

La version tl; dr:
Mes expériences et opinions indiquent que tous les objets qui ont une logique métier doivent faire partie du modèle de domaine. Le modèle de données ne devrait probablement avoir aucune logique. Les services devraient probablement lier les deux et répondre aux préoccupations transversales (bases de données, journalisation, etc.). Cependant, la réponse acceptée est la plus pratique.

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

Concrètement, vous ne devriez pas avoir de service qui modifie les 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 alors, 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 ouvert-fermé; au lieu de cela, chaque fois que vous ajoutez plus de données à votre modèle (quel que soit le domaine vs les données), vous finissez par devoir ajouter plus de fonctions à votre service. Il existe certains moyens de contourner ce problème, mais c'est la raison la plus courante pour laquelle j'ai vu des applications "d'entreprise" échouer, en particulier lorsque ces organisations 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 (celle intégrée à l'application, l'implémentation fictive et celle de débogage, celle en mémoire?), Juste pour une seule propriété sur votre modèle? Cela me semble une terrible idée.

Il y a un problème plus long ici que je ne vais pas aborder, mais l'essentiel est le suivant: la programmation orientée objet hardcore dit que personne en dehors de l'objet pertinent ne devrait pouvoir 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 comme quand beaucoup de gens utilisent les données même en lecture seule et vous devez changer le type de ces données. Il est possible que tous les consommateurs devront changer pour s'adapter à cela. C'est pourquoi, lorsque vous créez des API destinées à être utilisées par n'importe qui et par tout le monde, il est conseillé de ne pas avoir de propriétés/données publiques ou même protégées; c'est la raison même OOP a été inventé, finalement.

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

Dans DDD, les services sont destinés spécifiquement à la situation où vous avez une opération qui n'appartient pas correctement à une racine agrégée. Vous devez être prudent ici, car souvent le besoin d'un service peut impliquer que vous n'avez pas utilisé les bonnes racines. Mais en supposant que vous l'ayez fait, un service est utilisé pour coordonner les opérations sur plusieurs racines, ou parfois pour gérer des problèmes qui n'impliquent pas du tout le modèle de domaine ...

2
WolfgangSenff

Le concept de couche Service peut être vu du point de vue DDD. Aaronaught l'a mentionné dans sa réponse, je m'arrête là-dessus.

L'approche courante consiste à avoir un contrôleur spécifique à un type de client. Dites, ce pourrait être un navigateur Web, ce pourrait être une autre application, ce pourrait être 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'infrastructures spécifiques à une demande concrète. Par exemple, voici à quoi pourrait ressembler mon contrôleur servant les demandes de 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 veux 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 métier. C'est là que les classes de modèle sont appelées - indépendamment d'une implémentation d'infrastructure.

Donc, répondant à vos questions dans cette perspective:

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

Non.

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

Eh bien, on peut l'appeler ainsi.

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

Nan.


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

1
Zapadlo

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 métier reposant sur la couche de service. L'exemple que vous avez fourni est très simple. Cependant, une fois que vous commencez à penser à des systèmes ou services découplés et à ajouter un comportement transactionnel par-dessus, vous voulez vraiment que cela se produise dans le cadre de la couche de service.

Les sources que vous avez citées pour la couche de service dépourvue de toute logique métier introduisent une autre couche qui est la couche métier. Dans de nombreux scénarios, la couche service et la couche métier sont compressées en une seule. Cela dépend vraiment de la façon dont vous souhaitez 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 est des services de modèles qui englobent la logique métier pour travailler sur des modèles de domaine afin de conserver l'état . Vous devez essayer de dissocier les services autant que possible.

1
sunny

Dans MVC, le modèle est défini comme la logique métier. Prétendre qu'il devrait être ailleurs est incorrect, sauf s'il n'utilise pas MVC. Je considère les couches de service comme similaires à un système de modules. Il vous permet de regrouper un ensemble de fonctionnalités connexes dans un package Nice. Les internes de cette couche de service auraient un modèle faisant 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
stonemetal