Lors de la planification de l'architecture d'une application Web MVC à moyenne et grande échelle, comment implémentez-vous les couches pour qu'elles soient aussi découplées que possible et faciles à tester? (suivez les meilleures pratiques) Supposons que j'utilise d'abord le code comme accès aux données.
J'ai du mal à définir la "logique métier" et comment elle doit interagir avec la couche de données. En prenant une application de vente de véhicules comme exemple, la logique commerciale serait-elle des classes qui effectuaient des tâches telles que le calcul de la fourchette de taxe pour des véhicules donnés, la comparaison de statistiques de mile par gallon, etc. En ce qui concerne les entités commerciales (par exemple, voitures, fourgonnettes, motos), je les mettrais dans la couche de données avec ma classe DataContext
.
De plus, qu'est-ce qui constituerait une logique d'application par opposition à une entreprise - je suppose des choses comme des validations d'entrée de session/utilisateur?
Ainsi, par exemple, un contrôleur de voiture peut retourner un résultat d'action/vue qui répertorie les dix meilleures voitures filtrées par type et meilleur mpg. Disons que j'ai un ICarRepository
'carRepo' injecté dans mon contrôleur (en utilisant le modèle de référentiel/DI), je filtre mes voitures à partir d'un paramètre de méthode d'action, par exemple var cars = carRepo.getCarsByType("hatchback");
J'ai donc gardé les connaissances d'accès aux données hors de mon contrôleur en utilisant un référentiel, maintenant pour garder la logique métier hors du contrôleur en utilisant un modèle de domaine - var result = new MpgCalculator (cars); - Disons que j'ai besoin de la classe de calculatrice car elle doit effectuer une logique supplémentaire pour calculer la meilleure efficacité énergétique, plus que simplement charger/filtrer des entités à partir de la base de données. Alors maintenant, j'ai un ensemble de données à afficher pour ma vue qui utilise un référentiel pour récupérer à partir de la couche d'accès aux données, et un objet spécifique au domaine pour traiter et effectuer des tâches liées à l'entreprise sur ces données.
Suis-je en train de faire des erreurs ici? avons-nous encore besoin d'utiliser le modèle de référentiel ou puis-je simplement coder contre une interface pour découpler l'ORM et tester? Sur ce sujet, comme mes classes concrètes d'accès aux données dbcontext se trouvent dans la couche de données, les définitions d'interface doivent-elles aller dans la couche domaine/entreprise, ce qui signifie que si la technologie d'accès aux données est modifiée, mes autres couches ne sont pas affectées?
D'après ce que j'ai étudié jusqu'à présent, ma structure ressemble à ceci:
Application Internet MVC -> Le projet Internet standard - les modèles ici sont ViewModels
Couche Domaine/Entreprise -> classes/modèles spécifiques à l'entreprise que les contrôleurs peuvent utiliser pour traiter les entités de domaine de la couche de données avant de passer aux vues pertinentes
Abstraction du référentiel nécessaire? -> J'entends beaucoup de débats à ce sujet, en particulier lors de l'utilisation d'un ORM
Couche de données -> Classes d'entité (voiture, fourgonnette, moto), DbContext - Couche de technologie d'accès aux données concrètes
Vous avez beaucoup de pièces mobiles dans votre question, touchant à de nombreux concepts, mais voici mes conseils de base en ce qui concerne la façon de penser à une application MVC de moyenne à grande échelle:
Présentation <---> Business Logic <---> Accès aux données
Premièrement, il vaut mieux pas considérer l'application comme "une application MVC". C'est une application qui utilise le modèle MVC comme composant de présentation. En y réfléchissant de cette façon, vous pourrez séparer vos logique métier préoccupations de vos présentation préoccupations. Peut-être que les petites applications peuvent tout empiler pour accéder à la base de données dans la structure MVC, mais cela deviendra rapidement intenable pour une application de taille moyenne à grande.
MVC (Présentation)
Dans votre application, le composant ASP.NET MVC doit traiter la transformation des données commerciales à des fins d'affichage (modèles), l'affichage de l'interface utilisateur (vues) et les problèmes de communication tels que le routage, l'authentification, l'autorisation, la validation des demandes, la gestion des réponses et la comme (Contrôleurs). Si vous avez du code qui fait autre chose, alors il n'appartient pas au composant MVC.
Référentiel/ORM (accès aux données)
Toujours dans votre application, la couche d'accès aux données doit être concernée par la récupération et le stockage des données persistantes. Cela se présente généralement sous la forme d'une base de données relationnelle, mais il existe de nombreuses autres façons de conserver les données. Si vous avez du code qui ne lit pas ou ne stocke pas de données persistantes, alors il n'appartient pas à la couche de données. J'ai partagé mes réflexions sur la discussion ORM/Repository précédemment sur SO, mais pour récapituler, je ne considère pas qu'un ORM soit la même chose qu'un référentiel, pour plusieurs raisons.
Logique d'entreprise
Alors maintenant, vous avez votre couche de présentation (MVC), et votre couche de données (référentiel ou ORM) ... Tout le reste est votre couche de logique métier (BLL). Tout votre code qui décide des données à récupérer, effectue des calculs compliqués ou prend des décisions commerciales doit être ici. J'organise généralement ma logique métier sous forme de "services", auxquels ma couche présentation peut faire appel pour effectuer le travail demandé. Tous mes modèles de domaine existent ici.
Votre approche
C'est là que votre approche se décompose un peu pour moi. Vous décrivez votre contrôleur MVC comme l'endroit où vous obtiendrez les données du référentiel, et vous appelez le MPGCalculator pour faire du travail, etc. Je ne demanderais pas à mon contrôleur de faire tout cela, mais à la place déléguerait tout cela à un service de la BLL.
En d'autres termes, je n'injecterais pas de référentiel et de MPGCalculator dans le contrôleur, ce qui donne trop de responsabilité au contrôleur (il gère déjà tous les trucs contrôleur que j'ai mentionnés ci-dessus). Au lieu de cela, j'aurais un service dans le BLL gérer tout cela et transmettre les résultats au contrôleur. Le contrôleur peut ensuite transformer les résultats en modèle correct et les transmettre à la vue correcte. Le contrôleur n'a pas de logique métier, et les seules choses injectées dans le contrôleur seraient les services BLL appropriés.
En procédant de cette façon, votre logique métier (par exemple, étant donné un ensemble de véhicules, calculez le MPG et triez du mieux au pire) est indépendante des problèmes de présentation et de persistance. Ce sera généralement dans une bibliothèque qui ne connaît pas ou ne se soucie pas de la stratégie de persistance des données ni de la stratégie de présentation.
Il semble que tout soit correct pour votre structure. La seule chose dont je ne suis pas sûr, c'est que vous mentionnez que les modèles dans MVC sont des "ViewModels" et que vos contrôleurs parlent à la couche domaine. Je pense que cela a du sens si votre modèle par défaut consiste à utiliser le contrôleur pour accéder à la couche de domaine, puis à utiliser vos "ViewModels" comme compilations d'informations plus spécifiques à la vue à partir de plusieurs entités de domaine, comme il est logique pour cette vue particulière. Si c'est ce que vous faites, vous êtes probablement d'accord.
Il existe une école de pensée selon laquelle vous devriez avoir une abstraction complète de votre couche de domaine dans votre application MVC si vous en avez. Personnellement, l'idée de faire cela dans une application d'entreprise me cause de graves douleurs mentales.
Je préfère utiliser le modèle de référentiel pour gérer l'accès à la couche de données car il améliore la testabilité et la flexibilité. L'interface utilisateur et la base de données sont les deux éléments qui ont tendance à apporter les changements les plus drastiques. Imaginez si certaines des informations que vous extrayez directement de la base de données sont modifiées de sorte qu'elles doivent être récupérées à partir d'un appel de service plutôt que d'un appel à la base de données, ou si certaines informations sont déplacées vers une base de données différente nécessitant un .edmx différent fichier. Le modèle de référentiel fournit une abstraction pour prendre en charge cela.