Learning Netflix Governator – Part 1
I have been working with Netflix Governator for the last few days and got to try out a small sample using Governator as a way to compare it with the dependency injection feature set of Spring Framework. The following is by no means comprehensive, I will expand on this in the next series of posts.
So Governator for the uninitiated is an extension to Google Guice enhancing it with some Spring like features, to quote the Governator site:
classpath scanning and automatic binding, lifecycle management, configuration to field mapping, field validation and parallelized object warmup.
Here I will demonstrate two features, classpath scanning and automatic binding.
Basic Dependency Injection
Consider a BlogService, depending on a BlogDao:
public class DefaultBlogService implements BlogService { private final BlogDao blogDao; public DefaultBlogService(BlogDao blogDao) { this.blogDao = blogDao; } @Override public BlogEntry get(long id) { return this.blogDao.findById(id); } }
If I were using Spring to define the dependency between these two components, the following would be the configuration:
package sample.spring; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import sample.dao.BlogDao; import sample.service.BlogService; @Configuration public class SampleConfig { @Bean public BlogDao blogDao() { return new DefaultBlogDao(); } @Bean public BlogService blogService() { return new DefaultBlogService(blogDao()); } }
In Spring, the dependency configuration is specified in a class annotated with @Configuration annotation. The methods annotated with @Bean return the components, note how the blogDao is being injected through constructor injection in blogService method.
A unit test for this configuration is the following:
package sample.spring; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import sample.service.BlogService; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; public class SampleSpringExplicitTest { @Test public void testSpringInjection() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SampleConfig.class); context.refresh(); BlogService blogService = context.getBean(BlogService.class); assertThat(blogService.get(1l), is(notNullValue())); context.close(); } }
Note that Spring provides good support for unit testing, a better test would be the following:
package sample.spring; package sample.spring; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import sample.service.BlogService; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class SampleSpringAutowiredTest { @Autowired private BlogService blogService; @Test public void testSpringInjection() { assertThat(blogService.get(1l), is(notNullValue())); } @Configuration @ComponentScan("sample.spring") public static class SpringConig { } }
This is basic dependency injection, so to specify such a dependency Governator itself is not required, Guice is sufficient, this is how the configuration would look using Guice Modules:
package sample.guice; import com.google.inject.AbstractModule; import sample.dao.BlogDao; import sample.service.BlogService; public class SampleModule extends AbstractModule{ @Override protected void configure() { bind(BlogDao.class).to(DefaultBlogDao.class); bind(BlogService.class).to(DefaultBlogService.class); } }
and a Unit test for this configuration is the following:
package sample.guice; import com.google.inject.Guice; import com.google.inject.Injector; import org.junit.Test; import sample.service.BlogService; import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.*; public class SampleModuleTest { @Test public void testExampleBeanInjection() { Injector injector = Guice.createInjector(new SampleModule()); BlogService blogService = injector.getInstance(BlogService.class); assertThat(blogService.get(1l), is(notNullValue())); } }
Classpath Scanning and Autobinding
Classpath scanning is a way to detect the components by looking for markers in the classpath. A sample with Spring should clarify this:
@Repository public class DefaultBlogDao implements BlogDao { .... } @Service public class DefaultBlogService implements BlogService { private final BlogDao blogDao; @Autowired public DefaultBlogService(BlogDao blogDao) { this.blogDao = blogDao; } ... }
Here the annotations @Service, @Repository are used as markers to indicate that these are components and the dependencies are specified by the @Autowired annotation on the constructor of the DefaultBlogService.
Given this the configuration is now simplified, we just need to provide the package name that should be scanned for such annotated components and this is how a full test would look:
package sample.spring; ... @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class SampleSpringAutowiredTest { @Autowired private BlogService blogService; @Test public void testSpringInjection() { assertThat(blogService.get(1l), is(notNullValue())); } @Configuration @ComponentScan("sample.spring") public static class SpringConig {} }
Governator provides a similar kind of a support:
@AutoBindSingleton(baseClass = BlogDao.class) public class DefaultBlogDao implements BlogDao { .... } @AutoBindSingleton(baseClass = BlogService.class) public class DefaultBlogService implements BlogService { private final BlogDao blogDao; @Inject public DefaultBlogService(BlogDao blogDao) { this.blogDao = blogDao; } .... }
Here, @AutoBindSingleton annotation is being used as a marker annotation to define the guice binding, given this a test with classpath scanning is the following:
package sample.gov; import com.google.inject.Injector; import com.netflix.governator.guice.LifecycleInjector; import com.netflix.governator.lifecycle.LifecycleManager; import org.junit.Test; import sample.service.BlogService; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; public class SampleWithGovernatorTest { @Test public void testExampleBeanInjection() throws Exception { Injector injector = LifecycleInjector .builder() .withModuleClass(SampleModule.class) .usingBasePackages("sample.gov") .build() .createInjector(); LifecycleManager manager = injector.getInstance(LifecycleManager.class); manager.start(); BlogService blogService = injector.getInstance(BlogService.class); assertThat(blogService.get(1l), is(notNullValue())); } }
See how the package to be scanned is specified using a LifecycleInjector component of Governator, this autodetects the components and wires them together.
Just to wrap the classpath scanning and Autobinding features, Governator like Spring provides a support for junit testing and a better test would be the following:
package sample.gov; import com.google.inject.Injector; import com.netflix.governator.guice.LifecycleTester; import org.junit.Rule; import org.junit.Test; import sample.service.BlogService; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; public class SampleWithGovernatorJunitSupportTest { @Rule public LifecycleTester tester = new LifecycleTester(); @Test public void testExampleBeanInjection() throws Exception { tester.start(); Injector injector = tester .builder() .usingBasePackages("sample.gov") .build() .createInjector(); BlogService blogService = injector.getInstance(BlogService.class); assertThat(blogService.get(1l), is(notNullValue())); } }
Conclusion
If you are interested in exploring this further I have a sample in this github project, I would be expanding this project as I learn more about Governator.
Reference: | Learning Netflix Governator – Part 1 from our JCG partner Biju Kunjummen at the all and sundry blog. |