Necesita ayuda para aplicar los principios SÓLIDOS

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

Pregunta

Realmente impresionado con el curso de visión plural de Juile Lerman sobre "EF in Enterprise" y decidí construir mi aplicación de demostración.

Estoy usando VS 2012 y las últimas versiones de EF, SQL Server y MVC. Estoy construyendo una aplicación de demostración que aplica principios SÓLIDOS. Hago esto para comprender mejor cómo implementar las pruebas DI y de unidades.

He utilizado el primer enfoque DB para esta aplicación de demostración. Contiene solo una tabla llamada UserDetails y a continuación se muestra cómo se ve en el servidor SQL. Usaré esta tabla para las operaciones CRUD. enter image description here

A continuación se muestra cómo he capas mi aplicación:

1. Solución WESModel: esta capa contiene mi archivo Model1.edmx y la clase de contexto de la siguiente manera.

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. Solución de WESDomain: esta capa contiene mis clases de dominio (o clases de POCO). Estas clases de POCO se generaron automáticamente en mi capa WESModel. Los moví a esta capa. Así es como se ve la clase única de 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: Solución WESDataLayer: esta capa contiene referencias a dlls de mis 2 capas anteriores. Esta capa tiene mi clase de repositorio como se muestra a continuación. Por ahora, mantengo el IRepository en la misma clase :)

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: Solución ConsoleApplication1 : esta es mi capa UI. Será mi aplicación MVC en mi aplicación final. Aquí simplemente consulto el DB y visualizo los datos. Así es como se ve el código.

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();

        }
    }
}

Pregunta: Mi capa de interfaz de usuario no tiene ninguna referencia a EF DLL. Sin embargo, tiene una instancia de la clase Repository. En la aplicación MVC, mi controlador tendrá una instancia de clase de repositorio o UnitOfWork.

a) ¿Es esto lo correcto?

b) ¿Hay alguna manera de que pueda resumirlo?

c) ¿Qué pasa si en el futuro quiero cambiar EF con Dapper o cualquier otra herramienta ORM?

d) ¿Cómo encajo mi herramienta DI en este proyecto? ¿En qué capa debería estar?

e) Prueba unitaria. Conozco StructureMap y quiero utilizarlo en este proyecto de tal manera que en el futuro pueda intercambiarlo con Ninject. Cómo logro esto ?

Gracias por leer esta gran pregunta y realmente aprecio que alguien me pueda orientar en la dirección correcta.

Respuesta aceptada

Pregunta: Mi capa de interfaz de usuario no tiene ninguna referencia a EF DLL. Sin embargo, tiene una instancia de la clase Repository. En la aplicación MVC, mi controlador tendrá una instancia de clase de repositorio o UnitOfWork.

Sí, las clases de capa UI no deben tener ninguna referencia a EF. Pero para hacer esto, no pueden tener una referencia al depósito concreto. En la aplicación MVC, si no usa una capa de servicio, el controlador tendrá una referencia justo en IUserDetailRepository, y esperará un tipo concreto desde la construcción. Acerca de UnitOfWork, depende de su implementación :-)

a) ¿Es esto lo correcto?

Lo correcto es llamar "acoplamiento flexible", parece que su diseño está eligiendo de esta manera.

b) ¿Hay alguna manera de que pueda resumirlo?

Sí, puedes usar un Resolutor de Dependencia. De esta forma, sin necesidad de hacer referencia a tipos concretos, tendrá un código basado únicamente en abstracciones

c) ¿Qué pasa si en el futuro quiero cambiar EF con Dapper o cualquier otra herramienta ORM?

Debe tener una capa de acceso a datos, por ejemplo, una biblioteca que contenga sus implementaciones concretas de sus contratos de IXxxRepository. En su caso, serán implementaciones de EF. Cuando cambie para Dapper, tendrá que volver a implementar esta capa. La refactorización tiene un límite aceptable.

d) ¿Cómo encajo mi herramienta DI en este proyecto? ¿En qué capa debería estar?

El mejor lugar para colocar su herramienta DI será la capa UI. Al inicio de la aplicación, configurará los enlaces de dependencias y todo funcionará automágicamente;)

e) Prueba unitaria. Conozco StructureMap y quiero utilizarlo en este proyecto de tal manera que en el futuro pueda intercambiarlo con Ninject. Cómo logro esto ?

¿Desea desconectar su Dependency Resolver para conectar otro? No hay problema, solo tenga un pronóstico al codificar la configuración de su DR para tener el mínimo acoplamiento con su aplicación. Hay algunos consejos para limitar el acoplamiento en algunos casos ... En el proyecto en el que estoy trabajando actualmente, tenemos primero una aplicación MVC y una capa de servicio, capa empresarial, capa de acceso a datos y capa de infraestructura. Usamos Ninject como DR, y las capas Infraestructura y UI web son las únicas que tienen una referencia en Ninject. Es muy fácil desenchufarlo, y ya probamos Unity de esta manera.

Una cosa más, no deberías tener un contrato para UserDetail. No hay necesidad de eso, use la Inyección de Dependencia en clases sin estado en lugar de en todas las clases como DTO.


Respuesta popular

Si utiliza el tipado variable implícito en lugar del tipado variable explícito (es decir, elimina la palabra clave var ), puede determinar las dependencias mucho más fácilmente. Siempre que sea posible, prefiera el uso de una interfaz ( IUserDetailRepository ) sobre el uso de una clase ( UserDetailRepository ).

ejemplos:

1) permitir que el compilador determine el tipo

var repo = new UserDetailRepository();

2) tipo determinado por referencia de clase

UserDetailRepository repo = new UserDetailRepository();

3) tipo determinado por la interfaz

IUserDetailRepository repo = new UserDetailRepository();

Al permitir que el tipo sea determinado por la interfaz en lugar de por el compilador, puede intercambiar diferentes referencias que se ajusten a la misma interfaz (es decir, IUserDetailRepository repo = new DapperUserDetailRepository();

Además, se encuentra al borde de un principio llamado Inversión de control (IoC), que es la práctica de usar un contenedor de IoC específico ( Ninject , CastleWinsor , Unity , etc.) para resolver sus dependencias automáticamente, por lo que nunca llama a la new palabra clave directamente.

Ya que mencionas StructureMap, aquí hay un ejemplo de cómo funciona eso:

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>();
            });
        }
    }
}


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué