Ioc / DI: ¿por qué tengo que hacer referencia a todas las capas / conjuntos en el punto de entrada de la aplicación?

123

(Relacionado con esta pregunta, EF4: ¿Por qué la creación de proxy tiene que habilitarse cuando la carga diferida está habilitada? ).

Soy nuevo en DI, así que tengan paciencia conmigo. Entiendo que el contenedor se encarga de crear instancias de todos mis tipos registrados, pero para hacerlo requiere una referencia a todas las DLL en mi solución y sus referencias.

Si no estuviera usando un contenedor DI, no tendría que hacer referencia a la biblioteca EntityFramework en mi aplicación MVC3, solo a mi capa empresarial, que haría referencia a mi capa DAL / Repo.

Sé que al final del día todas las DLL están incluidas en la carpeta bin, pero mi problema es tener que hacer referencia explícitamente a través de "agregar referencia" en VS para poder publicar un WAP con todos los archivos necesarios.

diegohb
fuente
1
Este extracto del libro Dependency Injection en .NET, segunda edición, es una versión más elaborada de las respuestas de Mark y de mí. Describe en detalle el concepto de la raíz de composición y por qué es realmente bueno dejar que la ruta de inicio de la aplicación dependa de cualquier otro módulo.
Steven
Leí el enlace del extracto y el capítulo 1. Compraré el libro porque realmente disfruté de las analogías y explicaciones simples sobre el complejo asunto de la DI. Creo que debería sugerir una nueva respuesta, responda claramente "no tiene que hacer referencia a todas las capas / ensamblajes en la capa lógica de entrada a menos que también sea la raíz de su composición", enlace al extracto y publique la imagen Figura 3, desde el extracto.
diegohb

Respuestas:

194

Si no estuviera usando un contenedor DI, no tendría que hacer referencia a la biblioteca EntityFramework en mi aplicación MVC3, solo mi capa empresarial que haría referencia a mi capa DAL / Repo.

Sí, esa es exactamente la situación que DI trabaja tan duro para evitar :)

Con un código estrechamente acoplado, cada biblioteca puede tener solo unas pocas referencias, pero estas nuevamente tienen otras referencias, creando un gráfico profundo de dependencias, como este:

Gráfico profundo

Debido a que la gráfica de dependencia es profundo, que significa que la mayoría de bibliotecas arrastran a lo largo de una gran cantidad de otras dependencias - por ejemplo, en el diagrama, Biblioteca C arrastra a lo largo Biblioteca H, Biblioteca E, Biblioteca J, Biblioteca M, Biblioteca K y Biblioteca N . Esto hace que sea más difícil reutilizar cada biblioteca independientemente del resto, por ejemplo, en pruebas unitarias .

Sin embargo, en una aplicación débilmente acoplada, al mover todas las referencias a la raíz de composición , el gráfico de dependencia se aplana severamente :

Gráfico poco profundo

Como lo ilustra el color verde, ahora es posible reutilizar la Biblioteca C sin arrastrar las dependencias no deseadas.

Sin embargo, todo lo dicho, con muchos contenedores DI, no tiene que agregar referencias duras a todas las bibliotecas requeridas. En cambio, puede usar el enlace tardío en forma de escaneo de ensamblaje basado en convenciones (preferido) o configuración XML.

Sin embargo, cuando haga eso, debe recordar copiar los ensamblajes en la carpeta bin de la aplicación, porque eso ya no ocurre automáticamente. Personalmente, rara vez encuentro que valga la pena ese esfuerzo extra.

Se puede encontrar una versión más elaborada de esta respuesta en este extracto de mi libro Dependency Injection, Principles, Practices, Patterns .

Mark Seemann
fuente
3
Muchas gracias, esto ahora tiene mucho sentido. Necesitaba saber si esto fue por diseño. En cuanto a forzar el uso correcto de las dependencias, había implementado un proyecto separado con mi programa de arranque DI como Steven mencionó a continuación, donde hago referencia al resto de las bibliotecas. La aplicación de punto de entrada hace referencia a este proyecto y al final de la compilación completa, esto hace que todos los dlls necesarios estén en la carpeta bin. ¡Gracias!
diegohb
2
@ Mark Seemann ¿Esta pregunta / respuesta es específica de Microsoft? Me gustaría saber si esta idea de mover todas las dependencias al "punto de entrada de la aplicación" tiene sentido para un proyecto Java EE / Spring usando Maven ... ¡gracias!
Grégoire C
55
Esta respuesta se aplica más allá de .NET. Puede consultar el capítulo Principios del diseño de paquetes de Robert C. Martin en, por ejemplo , Desarrollo, principios, patrones y prácticas de software ágil
Mark Seemann,
77
@AndyDangerGagne La raíz de composición es un patrón DI, lo opuesto a Service Locator . Desde la perspectiva de la raíz de composición, ninguno de los tipos es polimórfico; la raíz de composición ve todos los tipos como tipos concretos y, por lo tanto, el principio de sustitución de Liskov no se aplica a ella.
Mark Seemann el
44
Como regla general, las interfaces deben ser definidas por los clientes que las utilizan ( APP, cap. 11 ), por lo que si la Biblioteca J necesita una interfaz, debe definirse en la Biblioteca J. Eso es un corolario del Principio de Inversión de Dependencia.
Mark Seemann
65

Si no estuviera usando un contenedor DI, no tendría que hacer referencia a la biblioteca EntityFramework en mi aplicación MVC3

Incluso cuando usa un contenedor DI, no tiene que dejar que su proyecto MVC3 haga referencia a EF, sino que (implícitamente) elige hacer esto implementando la raíz de composición (la ruta de inicio donde compone sus gráficos de objetos) dentro de su proyecto MVC3. Si es muy estricto sobre la protección de sus límites arquitectónicos mediante el uso de ensamblajes, puede mover su lógica de presentación a un proyecto diferente.

Cuando mueve toda la lógica relacionada con MVC (controladores, etc.) del proyecto de inicio a una biblioteca de clases, permite que este ensamblaje de capa de presentación permanezca desconectado del resto de la aplicación. El proyecto de su aplicación web se convertirá en un shell muy delgado con la lógica de inicio requerida. El proyecto de aplicación web será la raíz de composición que hace referencia a todos los demás ensamblados.

Extraer la lógica de presentación a una biblioteca de clases puede complicar las cosas cuando se trabaja con MVC. Será más difícil conectar todo, ya que los controladores no están en el proyecto de inicio (mientras que las vistas, imágenes, archivos CSS, probablemente deben permanecer en el proyecto de inicio). Esto probablemente sea factible, pero llevará más tiempo configurarlo.

Debido a las desventajas, generalmente aconsejo mantener la raíz de composición en el proyecto web. Muchos desarrolladores no quieren que su ensamblaje MVC dependa del ensamblaje DAL, pero eso no es realmente un problema. No olvide que los ensamblajes son un artefacto de implementación ; divide el código en múltiples ensamblajes para permitir que el código se implemente por separado. Una capa arquitectónica, por otro lado, es un artefacto lógico . Es muy posible (y común) tener varias capas en el mismo ensamblaje.

En este caso, terminaremos teniendo la raíz de composición (capa) y la capa de presentación en el mismo proyecto de aplicación web (por lo tanto, en el mismo ensamblaje). Y aunque ese ensamblaje hace referencia al ensamblaje que contiene el DAL, la capa de presentación aún no hace referencia a la capa de acceso a datos . Esta es una gran distinción.

Por supuesto, cuando hacemos esto, perdemos la capacidad del compilador de verificar esta regla arquitectónica en el momento de la compilación, pero esto no debería ser un problema. El compilador no puede verificar la mayoría de las reglas arquitectónicas y siempre hay algo como el sentido común. Y si no hay sentido común en su equipo, siempre puede usar revisiones de código (que cada equipo debería hacer IMO siempre por cierto). También puede usar una herramienta como NDepend (que es comercial), que le ayuda a verificar sus reglas arquitectónicas. Cuando integra NDepend con su proceso de compilación, puede advertirle cuando alguien ingresó un código que viola dicha regla arquitectónica.

Puede leer una discusión más elaborada sobre cómo funciona la raíz de composición en el capítulo 4 de mi libro Inyección de dependencia, principios, prácticas, patrones .

Steven
fuente
Un proyecto separado para bootstrapping fue mi solución ya que no tenemos ndepend y nunca lo he usado antes. Sin embargo, lo investigaré ya que parece una mejor manera de lograr lo que estoy tratando de hacer cuando solo haya una aplicación final.
diegohb
1
El último párrafo es excelente y está comenzando a ayudarme a cambiar de opinión sobre cuán estricto soy con mantener capas en ensamblajes separados. Tener dos o más capas lógicas en un ensamblaje está realmente bien si emplea otros procesos en torno a la escritura de código (como revisiones de código) para garantizar que no haya referencias de clases DAL en su código de UI y viceversa.
BenM
6

Si no estuviera usando un contenedor DI, no tendría que hacer referencia a la biblioteca EntityFramework en mi aplicación MVC3, solo mi capa empresarial que haría referencia a mi capa DAL / Repo.

Puede crear un proyecto separado llamado "DependencyResolver". En este proyecto, debe hacer referencia a todas sus bibliotecas.

Ahora, la capa de interfaz de usuario no necesita NHibernate / EF ni ninguna otra biblioteca que no sea relevante para la interfaz de usuario, excepto Castle Windsor como referencia.

Si desea ocultar Castle Windsor y DependencyResolver de su capa de interfaz de usuario, puede escribir un HttpModule que llame al material de registro de IoC.

Solo tengo un ejemplo para StructureMap:

public class DependencyRegistrarModule : IHttpModule
{
    private static bool _dependenciesRegistered;
    private static readonly object Lock = new object();

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, args) => EnsureDependenciesRegistered();
    }

    public void Dispose() { }

    private static void EnsureDependenciesRegistered()
    {
        if (!_dependenciesRegistered)
        {
            lock (Lock)
            {
                if (!_dependenciesRegistered)
                {
                    ObjectFactory.ResetDefaults();

                    // Register all you dependencies here
                    ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry()));

                    new InitiailizeDefaultFactories().Configure();
                    _dependenciesRegistered = true;
                }
            }
        }
    }
}

public class InitiailizeDefaultFactories
{
    public void Configure()
    {
        StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type);
          ...
    }
 }

DefaultControllerFactory no usa el contenedor IoC directamente, pero delega a los métodos del contenedor IoC.

public class StructureMapControllerFactory : DefaultControllerFactory
{
    public static Func<Type, object> GetController = type =>
    {
        throw new  InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!");
    };

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }
        return GetController(controllerType) as Controller;
    }
}

El GetControllerdelegado se configura en un registro de StructureMap (en Windsor debería ser un instalador).

Rookiano
fuente
1
Me gusta aún más de lo que terminé haciendo, los módulos son geniales. Entonces, ¿dónde haría la llamada a Container.Dispose ()? ¿Evento ApplicationEnd o EndRequest dentro del módulo ...?
diegohb
1
@Steven Porque Global.asax está en tu capa de interfaz de usuario de MVC. El HttpModule estaría en el Proyecto DependencyResolver.
Novato
1
El pequeño beneficio es que nadie puede usar el contenedor IoC en la interfaz de usuario. Es decir, nadie puede usar el Contenedor de IoC como un localizador de servicios en la IU.
Novato
1
Además, no permite a los desarrolladores usar accidentalmente el código DAL en la capa de la interfaz de usuario, ya que no hay una referencia sólida al ensamblaje en la interfaz de usuario.
diegohb
1
He descubierto cómo hacer lo mismo usando la API de registro genérico de Bootstrapper. Mi proyecto de IU hace referencia a Bootstrapper, el proyecto de resolución de dependencias donde conecto mis registros y proyectos en mi Core (para las interfaces) pero nada más, ni siquiera mi DI Framework (SimpleInjector). Estoy usando OutputTo nuget para copiar dlls en la carpeta bin.
diegohb
0
  • Hay una dependencia: si un objeto crea una instancia de otro objeto.
  • No hay dependencia: si un objeto espera una abstracción (inyección de constructor, inyección de método ...)
  • Las referencias de ensamblaje (que hacen referencia a dll, servicios web ...) son independientes del concepto de dependencia, porque para resolver una abstracción y poder compilar el código, la capa debe hacer referencia a ella.
riadh gomri
fuente