web-dev-qa-db-fra.com

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

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

pour expliquer plus:

Je pense que dans une bonne conception, les contrôleurs appellent le service et le référentiel d'utilisation des services.

mais parfois dans le contrôleur, je n'ai/n'ai pas besoin de logique et j'ai juste besoin d'aller chercher dans db et de le passer pour le voir.

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

53
mohsenJsh

Non, pensez-y 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, il n'y a pas besoin d'autres services. Le simple fait d'avoir le dépôt suffit.

Même si vous disposez de certains services que vous devez passer pour manipuler vos entités. Saisissez d'abord l'entité dans le référentiel, puis transmettez-la audit service. Pouvoir lancer un HTTP 404 avant même d'essayer est très pratique.

De plus, pour les scénarios de lecture, il est courant que vous ayez juste besoin de l'entité pour le projeter sur un DTO/ViewModel. Le fait d'avoir une couche de service entre les deux entraîne souvent de nombreuses méthodes de transmission, ce qui est plutôt moche.

39
Joppe

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, alors utilisez-le là où cela a du sens.

NikolaiDante a commenté:

... Choisissez le bon modèle pour la bonne application. Je dirais que vous devez rendre votre candidature 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, allez directement dans le référentiel.

Pour promouvoir une bonne séparation des préoccupations et la testabilité, 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 prendre dans 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/entités de domaine, puis en les conservant à l'aide d'un référentiel.

Dans la direction opposée, si votre contrôleur a besoin d'obtenir un enregistrement par son identifiant, déléguer à un objet de service pour cela revient à frapper une punaise avec un marteau - c'est bien plus que ce dont vous avez besoin.

J'ai trouvé 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 Of Work déléguerait alors des objets de service pour des opérations complexes, ou irait directement au référentiel pour des opérations simples (comme trouver un enregistrement par Id).

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'un mélange de services et de travailler directement avec des référentiels est parfaitement acceptable. Vous pouvez encapsuler davantage la transaction dans un objet Unité de travail si vous en ressentez le besoin.

La répartition des responsabilités se présente comme suit:

  • 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
    • Renvoie le formulaire avec des messages de validation si la validation échoue
    • Enregistre le panier si tout se passe bien
  • Le contrôleur délègue à une classe de service pour exécuter la logique métier sur vos modèles de domaine (ou entités). Les objets de service ne doivent pas implémenter la logique métier ! Ils exécutent 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 aux modèles de domaine pour exécuter la logique métier (par exemple, l'objet de service appelle des méthodes sur les modèles de domaine avant d'appeler des méthodes sur le référentiel)
  • Délégué des objets de service aux référentiels pour la persistance des données
  • Les contrôleurs doivent:
    1. Gérez la durée de vie d'une transaction, ou
    2. Créer un objet Unit Of Work pour gérer la durée de vie d'une transaction
6
Greg Burghardt

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

Si vous appelez des référentiels directement pour des opérations d'écriture (ou des services simples sans logique qui se contentent de déléguer au référentiel), vous utilisez probablement plusieurs transactions de base de données pour une opération qui doit être effectuée en une seule. 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 doivent fonctionner ou échouer, mais les opérations à mi-travail sont la cause de maux de tête.

Pour cette raison, je pense que l'appel de référentiels directement à partir de contrôleurs, ou l'utilisation de services de délégation simples, est une mauvaise pratique. Vous commencez à le faire juste pour la lecture, et très bientôt vous, ou un de vos camarades, commencerez à le faire pour les opérations d'écriture.

2
Rober2D2

Oui, c'est une mauvaise pratique. Le contrôleur ne doit être utilisé que pour contrôler le flux de votre application, obtenir des données des applications clientes, appeler le service et transmettre des données à la vue (JSON/HTML/XML/etc). Le référentiel/DAO doit être utilisé pour récupérer/conserver les données sans connaître la logique métier.

Envisagez les scénarios suivants:

  • D'une manière ou d'une autre, vous devez ajouter/modifier l'accès aux rôles pour les données. Vous devez ajouter la logique dans tous les contrôleurs qui appellent le référentiel, ou vous devez le mettre dans le référentiel (cassera la logique des autres services qui appellent ce référentiel). Si vous appelez via un service, il vous suffit de modifier le service, ou simplement d'ajouter un autre service, pas d'inquiétude, les autres services qui appellent ce repo sont cassés.
  • Supposons que vous ayez deux clients de votre application (site Web et REST API), à l'avenir, si vous devez modifier l'accès aux rôles ou la logique métier, vous devez modifier à la fois le site Web et REST Contrôleurs API.
0
Izhari Ishak Aksa