1. Overview
In our earlier article, we’ve shown how building scalable applications using Ratpack looks like.
In this tutorial, we will discuss further on how to use Google Guice with Ratpack as dependency management engine.
2. Why Google Guice?
Google Guice is an open source software framework for the Java platform released by Google under the Apache License.
It’s extremely lightweight dependency management module that is easy to configure. Moreover, it only allows constructor level dependency injection for the sake of usability.
More details about Guice can be found here.
3. Using Guice With Ratpack
3.1. Maven Dependency
Ratpack has first-class support for Guice dependency. Therefore, we don’t have to manually add any external dependency for Guice; it already comes pre-built with Ratpack. More details on Ratpack‘s Guice support can be found here.
Hence, we just need to add following core Ratpack dependency in the pom.xml:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-core</artifactId>
<version>1.4.5</version>
</dependency>
You can check the latest version on Maven Central.
3.2. Building Service Modules
Once done with the Maven configuration, we’ll build a service and make good use of some simple dependency injection in our example here.
Let’s create one service interface and one service class:
public interface DataPumpService {
String generate();
}
This is the service interface which will act as the injector. Now, we have to build the service class which will implement it and will define the service method generate():
public class DataPumpServiceImpl implements DataPumpService {
@Override
public String generate() {
return UUID.randomUUID().toString();
}
}
An important point to note here is that since we are using Ratpack’s Guice module, *we don’t need to use Guice‘s @ImplementedBy or @Inject annotation to manually inject the service class.*
3.3. Dependency Management
There are two ways to perform dependency management with Google Guice.
The first one is to use Guice‘s AbstractModule and other on is to use Guice’s instance binding mechanism method:
public class DependencyModule extends AbstractModule {
@Override
public void configure() {
bind(DataPumpService.class).to(DataPumpServiceImpl.class)
.in(Scopes.SINGLETON);
}
}
A few of points to note here:
- by extending AbstractModule we are overriding default configure() method
- we are mapping DataPumpServiceImpl class with the DataPumpService interface which is the service layer built earlier
- we have also injected the dependency as Singleton manner.
3.4. Integration With the Existing Application
Since the dependency management configuration is ready, let’s now integrate it:
public class Application {
public static void main(String[] args) throws Exception {
RatpackServer
.start(server -> server.registry(Guice
.registry(bindings -> bindings.module(DependencyModule.class)))
.handlers(chain -> chain.get("randomString", ctx -> {
DataPumpService dataPumpService = ctx.get(DataPumpService.class);
ctx.render(dataPumpService.generate().length());
})));
}
}
Here, with the registry() – we’ve bound the DependencyModule class which extends AbstractModule. Ratpack’s Guice module will internally do the rest of the needful and inject the service in the application Context.
Since it’s available in the application-context, we can now fetch the service instance from anywhere in the application. Here, we have fetched the DataPumpService instance from the current context and mapped the /randomString URL with the service’s generate() method.
As a result, whenever the /randomString URL is hit, it will return random string fragments.
3.5. Runtime Instance Binding
As said earlier, we will now use Guice’s instance binding mechanism to do the dependency management at runtime. It’s almost the same like previous technique apart from using Guice’s bindInstance() method instead of AbstractModule to inject the dependency:
public class Application {
public static void main(String[] args) throws Exception {
RatpackServer.start(server -> server
.registry(Guice.registry(bindings -> bindings
.bindInstance(DataPumpService.class, new DataPumpServiceImpl())))
.handlers(chain -> chain.get("randomString", ctx -> {
DataPumpService dataPumpService = ctx.get(DataPumpService.class);
ctx.render(dataPumpService.generate());
})));
}
}
Here, by using bindInstance(), we’re performing instance binding, i.e. injecting DataPumpService interface to DataPumpServiceImpl class.
In this way, we can inject the service instance to the application-context as we did in the earlier example.
Although we can use any of the two techniques for dependency management, it’s always better to use AbstractModule since it’ll completely separate the dependency management module from the application code. This way the code will be a lot cleaner and easier to maintain in the future.
3.6. Factory Binding
There’s also one more way for dependency management called factory binding. It’s not directly related to Guice’s implementation but this can work in parallel with Guice as well.
A factory class decouples the client from the implementation. A simple factory uses static methods to get and set mock implementations for interfaces.
We can use already created service classes to enable factory bindings. We just need to create one factory class just like DependencyModule (which extends Guice’s AbstractModule class) and bind the instances via static methods:
public class ServiceFactory {
private static DataPumpService instance;
public static void setInstance(DataPumpService dataPumpService) {
instance = dataPumpService;
}
public static DataPumpService getInstance() {
if (instance == null) {
return new DataPumpServiceImpl();
}
return instance;
}
}
Here, we’re statically injecting the service interface in the factory class. Hence, at a time only one instance of that interface would be available to this factory class. Then, we have created normal getter/setter methods to set and fetch the service instance.
Point to note here is that in the getter method we have made one explicit check to make sure only a single instance of the service is present or not; if it’s null then only we have created the instance of the implementational service class and returned the same.
Thereafter, we can use this factory instance in the application chain:
.get("factory", ctx -> ctx.render(ServiceFactory.getInstance().generate()))
4. Testing
We will use Ratpack‘s MainClassApplicationUnderTest to test our application with the help of Ratpack‘s internal JUnit testing framework. We have to add the necessary dependency (ratpack-test) for it.
Point to note here is since the URL content is dynamic, we can’t predict it while writing the test case. Hence, we would match the content length of the /randomString URL endpoint in the test case:
@RunWith(JUnit4.class)
public class ApplicationTest {
MainClassApplicationUnderTest appUnderTest
= new MainClassApplicationUnderTest(Application.class);
@Test
public void givenStaticUrl_getDynamicText() {
assertEquals(21, appUnderTest.getHttpClient()
.getText("/randomString").length());
}
@After
public void shutdown() {
appUnderTest.close();
}
}
5. Conclusion
In this quick article, we’ve shown how to use Google Guice with Ratpack.
As always, the full source code is available over on GitHub.