Adding, replacing, removing modules on the fly

790 views
Skip to first unread message

Warren

unread,
Jan 9, 2012, 12:16:42 PM1/9/12
to google-guice
I have a use case where I need to add, replace or remove a
datasource(s). I have each data source living in a MyBatisModule
installed in a PrivatModule with a common service exposed. I currently
writing an implementation that can do this, but I will have to restart
the app every time I make a change. Is there a way of doing this on
the fly with the use of Inject#createChildInjector(Module... modules)
or some other method. The services that are exposed from each
PrivateModule do not depend on each other. It would also be nice to be
able to inject a Map of some sort where I can get the different
exposed services that are created:

@Inject
Map<String<ItemService> itemServices;

...

itemServices.get("DataSource1").getItem(itemId);
itemServices.get("DataSource2").getItem(itemId);


I am very new to Guice and from what I have seen so far, it looks like
I should be able to do this, just need some help so I don't try to
reinvent the wheel.

Thanks,

Warren Bell

Mathieu Clavel

unread,
Jan 10, 2012, 10:50:07 AM1/10/12
to google...@googlegroups.com
Hello,

You can inject maps via constructors like this :
- add an annotation to the map parameter in the constructor :

public A(@com.google.inject.name.Named("name") final Map<String, ItemService> itemServices) {
...
}

"name" can be changed to any string.

- add a binding in the module class :
bind(new TypeLiteral<Map<String, ItemService>>() {}).annotatedWith(com.google.inject.name.Names.named("name")).toProvider(MyMapProvider.class);

There I used a provider to get the map, but you can also bind directly to an instance.

Hope it helps you,
Regards,

Mathieu

Warren Bell

unread,
Jan 9, 2012, 1:55:27 PM1/9/12
to google-guice
I meant:

@Inject
Map<String, ItemService> itemServices;

in my initial post.

Thanks,

Warren Bell

Warren Bell

unread,
Jan 10, 2012, 1:26:03 PM1/10/12
to google...@googlegroups.com
How would you create the map in MyMapProvider.class given the following
bindings:

bind(ItemService.class).annotatedWith(Names.named("datasource1")).to(ItemServiceDataSource1Impl.class);
bind(ItemService.class).annotatedWith(Names.named("datasource2")).to(ItemServiceDataSource2Impl.class);
bind(ItemService.class).annotatedWith(Names.named("datasource3")).to(ItemServiceDataSource3Impl.class);
bind(ItemService.class).annotatedWith(Names.named("datasource4")).to(ItemServiceDataSource4Impl.class);

Each ItemService would represent a different data source. I plan on
exposing each one of these ItemService bindings from a seperate
PrivateModule. Each PrivateModule will have it's own MyBatisModule.

Also the number of ItemService implementations will grow and not all
ItemServices connect to a live database.

So the app may be initially configured like:

datasource1 - live
datasource2 - not live
datasource3 - live
datasource4 - not live

and then the user could reconfigure it to:

datasource1 - not live
datasource2 - not live
datasource3 - live
datasource4 - live
datasource5 - live

I am also wondering if Modules#override(Module... modules) can be used
to switch a data source from "not live" to "live" and back, something
like this:

Modules.override(new LivePrivateModuleWithMyBatisModule()).with(new
NotLivePrivateModuleWithNoMyBatisModule());

where the only exposed binding in each Module would be this in
LivePrivateModuleWithMyBatisModule:

bind(ItemService.class).annotatedWith(Names.named("datasource1")).to(ItemServiceDataSource1Impl.class);

and this in NotLivePrivateModuleWithNoMyBatisModule:

bind(ItemService.class).annotatedWith(Names.named("datasource1")).to(ItemServiceDummyImpl.class);

I am hoping this would replace the first binding with the second binding
effectively turning a live data source to "not live". I know I would
still need to clean up the underlying DataSource some how.

Also, if Injector#createChildInjector(Module... modules) could be used
to add a new Module representing a new DataSource.

Bottom line is that the user will be able to change and add data sources
and switch them from live to "not live", and in my code I want to do
something like this:


@Inject
Map<String, ItemService> itemServices;

...

Item item = itemServices.get(activeDataSource).getItem(itemId);


Please let me know if there is a better way of doing all of this.


Thanks,

Warren Bell

Mathieu Clavel

unread,
Jan 11, 2012, 9:13:41 AM1/11/12
to google...@googlegroups.com
I'm not sure I understand all that you want to do.
You can create the map in a provider by injecting each different annotated ItemService in the provider constructor, and adding them to the map that you will return with the get() method.

Something like :
 
 
public class MyMapProvider implements Provider<Map<String, ItemService>> {

private final Map<String, ItemService> myMap;

@Inject
public MyMapProvider(@Named("datasource1") final ItemService datasource1,
  @Named("datasource2") final ItemService datasource2,
  @Named("datasource3") final ItemService datasource3,
  @Named("datasource4") final ItemService datasource4) {
 
  myMap = new HashMap<String, ItemService>();
  myMap.put("datasource1", datasource1);
  myMap.put("datasource2", datasource2);
  myMap.put("datasource3", datasource3);
  myMap.put("datasource4", datasource4);
}

@Override
public Map<String, ItemService> get() {
  return myMap;
}
}

With that code, you will always return the same instance with the provider.
If that instance is modified, it will be visible for every class using it (HashMap is not synchronized, so be careful with concurrent modification).

You can also write the provider to return a new instance at each call.

For the module manipulation, I never used it so I don't know how to do it and if it will work.

Maybe adding a boolean to the ItemService so you know if the datasource is active or not ?
You will need help from someone tht's better at guice.

Regards,

Mathieu
Reply all
Reply to author
Forward
0 new messages