Contenedores IoC, ¿buenos o malos?

Durante este año ha habido bastantes debates sobre el uso de contenedores de inversión de control para realizar inyección de dependencias. Hay quien después de evangelizar sobre el uso de contenedores, ahora los considera algo maligno e innecesario, y hay quien cree que siguen siendo piezas fundamentales para construir aplicaciones y hay discusiones muy interesantes. ¿Y yo? Buena pregunta…

Antes de analizar ventajas e inconvenientes, quiero dejar claro que estamos hablando de contenedores de inversión de control y no sólo de inyección de dependencias como patrón de
diseño.

Ya escribí en su día sobre la diferencia entre ambos conceptos y creo que la inyección de dependencias es útil en contextos donde un contenedor IoC puede no ser la solución más natural, como es el caso de lenguajes dinámicos.

Ventajas de usar un contenedor de inversión de control

Utilizar un contenedor de inversión de control como herramienta para implementar inyección de dependencias es una alternativa muy frecuente en el mundo de los lenguajes estáticos.

El contenedor nos permite gestionar de una forma fácil la infinidad de componentes que forman parte de cualquier aplicación medianamente compleja, ayudándonos a controlar qué dependencias usar en cada momento y definir de forma externa a las mismas sus relaciones y ciclos de vida.

Desde ese punto de vista, se trata de una herramienta muy valiosa para conseguir diseñar aplicaciones en que los componentes no están muy acoplados, tienen responsabilidades muy definidas y, por tanto, pueden ser reutilizados fácilmente.

Además, hoy en día los frameworks de inversión de control son elementos de software muy bien desarrollados, con muchos puntos de extensión que nos permiten aplicar técnicas como AOP y convenciones de una forma sencilla y conseguir así escribir código más conciso y estructurado.

Otra ventaja importante es poder reconfigurar la aplicación a través del contenedor. Gracias a eso es bastante fácil separar una aplicación monolítica en varios procesos, o unir una aplicación distribuida en un único proceso, cambiando únicamente la configuración del contenedor.

También es posible reemplazar determinados servicios para adaptar una aplicación genérica a un cliente concreto, sin necesidad de cambiar nada más que la forma en que se registran esos componentes en el contenedor.

En contra de los contenedores

Los principales argumentos en contra del uso de contenedores se basan en la complejidad que introducen en las aplicaciones que los utilizan.

Indudablemente, utilizar cualquier tipo de librería supone asumir ciertos costes en cuanto a complejidad, puesto que a la complejidad inherente al sistema que estemos desarrollando, hemos de tomar en consideración la complejidad de la propia librería.

Esto puede hacer que para determinados escenarios, en los cuales el equipo de desarrollo no está familiarizado con este tipo de librerías, tener que aprender a manejar la librería y ajustar la forma de trabajo sea un gran hándicap.

Además, por el tipo de desarrollo que suele aplicarse cuando se utiliza un contenedor, se hace más difícil saber exactamente quién utiliza cada componente puesto que toda la composición se realiza a partir de la configuración del contenedor, y no siempre es sencillo saber qué implementación de qué servicio se está usando en un componente concreto.

En el caso de los lenguajes estáticos, al utilizar un contenedor perdemos parte de la seguridad que nos ofrece el compilador. Cuando introducimos un nuevo parámetro en un constructor, puede ocurrir que la aplicación compile sin problemas, pero a la hora de resolver el componente no sea posible hacerlo porque no haya una implementación del nuevo servicio registrada en el contenedor.

Mi opinión

En realidad, el caso de los contenedores no es diferente del caso de los ORMs o de cualquier otra herramienta “avanzada”.

Usarlos no es gratis y no son aptos para cualquier tipo de aplicación, pero desde mi punto de vista, si la aplicación tiene cierta complejidad su uso merece la pena. Desde que empecé a utilizar Castle hace 4 o 5 años, creo que no he desarrollado ninguna aplicación “grande” que no usara de una forma u otra un contenedor de inversión de control.

Es cierto que existe una curva de aprendizaje, pero también es verdad que este tipo de frameworks no suele ser nada invasivo: si se usa correctamente, el contenedor sólo será visible en 2 o 3 puntos de la aplicación.

Aplicando unas convenciones correctamente diseñadas, podemos olvidarnos de que el contenedor existe durante el 99% del tiempo, haciendo que cualquier desarrollador que sea capaz de crear las clases en las carpetas adecuadas pueda ser productivo aunque no domine la filosofía detrás del contenedor. Es algo parecido a lo que ocurre con ASP.NET MVC: aunque no comprendas todo lo que está pasando, si creas el controlador y las vistas en las carpetas adecuadas, todo funciona correctamente.

En cuanto a la pérdida de seguridad por el componente de dinamismo que introducen, no me parece algo tan grave. Con un uso adecuado de tests automatizados se puede mantener el control. Hay muchas aplicaciones desarrolladas con lenguajes puramente dinámicos, por lo que no debe de ser tan grave.

Hay quien dice que el bajo acoplamiento entre dependencias tiene a convertirse en una escasa cohesión que hace complicado seguir el código porque el código necesario para realizar una tarea queda repartido en mil sitios. Sin embargo, en mi experiencia eso suele estar más motivado por el nivel de granularidad elegido para los tests «unitarios» que por usar o no un contenedor.

Quizá la parte que menos me guste del uso de contenedores es que es fácil acabar cayendo en lo que se conoce como header interfaces, es decir, interfaces que sólo tienen una implementación, y que es algo que muchos consideran un antipatrón.

En resumen, creo que un contenedor de inversión de control es una pieza clave cuando se desarrollan aplicaciones de cierta complejidad con lenguajes estáticos. Está claro que nada es gratis, pero en esos escenarios el coste de introducir un contenedor se compensa sobradamente con las ventajas.

Es cierto que con arquitecturas más orientadas a servicios independientes, al estilo de las que se suelen emplear con CQRS, cada parte del sistema puede que sea lo bastante simple y pequeña como para que no merezca la pena el uso de un contenedor, pero creo que este tipo de arquitecturas presentan otro tipo de desafíos (y resuelven otro tipo de problemas) como para meterlas en esta discusión sobre contenedores.