Besoin d'aide pour appliquer les principes SOLID

asp.net-mvc dapper entity-framework n-tier-architecture oop

Question

Vraiment impressionné par le cours plurifonctionnel de Juile Lerman sur "EF in Enterprise" et a décidé de créer mon application de démonstration.

J'utilise VS 2012 et les dernières versions de EF, SQL Server et MVC. Je construis une application de démonstration qui applique les principes SOLID. Je le fais pour mieux comprendre comment implémenter les tests DI et unitaires.

J'ai utilisé l'approche DB pour cette application de démonstration. Il ne contient qu'une seule table nommée UserDetails et voici comment il se présente dans SQL Server. Je vais utiliser cette table pour les opérations CRUD. entrer la description de l'image ici

Voici comment j'ai réparti ma candidature:

1. WESModel Solution: Cette couche contient mon fichier Model1.edmx et la classe de contexte ci-dessous.

namespace WESModel
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using WESDomain;

    public partial class WESMVCEntities : DbContext
    {
        public WESMVCEntities()
            : base("name=WESMVCEntities")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

        public DbSet<UserDetail> UserDetails { get; set; }
    }
}

2. WESDomain Solution: Cette couche contient mes classes de domaine (ou classes POCO). Ces classes POCO ont été générées automatiquement dans ma couche WESModel. Je les ai déplacés à cette couche. Voici à quoi ressemble la classe POCO.

namespace WESDomain
{
    using System;
    using System.Collections.Generic;

    public partial class UserDetail:IUserDetail
    {
        public int Id { get; set; }
        public string UserName { get; set; }
    }
}

3: WESDataLayer Solution: Cette couche contient une référence aux DLL de mes 2 couches supérieures. Cette couche a ma classe de référentiel comme indiqué ci-dessous. Pour l'instant, je garde l'IRepository dans la même classe :)

namespace WESDataLayer
{ 
    public class UserDetailRepository : IUserDetailRepository
    {
        WESMVCEntities context = new WESMVCEntities();

        public IQueryable<IUserDetail> All
        {
            get { return context.UserDetails; }
        }

        public IQueryable<IUserDetail> AllIncluding(params Expression<Func<IUserDetail, object>>[] includeProperties)
        {
            IQueryable<IUserDetail> query = context.UserDetails;
            foreach (var includeProperty in includeProperties) {
                query = query.Include(includeProperty);
            }
            return query;
        }

        public IUserDetail Find(int id)
        {
            return context.UserDetails.Find(id);
        }

        public void InsertOrUpdate(UserDetail userdetail)
        {
            if (userdetail.Id == default(int)) {
                // New entity
                context.UserDetails.Add(userdetail);
            } else {
                // Existing entity
                context.Entry(userdetail).State = EntityState.Modified;
            }
        }

        public void Delete(int id)
        {
            var userdetail = context.UserDetails.Find(id);
            context.UserDetails.Remove(userdetail);
        }

        public void Save()
        {
            context.SaveChanges();
        }

        public void Dispose() 
        {
            context.Dispose();
        }
    }

    public interface IUserDetailRepository : IDisposable
    {
        IQueryable<IUserDetail> All { get; }
        IQueryable<IUserDetail> AllIncluding(params Expression<Func<UserDetail, object>>[] includeProperties);
        UserDetail Find(int id);
        void InsertOrUpdate(UserDetail userdetail);
        void Delete(int id);
        void Save();
    }
}

4: Solution ConsoleApplication1 : Ceci est ma couche d'interface utilisateur. Ce sera mon application MVC dans mon application finale. Ici, j'interroge simplement la base de données et affiche les données. Voici à quoi ressemble le code.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
             IUserDetailRepository repo = new UserDetailRepository();

             var count = repo.All.ToList().Count().ToString();
             Console.WriteLine("Count: {0}", count);
             Console.ReadLine();

        }
    }
}

Question: Ma couche d'interface utilisateur n'a aucune référence à la DLL EF. Cependant, il a une instance de classe Repository. Dans l'application MVC, mon contrôleur aura une instance de classe de référentiel ou UnitOfWork.

a) Est-ce la bonne chose à faire?

b) Y a-t-il un moyen de l'abstraire?

c) Que faire si à l'avenir je veux échanger EF avec Dapper ou tout autre outil ORM?

d) Comment puis-je adapter mon outil DI à ce projet? Dans quelle couche devrait-il être?

e) Tests unitaires. Je suis au courant de StructureMap et je veux l'utiliser dans ce projet de telle manière qu'à l'avenir je devrais pouvoir le remplacer par Ninject. Comment puis-je y arriver?

Merci d'avoir lu cette grande question et j'apprécie vraiment que quelqu'un puisse me diriger dans la bonne direction.

Réponse acceptée

Question: Ma couche d'interface utilisateur n'a aucune référence à la DLL EF. Cependant, il a une instance de classe Repository. Dans l'application MVC, mon contrôleur aura une instance de classe de référentiel ou UnitOfWork.

Oui, les classes de couche d'interface utilisateur ne doivent avoir aucune référence à EF. Mais pour ce faire, ils ne peuvent pas avoir de référence au référentiel concret. Dans MVC Application, si vous n'utilisez pas une couche de service, le contrôleur aura une référence sur IUserDetailRepository et attendra un type concret de la construction. À propos de UnitOfWork, cela dépend de votre implémentation :-)

a) Est-ce la bonne chose à faire?

La bonne chose à faire est appelée "couplage lâche", il semble que votre conception choisisse cette voie.

b) Y a-t-il un moyen de l'abstraire?

Oui, vous pouvez utiliser un résolveur de dépendance. De cette façon, pas besoin de référencer des types concrets, vous aurez un code uniquement basé sur des abstractions

c) Que faire si à l'avenir je veux échanger EF avec Dapper ou tout autre outil ORM?

Vous devez disposer d'une couche d'accès aux données, par exemple, une bibliothèque contenant vos implémentations concrètes de vos contrats IXxxRepository. Dans votre cas, ce seront les implémentations EF. Lorsque vous changerez pour Dapper, vous devrez ré-implémenter cette couche. Le refactoring a une limite acceptable.

d) Comment puis-je adapter mon outil DI à ce projet? Dans quelle couche devrait-il être?

Le meilleur endroit pour placer votre outil DI sera la couche d'interface utilisateur. Au démarrage de l'application, vous allez configurer les liaisons de dépendances et tout fonctionnera automatiquement;)

e) Tests unitaires. Je suis au courant de StructureMap et je veux l'utiliser dans ce projet de telle manière qu'à l'avenir je devrais pouvoir le remplacer par Ninject. Comment puis-je y arriver?

Vous souhaitez débrancher votre résolveur de dépendance pour brancher un autre? Pas de problème, il suffit d'avoir une prévision lors du codage de la configuration de votre DR pour avoir le couplage minimum avec votre application. Il y a quelques astuces pour limiter le couplage dans certains cas ... Dans le projet sur lequel je travaille actuellement, nous avons d'abord une application MVC et une couche de service, une couche métier, une couche d'accès aux données et une couche d'infrastructure. Nous utilisons Ninject en tant que DR, et les couches infratructure et Web UI sont les seules à avoir une référence sur Ninject. Il est très facile de le débrancher et nous avons déjà essayé Unity de cette façon.

Encore une chose, vous ne devriez pas avoir de contrat pour UserDetail. Cela n'est pas nécessaire, utilisez l'injection de dépendance sur les classes sans état plutôt que sur toutes les classes comme les DTO.


Réponse populaire

Si vous utilisez le typage de variable implicite au lieu du typage de variable explicite (c'est-à-dire éliminer le mot-clé var ), vous pouvez déterminer plus facilement les dépendances. Dans la mesure du possible, préférez l'utilisation d'une interface ( IUserDetailRepository ) sur l'utilisation d'une classe ( UserDetailRepository ).

exemples:

1) permettre au compilateur de déterminer le type

var repo = new UserDetailRepository();

2) type déterminé par référence de classe

UserDetailRepository repo = new UserDetailRepository();

3) type déterminé par interface

IUserDetailRepository repo = new UserDetailRepository();

En permettant au type d'être déterminé par Interface plutôt que par le compilateur, vous pouvez échanger différentes références conformes à la même interface (par exemple, IUserDetailRepository repo = new DapperUserDetailRepository();

En outre, vous êtes à la frontière d'un principe appelé Inversion de contrôle (IoC), qui consiste à utiliser un conteneur IoC spécifique ( Ninject , CastleWinsor , Unity , etc.) pour résoudre automatiquement vos dépendances, sans jamais appeler le new mot clé. directement.

Comme vous mentionnez StructureMap, voici un exemple de fonctionnement:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            IContainer container = ConfigureDependencies();
            IUserDetailRepository repo = container.GetInstance<IUserDetailRepository>();

            var count = repo.All.ToList().Count().ToString();
            Console.WriteLine("Count: {0}", count);
            Console.ReadLine();

        }

        private static IContainer ConfigureDependencies() {
            return new Container(x =>{
                x.For<IUserDetailRepository>().Use<UserDetailRepository>();
            });
        }
    }
}


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi