est-ce une mauvaise pratique que le référentiel d'appels du contrôleur au lieu du service?

44

est-ce une mauvaise pratique que le référentiel d'appels du contrôleur au lieu du service?

pour expliquer plus:

Je me rends compte que dans une bonne conception, les contrôleurs appellent un service et un référentiel d’utilisation des services.

mais parfois, dans le contrôleur, je n’ai / n’ai besoin de aucune logique et je n’ai besoin que de récupérer la base de données et de la transmettre à la vue.

et je peux le faire en appelant simplement le référentiel - pas besoin d'appeler le service - est-ce une mauvaise pratique?

MohsenJsh
la source
Comment appelez-vous le service? Via une interface REST?
Robert Harvey
2
J'utilise cette approche de conception moi-même assez couramment. Mon contrôleur (ou une classe de compositeur sous-jacente) demandera des données à ou enverra des données au référentiel, puis les transmettra à toutes les classes de service devant effectuer un traitement. Aucune raison de combiner des classes de traitement de données avec des classes d'extraction / gestion de données, ce sont des préoccupations différentes bien que je sache que l'approche typique est de le faire de cette façon.
Jimmy Hoffa
3
Meh S'il s'agit d'une petite application et que vous essayez simplement d'obtenir des données d'une base de données, une couche de service est une perte de temps, sauf si cette couche de service fait partie d'une API publique telle qu'une interface REST. "Le lait est-il bon ou mauvais pour vous?" Cela dépend si vous êtes intolérant au lactose.
Robert Harvey
4
Il n’existe pas de règle absolue selon laquelle vous devriez avoir une structure Contrôleur -> Service -> Référentiel sur Contrôleur -> Référentiel. Choisissez le bon motif pour la bonne application. Ce que je dirais, c'est que vous devriez rendre votre demande cohérente.
NikolaiDante
Vous pourriez peut-être trouver un service générique qui ne fera que transmettre votre demande au référentiel puis le renverra. Cela pourrait être utile pour garder une interface uniforme et serait simple si à l'avenir vous devez ajouter un service réel pour faire quelque chose avant d'appeler le référentiel.
Henrique Barcelos

Réponses:

32

Non, pensez de cette façon: un référentiel est un service (aussi).

Si les entités que vous récupérez via le référentiel gèrent la majeure partie de la logique métier, aucun autre service n'est nécessaire. Il suffit de disposer du référentiel.

Même si vous avez des services que vous devez utiliser pour manipuler vos entités. Prenez d'abord l'entité dans le référentiel, puis transmettez-la au dit service. Il est très pratique de pouvoir lancer un HTTP 404 avant même d’essayer.

Aussi, pour les scénarios de lecture, il est courant que vous ayez simplement besoin de l'entité pour la projeter sur un DTO / ViewModel. Avoir une couche de service entre les deux entraîne souvent de nombreuses méthodes de pass-through, ce qui est plutôt moche.

Joppe
la source
2
Bien dit! Ma préférence est d'appeler des référentiels, et uniquement dans les cas où un référentiel ne suffit pas (c.-à-d. Que deux entités doivent être modifiées à l'aide de référentiels différents), je crée un service responsable de cette opération et l'appelle depuis le contrôleur.
Zygimantas
J'avais remarqué un code assez compliqué juste pour justifier l'utilisation d'un service. Absurde, le moins ...
Gi1ber7
Mon référentiel renvoie donc une liste des "objets métier" que je dois convertir en "objets xml". Cette raison est-elle suffisante pour disposer d'une couche de service? J'appelle une méthode sur chaque objet pour le convertir en un autre type et l'ajouter à une nouvelle liste.
bot_bot
L’accès direct à la DAO est dangereux dans les contrôleurs, il peut vous rendre vulnérable aux injections SQL et donne accès à des actions dangereuses comme, par exemple, deleteAll.
Anirudh
4

Ce n’est pas une mauvaise pratique pour un contrôleur d’appeler directement un référentiel. Un "service" n'est qu'un autre outil, utilisez-le lorsque cela vous semble utile.

NikolaiDante a commenté:

... Choisissez le bon motif pour la bonne application. Ce que je dirais, c'est que vous devriez rendre votre demande cohérente.

Je ne pense pas que la cohérence soit l'aspect le plus important. Une classe "service" est destinée à encapsuler une logique de niveau supérieur afin que le contrôleur n'ait pas besoin de l'implémenter. S'il n'y a pas de "logique de niveau supérieur" requise pour une opération donnée, il suffit d'aller directement au référentiel.

Pour promouvoir une séparation et une testabilité correctes, le référentiel doit être une dépendance que vous injectez dans le service via un constructeur:

IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);

service.DoSomething(...);

Si la recherche d'enregistrements dans la base de données nécessite une sorte de requête paramétrée, une classe de service peut être un bon endroit pour intégrer votre modèle de vue et créer une requête qui est ensuite exécutée par le référentiel.

De même, si vous avez un modèle de vue complexe pour un formulaire, une classe de service peut encapsuler la logique de création, de mise à jour et de suppression d'enregistrements en appelant des méthodes sur vos modèles de domaine / entités, puis en les conservant à l'aide d'un référentiel.

Si votre contrôleur doit obtenir un enregistrement par son ID, déléguer à un objet de service à cet effet revient à frapper une punaise avec une masse, c'est bien plus que ce dont vous avez besoin.

J'ai constaté que le contrôleur est le mieux placé pour gérer la transaction, ou un objet Unit of Work . Le contrôleur ou l'objet Unité de travail déléguerait ensuite des objets de service pour des opérations complexes ou irait directement au référentiel pour des opérations simples (comme la recherche d'un enregistrement par identifiant).

public class ShoppingCartsController : Controller
{
    [HttpPost]
    public ActionResult Edit(int id, ShoppingCartForm model)
    {
        // Controller initiates a database session and transaction
        using (IStoreContext store = new StoreContext())
        {
            // Controller goes directly to a repository to find a record by Id
            ShoppingCart cart = store.ShoppingCarts.Find(id);

            // Controller creates the service, and passes the repository and/or
            // the current transaction
            ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);

            if (cart == null)
                return HttpNotFound();

            if (ModelState.IsValid)
            {
                // Controller delegates to a service object to manipulate the
                // Domain Model (ShoppingCart)
                service.UpdateShoppingCart(model, cart);

                // Controller decides to commit changes
                store.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            else
            {
                return View(model);
            }
        }
    }
}

Je pense qu’une combinaison de services et de travailler directement avec des dépôts est parfaitement acceptable. Vous pouvez en outre encapsuler la transaction dans un objet Unit of Work si vous en ressentez le besoin.

La répartition des responsabilités est la suivante:

  • Le contrôleur contrôle le flux de l'application
    • Renvoie "404 non trouvé" si le panier n'est pas dans la base de données.
    • Re-rend le formulaire avec des messages de validation si la validation échoue
    • Enregistre le panier si tout est vérifié
  • Le contrôleur délègue à une classe de service l'exécution de la logique applicative sur vos modèles de domaine (ou entités). Les objets de service ne doivent pas implémenter de logique métier! Ils exécutent la logique métier.
  • Les contrôleurs peuvent déléguer directement aux référentiels pour des opérations simples
  • Les objets de service prennent des données dans le modèle de vue et les délèguent à des modèles de domaine pour exécuter une logique métier (par exemple, les méthodes d'appels de service appellent les modèles de domaine avant d'appeler des méthodes sur le référentiel).
  • Les objets de service sont délégués aux référentiels pour la persistance des données
  • Les contrôleurs doivent soit:
    1. Gérer la durée de vie d'une transaction, ou
    2. Créer un objet Unité de travail pour gérer la durée de vie d'une transaction
Greg Burghardt
la source
1
-1 pour mettre DbContext dans un contrôleur plutôt que dans un dépôt. Le référentiel est conçu pour gérer les fournisseurs de données afin que personne d'autre ne le soit en cas de changement de fournisseur de données (de MySQL aux fichiers JSON à plat, par exemple, au même endroit)
Jimmy Hoffa Le
@ JimmyHoffa: En fait, je regarde en arrière le code que j'ai écrit et, pour être honnête, je crée un objet "contexte" pour mes référentiels, pas nécessairement la base de données. Je pense que DbContextc'est une mauvaise réputation dans ce cas. Je vais changer ça. J'utilise NHibernate, et les référentiels (ou le contexte si cela est utile) gèrent la fin de la base de données, de sorte que la modification des mécanismes de persistance ne nécessite pas de modifications de code en dehors du contexte.
Greg Burghardt
vous semblez confondre contrôleur avec référentiel par l'apparence de votre code ... Je veux dire, votre "contexte" est faux et ne devrait absolument pas exister dans le contrôleur.
Jimmy Hoffa
6
Je n'ai pas à répondre et je ne suis pas sûr que ce soit une bonne question pour commencer, mais j'ai voté contre, car je pense que votre approche est mauvaise. Pas de rancune, je décourage juste les contextes appartenant aux contrôleurs. OMI, un contrôleur ne devrait pas commencer et valider des transactions comme celle-ci. C'est le travail de nombreux autres endroits. Je préfère que les contrôleurs délèguent tout ce qui n'est pas simplement en train de remplir la demande HTTP.
Jimmy Hoffa
1
Un référentiel, il est généralement responsable de toutes les informations de contexte de données pour s'assurer que rien d'autre n'a besoin de rien savoir sur les problèmes de données autres que ce que le domaine lui-même doit connaître
Jimmy Hoffa
1

Cela dépend de votre architecture. J'utilise Spring et la transactionnalité est toujours gérée par les services.

Si vous appelez directement des référentiels pour des opérations d'écriture (ou des services simples sans logique qui délèguent simplement au référentiel), vous utilisez probablement plusieurs transactions de base de données pour une opération devant être exécutée simultanément. Cela conduira à des données incohérentes dans votre base de données. En règle générale, les opérations de base de données devraient fonctionner ou échouer, mais des opérations à moitié fonctionnant sont la cause de maux de tête.

Pour cette raison, je pense qu’appeler des référentiels directement à partir de contrôleurs, ou utiliser de simples services de délégation, est une mauvaise pratique. Vous commencez à le faire uniquement pour la lecture, et très bientôt, vous, ou l'un de vos amis, le ferez pour les opérations d'écriture.

Rober2D2
la source