services / healthcheck registration

98 views
Skip to first unread message

steve christensen

unread,
Feb 7, 2015, 7:46:25 PM2/7/15
to gwi...@googlegroups.com
Right now, a Service needs to register itself by injecting the Services singleton and calling the add() method:

public class MyService extends AbstractIdleService {
    @Inject
    public WebServerService(Services services) {
        services.add(this);
    }
}

HealthChecks are similarly registered by injecting the HealthChecks singleton


Rather than that approach, we could create a TypeListener that would take care of registration whenever a Service was instantiated. 

That'd eliminate the need for a user's service implementation to inject Services itself. But, would wiring things up like that be too magical/surprising? 


-Steve

Yun Zhi Lin

unread,
Sep 2, 2015, 6:02:45 AM9/2/15
to GWizard Discussion

Instead of using a TypeListener which acts behind the scenes, perhaps things can be less magical using Guice AOP instead? That way the user has the option of either manually adding the Service/HealthCheck or use the annotation such @Service or @HealthCheck.

Jeff mentions GWizard should express an opinion, but alternative implementation supporting the same opinion can co-exist. E.g. Currently GWizard requires all bindings to be declared explicitly and all example code uses:

bind(MyService.class).asEagerSingleton()

But this can also achieved either by @Singleton with Stage.PRODUCTION, or Governator's @AutoBindSingleton.

I personally think it would be nice if the Service / HealthCheck injection can be freed up for dependencies they really need and focus on their business logic, rather than injecting Services or HealthChecks every time which seem a bit anti-DRY. 

Another option would be to do a static method similar to the EntityManager?

steve christensen

unread,
Sep 2, 2015, 4:09:27 PM9/2/15
to GWizard Discussion
I like your idea of having a @Service or @HealthCheck annotation, it's much more DRY and clear than my current approach.

Yun Zhi Lin

unread,
Sep 2, 2015, 5:59:33 PM9/2/15
to GWizard Discussion
Thanks Steve, I'm having a go at this at the moment. I originally started with the approach similar to dropwizard-guice's AutoConfig, where a classpath scan is done and all classes are registered accordingly. However I find that approach rather brual and it won't work for Generic typed Services/HealthChecks:

bind(new TypeLiteral<GenericService<Foo>>(){}).toEagerSingleton()
bind
(new TypeLiteral<GenericService<Bar>>(){}).toEagerSingleton()

In the above scenario, there will be 2 Service singletons registered by Guice  thanks to TypeLiteral, however the classpath scan will only register 1 Service. 

Thus it does seem keeping the Registration logic (and also Binding logic for non-Generic classes) within the actual class itself is the best approach. So that the configuration logic for each Service/HealthCheck can be self-contained. 

Will update on how I go.

steve christensen

unread,
Sep 3, 2015, 1:24:38 PM9/3/15
to GWizard Discussion
How widely-used do you foresee generic-typed services/healthchecks being?   

As long as the limitation is clearly documented... If most services could use @Service annotation, but we still let a generic-typed Service register itself a slightly-more-messy way, that seems like an OK compromise to me. Although... that does make the tiny dev on my shoulder cry a little from having two solutions to the same problem. 



Rather than eager singletons to instantiate services when the injector is created, and scanning the entire classpath... Could the GWizard application's configuration be what drives service creation?

For example, in my dropwizard/guice project... I configure a list of plugins in my YaML:

plugins:
  - type: alerts
  - type: history
  - type: auth-shiro

Dropwizard creates its configuration POJO using its Discoverable interface to do the Jackson polymorphic deserialization. When my guice-injector-bundle's run() method is called w/ the configuration POJO,
it scans for Guice Module's annotated with that plugin name, and adds them to the injector:


        Set<String> overrideModules = Sets.newHashSet();
        for (PluginConfigurationFactory cfg : configuration.getPlugins()) {
            final JsonTypeName ann = cfg.getClass().getAnnotation(JsonTypeName.class);
            overrideModules.add(ann.value());
        }
       // ... uses reflection to find all the guice modules in the set of names and build a compound module


in a main application module, we bind a 'plugin loader' eager singleton, which has a method like:


    @Inject
    public void loadPlugins(Injector injector,
                            DwApplicationConfiguration cfg) {
        for (PluginConfigurationFactory pluginCfg: cfg.getPlugins()) {
            try {
                logger.debug("Loading plugin cfg: {}", pluginCfg);
                pluginCfg.load(injector);
            } catch (Exception ex) {
                final JsonTypeName ann = pluginCfg.getClass().getAnnotation(JsonTypeName.class);
                logger.error("failed to load plugin '{}'", ann.value(), ex);
            }
        }
    }


and each pluginConfig's load() method took the injector and did whatever instantiation it required. That works for the way we slapped Guice into Dropwizard -- it let us have an application in a fat jar, and let the configuration drive the behavior of the application via loading different Guice modules and running specific code at injection time.


It's been long enough since I played in GWizard that I'm having a hard time picturing how applicable/useful a similar approach might be for gwizard itself.... Probably not much, since the configuration is something defined by the GWizard application, rather than anything dictated by the framework. 

-Steve

Yun Zhi Lin

unread,
Sep 7, 2015, 7:32:07 PM9/7/15
to GWizard Discussion
I use generic serivces/DAOs quite a bit and it saves alot of boilerplate code.

Annotation based Service/Healthcheck registration can be done, I've got the code ready for PR. However I had to resort to a TypeListener + Annotation approach instead of AOP Method Listener. Because Method Listeners cannot be annotated at a Class or Constructor level. 

I also found a issue along the way, basically GWizard isn't compatible with Guice injector Stage.PRODUCTION. See: https://github.com/gwizard/gwizard/issues/11

To declare all Services in configuration files would be very much against the Convention Over Configuration, let's not go back to the Spring days... :) My PR should be ready later today, have a look and see what you think 

Yun Zhi Lin

unread,
Sep 8, 2015, 11:14:49 AM9/8/15
to GWizard Discussion
PR raised https://github.com/gwizard/gwizard/pull/12

Code are placed in separate packages: `gwizard-services` and `gwizard-healthchecks`, to avoid centralised configuration and keep things modular. 

Feel free to suggest better names for the annotations. I didn't want to use "@Service" or "@HealthCheck" because they would cause import conflicts with the actual classes. 

steve christensen

unread,
Sep 10, 2015, 2:41:31 PM9/10/15
to GWizard Discussion
I like it a lot -- having the annotations eliminates my worry about a TypeListener being too magical. 
Reply all
Reply to author
Forward
0 new messages