Architecture / superposition de projet .NET MVC

11

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 essentiellement 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.? Quant aux entités commerciales (par exemple les voitures, les fourgonnettes, les motos), je les mettrais dans la couche de données avec ma DataContextclasse.

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. Alors 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 exemplevar 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? À ce sujet, comme mes classes concrètes d'accès aux données dbcontext sont 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

Michael Harper
la source

Réponses:

26

Vous avez beaucoup de pièces mobiles dans votre question, touchant à beaucoup de 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

Tout d'abord, il est préférable de ne 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 préoccupations de logique métier de vos préoccupations de présentation . 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, 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. Généralement, cela se présente 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, 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 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 faire 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 effectuer un travail, etc. Je ne demanderais pas à mon contrôleur de faire tout cela, mais je déléguerais tout cela à un service dans le BLL.

En d'autres termes, je n'injecterais pas de référentiel et de MPGCalculator dans le contrôleur, cela donne trop de responsabilité au contrôleur (il gère déjà tous les trucs de contrôleur que j'ai mentionnés ci-dessus). Au lieu de cela, j'aurais un service dans le BLL pour 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 la stratégie de persistance des données ni la stratégie de présentation.

Eric King
la source
Salut Eric, excellente réponse - en ce qui concerne les référentiels, je suppose que les classes concrètes vivraient dans la couche d'accès aux données et le 'ICarRepository' etc. dans la couche business / service? Ensuite, je pourrais injecter des services dans mon contrôleur qui peuvent contenir 1 ou plusieurs référentiels en fonction des besoins?
Michael Harper
@MichaelHarper Oui, cela semble être une excellente façon de procéder.
Eric King
1
Bien que l'authentification soit une préoccupation du contrôleur (différentes interfaces utilisateur s'authentifient différemment), je dirais que l'autorisation est une logique métier et appartient à la couche métier. Êtes-vous d'accord?
Tom
1
@tom Oui, vous avez un bon point. Je pensais à une simple autorisation car l' utilisateur a accès à cette route , mais il peut y avoir beaucoup plus que cela. La partie "beaucoup plus" appartient à la couche métier.
Eric King
1
@HunterNelson Si vous mappez dans un modèle de vue, le mappage doit se produire là où se trouve la vue, dans la couche de présentation. Cela n'aurait aucun sens ailleurs.
Eric King du
0

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 choses 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 autre base de données nécessitant un .edmx différent fichier. Le modèle de référentiel fournit une abstraction pour prendre en charge cela.

wpenberthy
la source
Merci pour la réponse William 😊 Je considérerais mes objets métier / logique et entités de domaine comme des `` modèles '' que le contrôleur utilise pour traiter les actions des utilisateurs et les modèles de vue comme des modèles spécifiques pouvant contenir des groupes de modèles, etc.
Michael Harper