Dependency Injection con Guice - GTUG

Post on 15-Jan-2015

690 views 7 download

description

 

Transcript of Dependency Injection con Guice - GTUG

Dependency Injection con Guice

@jordi923 octubre 2010

roadmapMotivación

Problema

3 aproximaciones y media

Más DI con Guice

Unit Testing

Conclusiones

#bcngtug #guice

motivación

diseño

Diseño típico de una aplicación

problema común

Cómo obtienen los clientes los servicios?

3 aproximaciones y media

The Factory Pattern (Patrón Fábrica)        + Service Locator

Dependency Injection a mano

Dependency Injection con Guice

parte constante

public interface Dictionary {    void spellchecking(Text text);}   public class CatalanDictionary        implements Dictionary {     @Override    public void spellchecking(Text text) {        // comprobación en catalán    } }

Cualquier servicio se mantiene independientemente de la aproximación

Aproximación #1

The Factory Pattern

factory pattern / client

public class PhoneClient {         Dictionary dictionary;

    public void sendSMS(Text text) {        dictionary = DictionaryFactory.getInstance();        dictionary.spellchecking(text);      }

}

factory pattern / servicepublic static class DictionaryFactory {    static class DictionaryHolder {        static Dictionary instance = new CatalanDictionary();     }

    public static Dictionary getInstance() {        return DictionaryHolder.instance;     }

   public static void setInstance(Dictionary mock) {        DictionaryHolder.instance = mock;    }}

factory pattern / consideraciones

Hay que escribir una fábrica para cada objeto y para cada dependencia - boilerplate code

Hay que escribir una fábrica para cada variación en las dependencias.

El cliente establece sus dependencias en tiempo de compilación - Están encapsuladas.

Propagación viral de clases estáticas - static Difícil reutilizar el código cliente en otro contexto.

Hay que modificar el código para poder hacer unit testing.

Aproximación #1.5

Service Locator

service locator

Tipo especial de fábrica.    Hereda casi todos sus problemas.     Introduce nuevos problemas. eg: no type-safety

Dictionary dictionary = (Dictionary) new ServiceLocator()                                    .get("CatalanDictionary");  

Aproximación #2

Dependency Injection a mano

el principio de hollywood

public class PhoneClient {

    private final Dictionary dictionary;     public PhoneClient(Dictionary dictionary) {        this.dictionary = dictionary    }

    public void sendSMS(Text text) {        dictionary.spellchecking(text);      }

}

"Don't call us, we'll call you"

DI manual / ventajas

No podemos crear un cliente sin los servicios necesarios. Dependencias claras.

Podemos reutilizar el cliente con múltiples implementaciones de un mismo servicio. Las dependencias pasan de estar en tiempo de compilación a estar a nivel de de aplicación.

La clase es testeable.

  Pero aun tenemos que escribir más codigo fábrica! 

Aproximación #3

Dependency Injection con Guice

Google GuiceFramework de DI creado por Google (2007).

Toda aplicación Java lo utilitza: Google Docs, GMail, Adwords...

objetivos    Evitar escribir fábricas y boilerplate code

    Más comprobaciones de tipos - Type safety

    Flexibilidad y facilidad para escribir buen código

@Inject

public class PhoneClient {

    private final Dictionary dictionary;     @Inject     public PhoneClient(Dictionary dictionary) {        this.dictionary = dictionary    }

    public void sendSMS(Text text) {        dictionary.spellchecking(text);      }

}

Aplicamos @Inject para obtener las dependencias.

@Injections

@Inject private Dictionary dictionary;

field injection

method injection@Inject public void setDictionary(Dictionary dictionary) {    this.dictionary = dictionary;}

constructor injectionpublic class PhoneClient {    private final Dictionary dictionary;     @Inject     public PhoneClient(Dictionary dictionary) {        this.dictionary = dictionary    }}

modules

public class DictionaryModule extends AbstractModule {    protected void configure() {       bind(Dictionary.class).to(CatalanDictionary.class);     }}

Tenemos Modules que configuran Guice.   no XML... yay!

public class DictionaryModule implements Module {    public void configure(Binder binder) {      binder.bind(Dictionary.class).to(CatalanDictionary.class);     }}

versión DRY (Don't Repeat Yourself)

versión JIT (Just In Time)

@ImplementedBy(CatalanDictionary.class)public class Dictionary {}

bootstrappingIniciando una aplicaciónpublic class PhoneRunner {    public static void main(String[] args) {        Injector injector = Guice                        .createInjector(new DictionaryModule());        PhoneClient phone = Injector                        .getInstance(PhoneClient.class);

        phone.sendSMS(new Text("foo text"));    }}

consideraciones    @Inject es viral.         Solo una clase debería tratar con el Injector.

más Guice i DI…

múltiples implementaciones

public class DictionaryModule extends AbstractModule {    protected void configure() {          bind(Dictionary.class)            .annotatedWith(Catalan.class)            .to(CatalanDictionary.class);     }}

Binding annotationspublic class PhoneClient {    private final Dictionary dictionary;

    @Inject     public PhoneClient(@Catalan Dictionary dictionary) {        this.dictionary = dictionary

    }}

Hay que configurarlo en un Module

provider patternServicios que dependen del tiempo de ejecución, de librerias de terceros o que se acaban

public interface Provider<T> {    public T get();}

public class CoffeeJunkie {    CupOfCoffee cupOfcoffee;    @Inject    public CoffeeJunkie(CupOfCoffee cupOfcoffee) {        this.cupOfcoffee = cupOfcoffee;       }     public void drinkTwoCupsOfCoffe() {        cupOfcoffee.drink();        cupOfcoffee.drink(); // <-- is empty!    }}

provider injection

public class CoffeeModule extends AbstractModule {   protected void configure() {   bind(CupOfCoffee.class).toProvider(new Provider<CupOfCoffee>() {        public CupOfCoffee get() {            return new CupOfCoffe(); // cuidado con el new!        }      });    }}

public class CoffeeJunkie {    Provider<CupOfCoffee> cupsProvider;    @Inject    public CoffeeJunkie(Provider<CupOfCoffee> cupOfcoffee) {        this.cupsProvider = cupsProvider;       }     public void drinkTwoCupsOfCoffe() {        cupsProvider.get().drink();        cupsProvider.get().drink(); // <-- ok!    }}

@Providessince guice 2.0

Métodos que nos dan un objeto en concreto. Tienen que estar en un Module

public class CoffeeModule extends AbstractModule {   protected void configure() {     // ...   }

   @Provides @WithMilk   public CupOfCoffee provideCupOfCoffeWithMilk() {        return new CupOfCoffe.builder().withMilk().build();   }}

scopesPolítica de reutilización de instancias    No-scope (por defecto)    Singleton    Web: RequestScope, SessionScope...

@Singleton public class CatalanDictionary ... {     ... }

public class DictionaryModule extends AbstractModule {    protected void configure() {        bind(Dictionary.class)            .to(CatalanDictionary.class)            .in(Scopes.SINGLETON);     }}

o en un Module

otras característicasAspect Oriented Programming    con AOP Allicance

bindInterceptor(    Matchers.any(), // classes     Matchers.annotatedWith(Foo.class), // methods    new FooInterceptor() // interceptor); 

Cargar constantes con comprobaciones de tipo.    gtug.properties -> String gtug; int members;

 Injections opcionales, estáticas, ...    @Inject(optional=true) Date launchDate;

Integraciones:    JNDI, Spring, JMX, Struts2, OSGi...

guice 2.0Multibindings   Hello Multibinder y MapBinder / útil para plugins

@Inject ImageFinder(Set<UriBuilder> uriBuilders) { ... }

// modulepublic void configure() {    Multibinder<UriBuilder> uriBinder =           Multibinder.newSetBinder(binder(), UriBuilder.class);    uriBinder.addBinding().to(S3UriBinder.class);}

AssistedInject   Hello @Assisted

Private modules   Soluciona el robot-legs problem: Dos grafos del mismo objeto ligeramente    distintos

unit testing

unit testing

Aplicando DI con Guice, nuestro código ya es testeable!

Código (método) que ejecuta un otro código para comprobar su validez.  funcionamientoPrecondición - Clase a testear - Postcondición (asserts)

característicasRápido, repetible, automático.

frameworksJunit, TestNG, Mockito, EasyMock, JMock

test con junit

public class StringsTest {     @Test    public void stripAllHTMLForAGivenText() {        String html = "<a>Link</a>";        String expected = "Link";        assertEquals(expected,Strings.stripHTML(html));    }      }

public class Strings {

    public static String stripHTML(String input) {        return input.replaceAll("</?+(\\b)[ˆ<>]++>", "");    } }

unit test

test con dependencias

// Class under test CreditCardProcessor creditCardProcessor;

@Testpublic void chargeCreditCard() {        creditCardProcessor = Guice.createInjector()                   .getInstance(CreditCardProcessor.class);   CreditCard c = new CreditCard("9999 0000 7777", 5, 2009);   creditCardProcessor.charge(c, 30.0);   assertThat(creditCardProcessor.balance(c), is(-30.0));}

es incorrecto!

@Inject public CreditCardProcessor(Queue queue) {    this.queue = queue; }

unit test…?

test con dependencias / mocks

@Testpublic void chargeCreditCard() {    Queue queue = mock(Queue.class);   CreditCard c = new CreditCard("9999 0000 7777", 5, 2009);

   creditCardProcessor = new CreditCardProcessor(queue);   creditCardProcessor.charge(c, 30.0);

   verify(queue).enqueue(c, 30.0);}

Dependencias falsas: mocks (mockito)

para acabar…

futuro de dependency injection    Google + SpringSource = JSR-330 acabada    Spring 3 - annotation based    Maven 3 - de plexus a guice

guice 3.0    Soporte para JSR-330    guice-persist integrado (antiguo warp-persist)    Más SPI y Extension Points

y más guice!    GIN - GWT INjection, subset de Guice    RoboGuice - Guice en Android. Inject de views, resources...

gracias!

jordi@donky.org@jordi9