¿Qué complejidad agregan los marcos DI?

8

La respuesta más votada actualmente a una pregunta muy reciente establece que

Los contenedores DI son un patrón de "software empresarial", que se utiliza cuando el gráfico de objetos es muy grande y complejo. Sospecho que el 95% de las aplicaciones no lo requieren.

que es algo con lo que no estoy de acuerdo. Tal vez tengo la terminología equivocada, pero para mí el marco DI significa simplemente "algo que conecta mis objetos". ¿Me estoy perdiendo de algo?

Estoy usando Guice incluso para proyectos realmente pequeños (como 10 clases) para simplificarlos . Claro, es un archivo JAR de 400 kB, pero esto no es lo que me importa. Para un proyecto pequeño, casi nunca necesito ninguna configuración y la única "sobrecarga" es agregar la @Injectanotación.

Entonces realmente me pregunto, ¿qué complejidad adicional causan los marcos DI?

Actualización abordando las respuestas

En un proyecto de 82 clases, tengo

  • 32 @Injectanotaciones
  • 15 @Singletony 1 @ProvidedByanotaciones
  • 4 proveedores (todos los cuales necesitaría también sin DI ya que son mis fábricas)
  • 1 módulo que contiene una sola línea
  • 0 líneas XML !!!

Eso es todo. Claro, es un proyecto pequeño, pero exactamente este era mi punto. El trabajo adicional consistió en unas pocas palabras en lugar de líneas .

Estoy usando únicamente la inyección del constructor para obtener objetos inmutables "fáciles". Cada vez que un nuevo aparece de dependencia, que añaden un campo final, vamos a Lombok RequiredArgsConstructor cuidar de la declaración, y dejar que Guice se encargue de llamar correctamente.

maaartinus
fuente
¿Guice está haciendo una inyección de propiedad?
Reactgular
@MathewFoscarini, supongo que sí, conozco otros términos y este debería ser (un caso especial) del método de inyección.
maaartinus
1
Estoy muy agradecido por Guice. Ha simplificado mucho mi desarrollo y lo ha hecho más verificable y robusto. No puedo creer que estuve sin DI por tanto tiempo. El pastel es real.
El coordinador

Respuestas:

6

Hay un par de compromisos que se deben hacer al usar marcos DI por lo que puedo ver.

Lo más preocupante para mí es que el código de su aplicación generalmente se distribuye entre un lenguaje de programación principal (como Java) y el código de configuración XML / JSON ( es código). Esto significa que, en caso de problemas con su aplicación, debe buscar en dos lugares. A menudo, el código de configuración no se relaciona fácilmente con el código de la aplicación principal.

Por supuesto, la configuración también está fuera de los límites de lo que el compilador (y a menudo el IDE) puede verificar por usted, lo que significa que es mucho más fácil cometer errores en esta configuración que si estuviera escribiendo una clase principal que manejara el cableado. En efecto, esto significa que los problemas de cableado pasan de ser problemas de tiempo de compilación a ser problemas de tiempo de ejecución.

Además de dividir la aplicación, el uso de DI Frameworks a menudo significa que usa @Injecto similar dentro de su código, en lugar de las técnicas DI tradicionales (inyección de interfaz CTOR en Java / C ++, inyección de plantilla en C ++). La desventaja de esto es que debe usar un marco DI con la aplicación. Una alternativa sería diseñar su aplicación sin esperar un marco DI y luego permitir que el marco DI reutilice la inyección tradicional CTOR / setter de sus clases.

La desventaja del mecanismo de inyección tradicional se produce cuando una clase de encapsulación compleja y adecuada exige una cantidad de otras inyecciones relativamente complejas en el momento del CTOR. Esto generalmente se resuelve incorporando a Builder, pero puede ser un dolor.

EDITAR :

Los chicos a continuación han mencionado que Guiceno necesita una configuración XML / JSON separada. Mi respuesta realmente se aplica a mi propio uso de Spring.

Dennis
fuente
2
Lo bueno de Guice es que se puede configurar completamente en Java (no hay XML a menos que obtenga un complemento). Para proyectos pequeños (que es de lo que estoy enmascarando), el "cableado automático" en realidad es suficiente. He actualizado mi pregunta; tal vez todos los temores provienen de mal (o demasiado entusiastas marcos DI)
maaartinus
1
@ Dennis: Guice no es intrusivo en absoluto. El contenedor Guice es simplemente una forma alternativa de obtener instancias de objetos. Todavía puede llamar a constructores si lo desea. Eso es útil para pruebas unitarias con dependencias simuladas.
Kevin Cline
1
No puedo hablar con Guice en particular, pero muchas pilas DI son muy automáticas si las usas para cosas que tienen ciclos de vida largos: singletons, fábricas y similares. Estos tipos de objetos con inyección de constructor le permiten alejarse de cosas estáticas y dependencias difíciles con bastante facilidad; y creo que es útil para esos casos, incluso para pequeños proyectos. Yo diría que, si está utilizando las características más simples y la inyección del constructor de un marco DI, y agrega mucha complejidad ... encuentre un nuevo marco DI.
danwyand
1
@maaartinus: podrías tener razón al respecto. No estoy familiarizado con Guice para ser honesto, así que supongo que mi respuesta se aplica principalmente a Spring.
Dennis
4

Creo que la principal desventaja de usar un contenedor de IoC es toda la magia que hace detrás de escena. (Esto también es un problema con los ORM, otra herramienta mágica común). La depuración de problemas de IoC no es divertida, porque se hace más difícil ver cómo y cuándo se cumplen las dependencias. No puede pasar por la resolución de dependencia en el depurador.

Los contenedores a menudo dificultan la configuración de "clase A con una versión de una dependencia y clase B con una versión diferente", o escenarios similares donde se reutilizan las abstracciones. Puede terminar en situaciones en las que cada clase tiene su propia interfaz, incluso cuando significan lo mismo. Esto lo bloquea del poder de abstracción de reutilizar interfaces.

En mi opinión, la gran victoria que obtienes de IoC es la gestión automática del estilo de vida. Puede declarar componentes como singletons, singletons por solicitud, instancias transitorias, etc., y el contenedor se encarga de todo por usted. Es considerablemente más difícil configurar esto usando new-expresiones. Por otro lado, todavía es fácil causar fugas de recursos al no coincidir los estilos de vida.

Algunos proyectos usan IoC para construir todo en el sistema, incluso clases que representan datos comerciales, sin newpalabras clave. Creo que esto generalmente es un error porque lo alienta a escribir clases mal encapsuladas y demasiadas interfaces. Su código se vuelve dependiente de IoC. También parece ser comórbido con las pruebas excesivas.

Personalmente, generalmente prefiero ir sin contenedor. En cambio, compongo objetos usando new-expresiones encapsuladas en una raíz de composición. Es fácil ver las dependencias concretas de una clase determinada, y cuando cambio un constructor obtengo errores en tiempo de compilación, los cuales son grandes victorias de mantenimiento. También es sencillo configurar instancias separadas con diferentes componentes.

Mi consejo, si desea usar un contenedor de IoC, es cumplir con sus dependencias en el punto de entrada al sistema, y ​​solo usarlo para sus dependencias reales (repositorios y otros). Y recuerde que aún puede usar la inyección de dependencia e incluso mantener la configuración en un solo archivo, sin usar un contenedor.

Benjamin Hodgson
fuente
1
"recuerde que aún puede usar la inyección de dependencia e incluso mantener la configuración en un solo archivo, sin usar un contenedor". ++. Creo que muchas personas olvidan que los marcos de contenedores DI y DI no son lo mismo.
RubberDuck
1

Algunos desarrolladores argumentan que cualquier marco que use en su código es un problema potencial en una fecha posterior, porque generalmente requiere que escriba su código de cierta manera para interactuar con él correctamente, y esto puede causar problemas en una fecha posterior si Resulta que esas formas entran en conflicto con otros requisitos de diseño. Bob Martin expresa aquí un ejemplo de esta preocupación . No estoy de acuerdo personalmente, pero puedo ver su punto.

Jules
fuente
2
Entonces, ¿los beneficios obtenidos al usar este marco en un proyecto más complejo pueden hacer que valga la pena el riesgo?
JeffO
1

Para resumir: Parece que la única complejidad proviene del uso de métodos de configuración inadecuados (sí, XML no es de tipo seguro) y posiblemente también de marcos que hacen mucho más trabajo (Spring administra el ciclo de vida del objeto, que va mucho más allá de DI).

Para un proyecto simple, DI haciendo lo correcto es extremadamente útil:

  • Permite escribir servicios como pequeñas clases inmutables con un mínimo esfuerzo.
  • asegura la soltería en el alcance correcto mientras se mantiene comprobable
  • apenas necesita configuración (aproximadamente 1 línea por 20 clases DI)

También es bastante inofensivo, ya que todo se puede hacer manualmente. Solo está creando las instancias, pasándolas a los constructores para crear más instancias, y así sucesivamente. Aburrido y largo, pero simple.

Un servicio típico con Guice y Lombok se ve así

@Singleton
@RequiredArgsConstructor(onConstructor=@__(@Inject))
public class TotalCostsComputer {
    public CostsResult getCosts(Goods goods, Shippment shippment) {
        return taxComputer.applyTax(composeResult(
                    goodsCostComputer.getCosts(goods),
                    shippmentCostComputer.getCosts(shippment));
    }
    ...

    private final GoodsCostComputer goodsCostComputer;
    private final ShippmentCostComputer shippmentCostComputer;
    private final TaxComputer taxComputer;
}

Ahora, agregar una dependencia significa simplemente agregar un nuevo campo final privado . No puede ser nulo (Guice garantiza esto incluso sin @NonNull ).

Probar significa crear sus propias instancias u obtenerlas del Injector(posiblemente configurado para devolver algunos stubs). Ambos son bastante simples.

maaartinus
fuente