lunes, febrero 21, 2011

DDD, Spring y AspectJ: inyección de depencias (II)

En la entrada anterior analizabamos el uso de la anotación Configurable y cómo esta nos ayudaba a realizar la inyección de dependencias en nuestro objetos de dominio, ayudándonos de esta manera a realizar nuestro diseño siguiendo una filosofía orientada al dominio (podréis encontrar mucha información, artículos, referencias, ponencias, . . ., relativas a este tema en http://domaindrivendesign.org/).

Como ya comentamos anteriormente el uso de la anotación Configurable era beneficioso aunque si la clase anotada se instancia un gran número de veces podríamos incurrir en una notable pérdida de rendimiento puesto que la anotación anterior hace uso de la reflectividad. Adicionalmente, indicábamos que  otro de los "problemas" era que nuestro código estaba acoplado a la plataforma, en este caso, Spring. Durante esta entrada plantearemos otro mecanismo de inyección de dependencias en nuestros objetos de dominio: inyección basada en interfaces de dominio.

Partiendo del ejemplo analizado en el ejemplo anterior, definiremos un nuevo interface PricingStrategyClient que presentará el método setPricingStrategy(PricingyStrategy pricingStrategy) . Todas aquellas entidades de nuestro dominio que necesiten esta funcionalidad deberán implementar el interfaz anterior.

Hasta el momento no hemos anotado nuestra clase con ningún artefacto adicional (@Configurable) por lo que ahora tendremos que escribir nuestro propio aspecto para inyectar las dependencias en nuestra clase de dominio. Para llevar a cabo este trabajo extenderemos un aspecto abstracto disponible en spring-aspects-3.0.5.jar (los ejemplos de estas entradas están desarrollados con la versión 3.0.5 de Spring) de tipo GenericInterfaceDrivenDependencyInjectionAspect. De manera resumida, este aspecto base determina cuando se crea una nueva instancia (o serializa) de nuestra clase e invoca al método configureBean. Nosotros únicamente deberemos implementar el método configure tal y como se muestra en el siguiente fragmento de código:


public aspect PricingStrategyDIAspect extends
  GenericInterfaceDrivenDependencyInjectionAspect {
protected PricingStrategy pricingStrategy;
public PricingStrategy getPricingStrategy() {
return pricingStrategy;
}
public void setPricingStrategy(PricingStrategy pricingStrategy) {
this.pricingStrategy = pricingStrategy;
}
@Override
protected void configure(PricingStrategyClient bean) {
bean.setPricingStrategyClient(pricingStrategy);
}
}

Como último paso de nuestra nueva solución sólo tendremos que definir nuestro nuevo aspecto en el contexto de aplicación:
<bean id="pricingStrategy" class="com.blogspot.miguelinlas3.springexamples.ddd.domain.strategy.SimplePricingStrategy"/>

<bean class="com.blogspot.miguelinlas3.springexamples.ddd.domain.interfacedibased.PricingStrategyDIAspect"
factory-method="aspectOf">
<property name="pricingStrategy" ref="pricingStrategy"/>
</bean>
Una posible mejora sería incluir el aspecto anterior como un aspecto estático dentro del interfaz PricingStrategyClient de modo que estaríamos enfatizando la relación entre ambos aunque esto os lo dejo como ejercicio ;)

2 comentarios:

Miguel dijo...

Hola Miguel,

Me ha gustado mucho tu post, es realmente interesante.

Me gustaría darte mi opinión acerca de la definición de aspectos (ITD), un tanto basada en la tipología de los proyectos en los que me veo envuelto.

Muchas veces en pro de realizar un diseño que sea adaptable tendemos a extraer a configuración la definición de las dependencias que de otra manera estarían declarada explícitamente en el código. A veces esas dependencias toman la forma de declaraciones Inter-type en AspectJ, cuyo tejido es realizado en tiempo de compilación. (Roo por ejemplo, abusa de esto.)

Bien el caso es que si parte de la lógica es modularizada de esta forma, nos encontramos ante una explosión de artefactos*, que a mi modo de verlo, no hacen otra cosa que complicar la legibilidad del código y lo predecible de su comportamiento, y sin embargo, al ser dependencias que se enlazan en tiempo de compilación, no permiten ser sustituidas ante un cambio de los requerimientos en runtime.

El caso es que aunque puede parecer elegante, me parece llevar al extremo un concepto que ya está resuelto de forma más elegante en otros lenguajes como en C# (extension methods y partial classes) o en Ruby, Python, et. al (Open classes)

Me gustaría saber qué opinas tú a este respecto.

Un saludo, y gracias por el blog.

*con artefactos me refiero a http://en.wikipedia.org/wiki/Artifact_(software_development), no al significado que guarda relación con Maven.

migue dijo...

Hola Miguel,

En primer lugar muchas gracias por pasarte y dejar tu opinión. La verdad que se agrecede tener otros puntos de vista.

Personalmente, y seguramente esto está un poco sesgado porque yo me siento muy cómodo con la orientación a aspectos :), el uso de aop me permite escribir código mucho más comprensible aunque en un principio pueda parecer lo contrario. Cuando surge el tema de la legibilidad/trazabilidad siempre pongo el mismo ejemplo: ¿te imaginas el shock que tuvo que suponer para los programadores de programas secuenciales, escritos en C, la aparición de la orientación a objetos? En cierto modo la OO también "dificulta" la legibilidad/trazabilidad de código.

Los aspectos me permiten modularizar mi código de manera que puedo centralizar en un punto toda la funcionalidad relativa a una característica de mi desarrollo (por ejemplo la seguridad, o las transacciones, multihilo, etc) permitiendo que cada persona se centre en lo que realmente sabe hacer. Después, mediante la definición de reglas, puedo componer mi sistema de la manera que más me convenga.

Aunque no conozco en profundidad C# ni Ruby, si que conozco Groovy en detalle, y soy consciente de que son una gran alternativa gracias a sus técnicas de metaprogramación. Creo que las alternativas son buenas y tendremos que escoger la solución que mejor satisfaga nuestras necesidades en un determinado instante.

Respecto al reemplazo en tiempo de ejecución, tienes toda la razón; generalmente el proceso de weaving se realiza en tiempo de compilación por lo que para reemplazar una funcionalidad deberíamos recompilar nuestro sistema si necesitamos hacer uso de un aspecto diferente. Existe otro tipo de tejido que se realiza en tiempo de carga (el tejido se realiza en el momento en el que se carga una clase en un classloader) y con el que podríamos realizar otro tipo de soluciones diferentes a las que nos ofrece el tejido en tiempo de compilación.

Hasta el punto anterior me he referido única y exclusivamente a los "aspectos tradicionales" que me permiten modularizar la funcionalidad y construir mi sistema en función de unas reglas de tejido.

Respecto a los Inter Type Declarations (ITDs) . . . , y aunque no te lo creas, son una característica que, aunque tienen mucho tiempo, ha adquirido cierta popularidad en la actualidad con la aparición de Roo (como bien dices, Roo utiliza esta característica de manera notable). El uso de esta característica del lenguaje me permite hacer cosas que no puedo hacer con Java, como Traits (algo que si puedo hacer en Scala).

¿En resumen? (opinión personal), creo que AspectJ es una gran alternativa aunque bien es cierto que la popularidad de los lenguajes dinámicos y sus técnicas de metaprogramación son una gran alternativa (los lenguajes dinámicos son una tecnología "relativamente joven" por lo que creo que tardarán en convertirse en algo ampliamente difundido, sobre todo en el mundo empresarial, aunque bien es cierto que cada vez tienen más adeptos).

Ya termino :). Creo que cuantas más alternativas tengamos en nuestra caja de herramientas tendremos la opción de desarrollar programas de mejor calidad, más comprensibles y mantenibles. En función del tipo de desarrollo que vayamos a acometer tendremos que escoger la herramienta que mejor se adapte a nuestras necesidades.