Playframework + Google Guice (Español)


Note: This article is also available in english.

En el proyecto en el que estoy trabajando actualmente empezamos a utilizar Google Guice. Para quien no lo sepa, Guice es un framework de inyección de dependencias. La idea básica de la inyección de dependencias consiste en sumistrar a una clase sus dependencias, en lugar de que sea esta quien tenga que instanciarlas.

Play cuenta con un módulo para integrar Guice:

http://www.playframework.org/modules/guice-1.2/home

Además de la propia documentación del módulo, está este post de @_felipera que te puede ayudar a dar los primeros pasos:

http://geeks.aretotally.in/dependency-injection-with-play-framework-and-google-guice

Cómo usar el módulo

Añadir la dependencia:

require:
  - play
  - play -> guice 1.2

Descargar las dependencias

play deps

Crear una nueva clase que será la que se inyectará en el controlador.

services.MyService

package services;
public interface MyService {
   public void sayHello();
}

services.MyServiceImpl

package services;
public class MyServiceImpl implements MyService {
    public MyServiceImpl(){
        play.Logger.info("constructor!");
    }

    @Override
    public void sayHello() {
        play.Logger.info("hello");
    }
}

Configurar el inyector de dependencias

package config;
public class GuiceConfig extends GuiceSupport {
    @Override
    protected Injector configure() {
        return Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(MyService.class).to(MyServiceImpl.class).in(Singleton.class);
            }
        });
    }
}

De esta forma se configura la clase como un singleton. Cada vez que una clase tenga la dependencia de MyService se inyectará la misma instancia de MyServiceImpl.

Para inyectar la clase se utiliza la anotación @Inject

package controllers;
public class Application extends Controller {

    @Inject
    static MyService myService;

    public static void index() {
        myService.sayHello();
        render();
    }
}

Testing

Mi siguiente paso fue crear un test y es aquí cuando me encontré una sorpresa:

play test

http://localhost:9000/@tests

Compilation error! El problema está en que el módulo tiene una carpeta que se llama test. Esta carpeta en vez de tener algunos tests unitarios o funcionales, lo que tiene son 3 aplicaciones de ejemplo. Lo normal hubiera sido seguir la convención de play que es poner este tipo de aplicaciones en la carpeta ‘samples-and-tests’.

Hice un fork del proyeto para renombrar esta carpeta:

https://github.com/axelhzf/play-guice-module

También hice un pull-request, pero no he tenido respuesta:

https://github.com/pk11/play-guice-module/pull/5

Renombrando la carpeta test del módulo sería suficiente para poder ejecutar este test:

@InjectSupport
public class InjectTest extends UnitTest {
    @Inject
    static MyService myService;

    @Test
    public void injectOk(){
        assertNotNull(myService);
    }
}

Añadiendo más dependencias

Por defecto play detecta automáticamente la anotaciones @Inject en las clases que hereden de Controller, Job and Mail. Si queremos poder inyectar dependencias en otras clases debemos anotar la clase con @InjectSupport.

Normalmente nuestros servicios no son tan simples como MyService. Lo normal es tener dependencias entre servicios. Guice resuelve esto analizando las dependencias e instanciando los objetos en el orden adecuado.

services.MyDependentService

package services;

public interface MyDependentService {
    public void sayHelloWorld();
}

service.MyDependentServiceImpl

package services;

@InjectSupport
public class MyDependentServiceImpl implements MyDependentService {

    @Inject
    static MyService myService;

    public MyDependentServiceImpl(){
        play.Logger.info("Init MyDependentServiceImpl");
    }

    public void sayHelloWorld(){
        myService.sayHello();
        play.Logger.info("world");
    }
}

InjectTest

@InjectSupport
public class InjectTest extends UnitTest {

@Inject
static MyDependentService myDependentService;

@Test
public void injectOk(){
    assertNotNull(myDependentService);
    myDependentService.sayHelloWorld();
}

}

Binding en GuiceConfig

    bind(MyDependentService.class).to(MyDependentServiceImpl.class).in(Singleton.class);

Y esta es la salida por la consola

20:34:39,090 INFO ~ Init MyServiceImpl
20:34:39,095 INFO ~ Init MyDependentServiceImpl
20:34:39,095 INFO ~ Application 'lazySingleton' is now started !
20:34:39,136 INFO ~ hello
20:34:39,136 INFO ~ world

Inyección por parámetro del constructor

Una de las cosas que no me gusta del módulo es que te limita a que los campos que puedes inyectar deben de ser estáticos. Las dependencias por ejemplo me gustaría poder definirlas como parámetros en el constructor. De forma que quede claro que para crear un objeto de la clase MyDependentServiceImpl hace falta un objeto del tipo MyService. Además, utilizar las dependencias mediante constructor facilita hacer tests unitarios.Únicamente es necesario llamar al constructor y pasar como parámetros stubs o mocks de las dependencias. De esta forma no estamos obligados a configurar un inyector.

En la documentación del módulo no vi ninguna referencia a cómo hacer esto. Encontré un artículo que explicaba cómo hacerlo utilizando un Provider:

http://ericlefevre.net/wordpress/2011/05/08/play-framework-and-guice-use-providers-in-guice-modules/

Esta forma funciona correctamente pero más tarde encontré una pregunta en stackoverflow que me dio otra pista:

http://stackoverflow.com/questions/8435686/does-injector-getinstance-always-call-a-constructor

En el Edit pone que se olvidó de anotar con @Inject el constructor. Probé a hacer lo mismo y funcionó:

public class MyDependentServiceImpl implements MyDependentService {

    private final MyService myService;

    @Inject
    public MyDependentServiceImpl(MyService myService){
        this.myService = myService;
        play.Logger.info("Inicializando MyDependentServiceImpl");
    }

    ...

Lazy Singletons

Me faltaba un pequeño detalle para tener google guice configurado perfectamente. En el log se puede ver como los servicios se inicializan cuando se inicia la aplicación.

21:38:11,801 INFO ~ Inicializando MyServiceImpl
21:38:11,805 INFO ~ Inicializando MyDependentServiceImpl
21:38:11,805 INFO ~ Application 'lazySingleton' is now started !

Cuando la aplicación está en modo producción está bien, es el comportamiento adecuado. Los servicios se deberían instanciar al arrancar la aplicación. Pero cuando estoy en modo desarrollo prefiero que los Singletons se inicialicen bajo demanda (lazy). Puede que haya servicios que tarden en iniciarse y quiero que el tiempo que tarda la aplicación en arrancar en modo desarrollo sea lo más rápido posible.

Buscando en la documentación de google guice veo que está preparado para hacer justamente lo que quiero:

http://code.google.com/p/google-guice/wiki/Scopes

Lo único que hay que hacer es que configurar es el STAGE para indicarle a Guice si estamos en modo desarrollo o en modo producción:

Stage stage = Play.mode.isDev() ? Stage.DEVELOPMENT : Stage.PRODUCTION;
return Guice.createInjector(stage, new AbstractModule() {…..

Al volver a ejecutar el test

22:00:03,353 WARN ~ You're running Play! in DEV mode
22:00:04,615 INFO ~ Connected to jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0
22:00:04,811 INFO ~ Guice injector created: config.GuiceConfig
22:00:04,819 INFO ~ Init MyServiceImpl
22:00:04,824 INFO ~ Init MyDependentServiceImpl
22:00:04,824 INFO ~ Application 'lazySingleton' is now started !

Oops! se volvieron a instanciar los singletons al iniciar la aplicación. ¿Será que el Stage no sirve para lo que creo? Vamos a probar con un test:

public class StageTest {

    @Test
    public void testDevelopment(){
        Injector injector = createInjector(Stage.DEVELOPMENT);
        System.out.println("development - before getInstance");
        MyService instance = injector.getInstance(MyService.class);
        System.out.println("development - after getInstance");
    }

    @Test
    public void testProduction(){
        Injector injector = createInjector(Stage.PRODUCTION);
        System.out.println("production - before getInstance");
        MyService instance = injector.getInstance(MyService.class);
        System.out.println("production - after getInstance");
    }

    public Injector createInjector(Stage stage){
        Injector injector = Guice.createInjector(stage, new AbstractModule(){
            @Override
            protected void configure() {
                bind(MyService.class).to(MyServiceImpl.class);
            }
        });
        return injector;
    }
}

Y el resultado es:

INFO: development - before getInstance
INFO: Inicializando MyServiceImpl
INFO: development - after getInstance

INFO: Inicializando MyServiceImpl
INFO: production - before getInstance
INFO: production - after getInstance

Como pone en la documentación, cuando se está en modo DEVELOPMENT los Singleton se inicializan de forma lazy.

¿Si esto funciona así por qué cuando lo probé con el módulo de play no funcionó?

Revisando el código:

https://github.com/pk11/play-guice-module/blob/master/src/play/modules/guice/GuicePlugin.java

Encontré que lo que se hace en el @OnApplicationStart es buscar todas las clases que están anotadas con @InjectSupport las dependencias. Para inyectarlas hace un getBean de cada una. Aquí esta el problema, al hacer el getBean se instancia.

Buscando en internet encontré una solución a este problema:

https://groups.google.com/d/msg/google-guice/405HVgnCzsQ/fBUuueP6NfsJ

Este es el código:

Estas clases lo que hacen es que cuando se crea el inyector, crea un proxy para cada una de las clases que están anotadas como @LazySingleton. De forma que cuando inyecta los objetos lo que se inyecta en realidad es el proxy. La primera vez que se invoque un método de alguna de estas clases, el próxy se va a encargar de inicializar la clase.

La configuración del inyector quedaría así:

public class GuiceConfig extends GuiceSupport {
    @Override
    protected Injector configure() {
        Stage stage = Play.mode.isDev() ? Stage.DEVELOPMENT : Stage.PRODUCTION;
        return Guice.createInjector(stage, new AbstractModule() {
            @Override
            protected void configure() {
                bindScope(LazySingleton.class, MoreScopes.LAZY_SINGLETON);
                bindLazySingletonOnDev(MyService.class, MyServiceImpl.class);
                bindLazySingletonOnDev(MyDependentService.class, MyDependentServiceImpl.class);
            }

            protected <T> void bindLazySingletonOnDev(Class<T> expected, Class<? extends T> implClass){
                if(Play.mode.isDev()){
                    bind(implClass).in(MoreScopes.LAZY_SINGLETON);
                    Provider<T> provider = LazyBinder.newLazyProvider(expected, implClass);
                    bind(expected).toProvider(provider);
                }else{
                    bind(expected).to(implClass).in(Scopes.SINGLETON);
                }
            }
        });
    }
}

Cuando la aplicación está en modo desarrollo, las clases se instanciarán la primera vez que se llame a un método. Cuando usemos el modo producción, las clases se instanciarán cuando se inicie la aplicación.

Me queda pendiente añadir estas clases al fork para poder tener un módulo completo que se pueda reutilizar en todos los proyectos.

Conclusión

Durante los últimos años la inyección de dependencia ha evolucionado convirtiéndose en un recurso que utilizan a diario los programadores. En este artículos hemos visto lo sencillo que es integrar Guice en una aplicación que utiliza play framework. También hemos visto cómo configurar la librería para conseguir una mejor experiencia durante el desarrollo.

Artículo original publicado en http://axelhzf.tumblr.com.

4 responses to this post.

  1. […] Acerca de « Playframework + Google Guice (Español) […]

    Reply

  2. Excelente artículo, Axel…

    Por lo general, la parte más difícil de comenzar a trabajar con inyección de dependencias, es entender para qué lo necesitamos…

    En la página de google guice hay un excelente artículo que lo explica de manera muy claro, con un ejemplo muy simple y fácil de seguir, en el cual hablan del patrón factory, pruebas unitarias, modularidad y, por supuesto, inyección de dependencias.

    No se lo pierdan: http://code.google.com/p/google-guice/wiki/Motivation

    Reply

  3. […] "CRITEO-300×250", 300, 250); 1 meneos Playframework + Google Guice playlatam.wordpress.com/2012/02/06/playframework-google-g…  por opensas hace […]

    Reply

  4. Posted by abogarill on 18 September, 2012 at 9:36

    Excelente artículo, me sirvió mucho.

    Reply

Leave a comment