2009-09-11 6 views
46

voy a tener los siguientes componentes en mi solicitudDiseño - ¿Dónde deben estar registrados los objetos cuando se utiliza Windsor

  • DataAccess
  • DataAccess.Test
  • negocios
  • Business.Test
  • Aplicación

Tenía la esperanza de usar Castle Windsor como yo oC para pegar las capas pero no estoy seguro del diseño del encolado.

Mi pregunta es ¿quién debería encargarse de registrar los objetos en Windsor? Tengo un par de ideas;

  1. Cada capa puede registrar sus propios objetos. Para probar el BL, el banco de pruebas podría registrar clases simuladas para el DAL.
  2. Cada capa puede registrar el objeto de sus dependencias, p. la capa empresarial registra los componentes de la capa de acceso a datos. Para probar el BL, el banco de pruebas debería descargar el objeto DAL "real" y registrar los objetos simulados.
  3. La aplicación (o aplicación de prueba) registra todos los objetos de las dependencias.

¿Alguien me puede ayudar con algunas ideas y pros/contras con las diferentes rutas? Los enlaces a proyectos de ejemplo que utilizan Castle Windsor de esta manera serían muy útiles.

Respuesta

73

En general, todos los componentes de una aplicación se deben componer lo más tarde posible, porque eso asegura la modularidad máxima y los módulos se acoplan lo más libremente posible.

En la práctica, esto significa que debe configurar el contenedor en la raíz de su aplicación.

  • En una aplicación de escritorio, que sería en el método Main (o muy cerca de ella)
  • En una aplicación ASP.NET (incluyendo MVC), que sería en Global.asax
  • En WCF, que estaría en una ServiceHostFactory
  • etc.

el contenedor es simplemente el motor que compone módulos en una aplicación de trabajo. En principio, podría escribir el código a mano (esto se llama Poor Man's DI), pero es mucho más fácil usar un DI Container como Windsor.

Tal Raíz Composición ideal será la única pieza de código en la raíz de la aplicación, por lo que la aplicación de una llamada Humble Ejecutable (un término de la excelente xUnit Test Patterns) que no necesita unidad de pruebas en sí mismo.

Sus pruebas no deberían necesitar el contenedor en absoluto, ya que sus objetos y módulos deberían poder componerse, y usted puede suministrar directamente Test Dobles de las pruebas unitarias. Lo mejor es que pueda diseñar todos sus módulos para que sean independientes del contenedor.

también específicamente en Windsor debe encapsular la lógica de registro de componentes dentro de los instaladores (tipos implementar IWindsorInstaller) Ver the documentation para más detalles

+0

Una pregunta con respecto a su último párrafo. ¿Tiene algún enlace a ejemplos de este tipo de diseño? –

+0

Eso sería simplemente DI básica como Constructor Injection y tal ... –

+0

Re último párrafo- ¿significa esto que no usa el IOC en las pruebas? Solo aclarando. Ah, pero ¿y si el código que estás probando lo necesita para construirse? – Greg

23

Si bien la respuesta de Marcos es ideal para escenarios web, la falla llave con su aplicación para todas las arquitecturas (es decir, el cliente rico, es decir, WPF, WinForms, iOS, etc.) es la suposición de que todos los componentes necesarios para una operación pueden/deben crearse a la vez.

Para servidores web, esto tiene sentido ya que cada solicitud es extremadamente efímera y un controlador ASP.NET MVC se crea mediante el marco subyacente (sin código de usuario) para cada solicitud que entra. Así el controlador y todas sus dependencias puede ser fácilmente compuesto por un marco DI, y hay muy poco costo de mantenimiento para hacerlo. Tenga en cuenta que el marco web es responsable de administrar la vida útil del controlador y, a todos los efectos, la duración de todas sus dependencias (que el marco DI creará/inyectará para usted en la creación del controlador). Está muy bien que las dependencias vivan durante la duración de la solicitud y su código de usuario no necesita administrar el tiempo de vida de los componentes y subcomponentes en sí. También tenga en cuenta que los servidores web son apátridas en diferentes solicitudes (excepto en el estado de la sesión, pero eso es irrelevante para esta discusión) y que nunca tiene múltiples instancias de controlador/controlador infantil que necesitan vivir al mismo tiempo para atender una sola solicitud.

En las aplicaciones de cliente enriquecido, sin embargo, este no es el caso. Si utiliza una arquitectura MVC/MVVM (¡lo que debería hacer!) La sesión de un usuario es larga y los controladores crean subcontroladores/controladores hermanos a medida que el usuario navega por la aplicación (consulte la nota sobre MVVM en la parte inferior). La analogía con el mundo de la web es que cada entrada de usuario (clic de botón, operación realizada) en una aplicación de cliente enriquecido equivale a una solicitud que recibe el marco web. Sin embargo, la gran diferencia es que desea que los controladores en una aplicación de cliente enriquecido permanezcan vivos entre las operaciones (es muy posible que el usuario realice varias operaciones en la misma pantalla, que se rige por un controlador en particular) y que los subcontroladores obtengan creado y destruido mientras el usuario realiza diferentes acciones (piense en un control de pestañas que crea la pestaña de forma perezosa si el usuario navega hacia él, o una pieza de UI que solo necesita cargarse si el usuario realiza acciones particulares en una pantalla).

Ambas estas características significan que que es el código de usuario que necesita para gestionar el tiempo de vida de los controladores/sub-controladores, y que las dependencias de los controladores debe no todos los crearse por adelantado (es decir: Sub-controladores, ver-modelos , otros componentes de presentación, etc.). Si usa un marco DI para realizar estas responsabilidades, terminará con no solo mucho más código donde no pertenece (Ver: Constructor over-injection anti-pattern), sino que también tendrá que pasar un contenedor de dependencia en la mayor parte de su capa de presentación, de modo que sus componentes puedan usarlo para crear sus subcomponentes cuando sea necesario.

¿Por qué es malo que mi código de usuario tenga acceso al contenedor DI?

1) El contenedor de dependencias contiene referencias a una gran cantidad de componentes en su aplicación. Pasar este chico malo a cada componente que necesita crear/administrar un subcomponente anoter es el equivalente a usar elementos globales en su arquitectura. Peor aún, cualquier subcomponente también puede registrar nuevos componentes en el contenedor, tan pronto como sea posible se convertirá en un almacenamiento global. Los desarrolladores lanzarán objetos al contenedor solo para pasar los datos entre los componentes (ya sea entre los controladores hermanos o entre las jerarquías de los controladores profundos, es decir, un controlador ancestro necesita tomar datos de un controlador de abuelos). Tenga en cuenta que en el mundo web donde el contenedor no se pasa al código de usuario, esto nunca es un problema.

2) El otro problema con los contenedores de dependencia frente a los localizadores/fábricas de servicios/instanciación directa de objetos es que resolver desde un contenedor hace que sea completamente ambiguo si está CREANDO un componente o simplemente REUSANDO uno existente. En cambio, se deja a una configuración centralizada (es decir, bootstrapper/Composition Root) para averiguar cuál es la duración del componente. En ciertos casos, esto está bien (es decir, controladores web, donde no es el código de usuario el que necesita administrar la duración del componente, sino el propio marco de procesamiento de solicitud de tiempo de ejecución). Sin embargo, esto es extremadamente problemático cuando el diseño de los componentes debe INDICAR si es su responsabilidad administrar un componente y cuál debe ser su duración (Ejemplo: una aplicación de teléfono muestra una hoja que le pide al usuario cierta información. Esto se logra mediante un El controlador crea un subcontrolador que gobierna la hoja superpuesta. Una vez que el usuario ingresa cierta información, la hoja se renueva y el control se devuelve al controlador inicial, que todavía mantiene el estado de lo que el usuario estaba haciendo previamente. Si DI se usa para resolver el subcontrolador de hojas, es ambiguo qué duración debería tener o quién debería ser el responsable de gestionarlo (el controlador iniciador). Compare esto con la responsabilidad explícita dictada por el uso de otros mecanismos.

Escenario A:

// not sure whether I'm responsible for creating the thing or not 
DependencyContainer.GimmeA<Thing>() 

Escenario B:

// responsibility is clear that this component is responsible for creation 

Factory.CreateMeA<Thing>() 
// or simply 
new Thing() 

Escenario C:

// responsibility is clear that this component is not responsible for creation, but rather only consumption 

ServiceLocator.GetMeTheExisting<Thing>() 
// or simply 
ServiceLocator.Thing 

Como se puede ver DI hace que sea poco claro quien es responsable de la administración de la duración de el subcomponente

NOTA: Técnicamente hablando muchos marcos de DI tienen alguna forma de crear componentes con pereza (Ver: How not to do dependency injection - the static or singleton container), que es mucho mejor de lo que pasa el recipiente alrededor, pero aún se está pagando el costo de mutando su código para pasar funciones de creación en todas partes, falta soporte de primer nivel para pasar parámetros de constructor válidos durante la creación, y al final del día todavía está usando un mecanismo de indirección innecesariamente en lugares donde el único beneficio es para lograr la capacidad de prueba, que se puede lograr de maneras mejores y más simples (ver a continuación).

¿Qué significa todo esto?

Significa que DI es apropiado para ciertos escenarios e inapropiado para otros. En las aplicaciones de clientes ricos, sucede que tiene muchas de las desventajas de la DI con muy pocas ventajas. Mientras más grande sea su complejidad en la aplicación, mayores serán los costos de mantenimiento. También conlleva el grave potencial de uso indebido, que de acuerdo con cuán ajustados sean los procesos de comunicación de su equipo y de revisión de códigos, puede ser cualquier cosa, desde un costo de emisión de la tecnología no grave hasta un costo severo. Existe el mito de que los Localizadores o Fábricas de Servicios o las buenas instancias antiguas son de alguna manera mecanismos malos y desactualizados simplemente porque pueden no ser el mecanismo óptimo en el mundo de las aplicaciones web, donde tal vez mucha gente juegue. No deberíamos exagerar. generalice estos aprendizajes a todos los escenarios y vea todo como uñas solo porque hemos aprendido a manejar un martillo en particular.

Mi recomendación PARA APLICACIONES DE CLIENTE RICO es utilizar el mecanismo mínimo que cumpla con los requisitos para cada componente a mano. El 80% del tiempo esto debería ser una instanciación directa. Los localizadores de servicios se pueden usar para alojar los principales componentes de su capa empresarial (es decir, servicios de aplicaciones que generalmente son de naturaleza singleton) y, por supuesto, las fábricas e incluso el patrón Singleton también tienen su lugar. No hay nada que decir que no pueda usar un marco DI oculto detrás de su localizador de servicios para crear sus dependencias de capa empresarial y todo lo que dependen de una vez, si eso le facilita la vida en esa capa, y esa capa no lo hace No exhibe la carga diferida que las capas de presentación de cliente rico abrumadoramente hacen. Solo asegúrate de proteger tu código de usuario del acceso a ese contenedor para que puedas evitar el desorden que puede ocasionar al pasar un contenedor DI.

¿Qué hay de la capacidad de prueba?

La capacidad de prueba se puede lograr absolutamente sin un marco DI. Recomiendo usar un marco de interceptación como UnitBox (gratis) o TypeMock (caro). Estos frameworks le brindan las herramientas que necesita para resolver el problema (cómo se burla de la creación de instancias y las llamadas estáticas en C#) y no le piden que cambie toda su arquitectura para evitarlos (que desafortunadamente es donde la tendencia ha ido en el mundo .NET/Java). Es más sensato encontrar una solución al problema en cuestión y utilizar los mecanismos y patrones de lenguaje natural óptimos para el componente subyacente, luego intentar ajustar cada clavija cuadrada en el agujero DI redondo. Una vez que empiece a utilizar estos mecanismos más simples y más específicos, notará que hay muy poca necesidad de DI en su base de códigos, si es que hay alguno.

NOTA: Para las arquitecturas MVVM

En las arquitecturas básicas MVVM vista-modelos toman de manera efectiva en la responsabilidad de los controladores, lo que para fines consideran el redacción 'controlador' de arriba para aplicarse a 'vista -modelo'. MVVM básico funciona bien para aplicaciones pequeñas, pero a medida que la complejidad de una aplicación crece, puede que desee para utilizar un enfoque MVCVM. Los modelos de vista se vuelven DTO en su mayoría tontos a facilitar la vinculación de datos a la vista mientras interacción con la capa empresarial y entre grupos de modelos de vista que representan pantallas/subpantallas se encapsula en componentes controladores/subcontroladores explícitos. En cualquiera de las arquitecturas, existe la responsabilidad de los controladores y muestra las mismas características de discutidas anteriormente.

+0

En mi humilde opinión, incluso para escenarios web, el contenedor web SÓLO debe utilizarse para obtener el servicio correcto ... El peligro de contenedor.GetService es demasiado genial y en mi humilde opinión mejor con el antiguo código VBA o RAD que container.GetService , hace que la refactorización y la búsqueda de implementaciones concretas sea un problema y es más costoso de escribir ... – user1496062

+2

Estoy confundido por esta publicación. Las aplicaciones web requieren la construcción de objetos en tiempo de ejecución en función de los valores de tiempo de ejecución que no solo se aplican a las aplicaciones de cliente grueso. Nos ocupamos de esto mediante el uso de Abstract Factories, Dispose pattern, configurando IoC scoping (scope VM a la instancia View asociada) y la compatibilidad con contenedores secundarios anidados. Además, Unit Box no parece ser mejor que Service Locator, ya que combina la lógica de creación con la clase que necesita la dependencia (SRP y DI violation en comparación con just DI violation). Pero lo que más me confunde es que sin un contenedor ¿cómo mantienes SÓLIDOS tus diseños? –

+0

No entiendo su preocupación por las vidas personalizadas ... es trivial crear fábricas y fábricas abstractas (especialmente a través de castle windsor en mi experiencia) - ese es el camino a seguir si desea usar una dependencia según sea necesario en tiempo de ejecución (flojo carga y descarga todo lo que quieras). Si está inyectando todas las dependencias directas posibles en un constructor, lo está haciendo mal ... – Vivek

Cuestiones relacionadas