Microservices With Undertow: Dependency Injection
Learn all about what dependency injection is, what the different types of dependency injections are, and how to use Dagger.
Join the DZone community and get the full member experience.
Join For FreeThe previous article introduced Undertow and how its handler and thread pool system works. To create a full production quality microservice, we need to go above and beyond writing simple HTTP handlers. Most microservice frameworks use some kind of dependency injection and this article aims to introduce readers on how to address that need.
You may also like: An Introduction to Microservices With Undertow
What Is Dependency Injection
Dependency injection or DI is a mechanism where one system supplies the dependencies required by another object. Dependency injection has multiple benefits such as freeing the calling classes from composing the complete dependency graph.
It also promotes programming via the interface and thus allowing client code to change the behavior during runtime based on configuration. The advantages and disadvantages have been debated onerously and it's not the scope of this article. For now, we’ll focus on how to add support for DI within our slim HTTP container.
Most Java developers who have built services (microservices or monolith) in the last ten years have probably used one of the two most popular dependency injection frameworks — Spring or Guice.
Types of Dependency Injection
There are multiple implementations of dependency injection available to Java developers and we can broadly categorize them into two different types:
Compile Time DI: This is a mechanism where the required factory classes are generated during the compilation phase of the Java application. The advantage of this approach is that memory requirement is usually lower since there isn’t any need for using reflection and proxies to generate factory objects. The disadvantage is that this technique relies heavily on the annotation processor that is used during compilation and every signature change in dependencies will require recompiling the project.
Runtime DI: This is a mechanism where DI container generates proxies and factories on the fly during runtime. This often increases resource utilization during startup.
This is the list of some popular Java-based DI frameworks:
Name | Type | Actively Maintained (Maintainer) | JSR-330 Compliant |
Spring | Runtime | Yes (Pivotal) | Yes |
Guice | Runtime | Yes (Google) | Yes |
Dagger 2 | Compile-time | Yes (Google) | Yes |
Micronaut | Compile-time | Yes (Object Computing) | Yes |
Weld | Runtime | Yes (Redhat) | Yes |
To build microservices with the slim profile it is important to pull as little JAR dependencies as possible. We want to make sure our bootstrap process is fast which and somewhat deterministic. This may be necessary especially in cloud-native environments where during container auto-scaling we want them to the Java processes to launch and become available as quickly as possible. Owing to this requirement we can go with either Dagger or Micronaut. For this article, we’ll go with Dagger.
Note on JSR-330 Compliance: JSR-330 is a Java specification released on October 13th, 2009 to standardize the use of Dependency Injection via annotations. This way application developers can be sure that their service code may still work should they change the underlying Dependency Injection implementation. These annotations are available via javax-inject
API.
Brief Introduction to Dagger
A complete introduction to Dagger is out of the scope of this article; however, we'll briefly touch the basic concepts that apply in context with Undertow.
Singleton: The classes with an application-wide scope such as HTTP Handlers, Service and Repository Classes are generally marked as @Singleton
.
Module: Module classes provide an instance of dependency available via third-party libraries. Each method in this class is a factory method that provides such dependency. The method can be marked as @Singleton if it has an application scope. Examples of such dependencies may be Java DataSource object, HttpClient providers, Caching Client factories. We can have multiple module classes in the application and each such class must be annotation as @Module
.
Component: Component class is a glue that allows the non-DI class to get access to classes managed by the DI container. This class is required during the bootstrapping process of the application. Usually, there is one Component object in an application that is defined as an Interface marked with an annotation of @Component
. However, based on the complexity of the application and a need for custom scope, multiple Component classes can be created.
It's Code Time!
This is an example of an Undertow application that uses Dagger as a dependency injection framework and exposes just one API endpoint — Health Check that is required by application load balancers to mark a container healthy or unhealthy. The API endpoint in this service will always return the Http 200 OK status code.
The complete code is available here
Here is some explanation of classes:
HealthHandler
— Http Handler that handles request and response for the endpoint/health
.WebModule
— Dagger Module class that provides a factory method for customized JacksonObjectMapper
.ApplicationComponent
— Dagger Singleton Component class that provides access to all DI managed class required during bootstrap.Application
— Undertow launcher class that initiates the microservice to start accepting the request.
Let's examine the source code of Application
Class to understand the lifecycle.
package com.undertow_articles.article02;
import com.undertow_articles.article02.dagger.ApplicationComponent;
import com.undertow_articles.article02.dagger.DaggerApplicationComponent;
import io.undertow.Undertow;
import io.undertow.server.RoutingHandler;
public class Application {
public static void main(String[] args){
ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
final RoutingHandler routingHandler = new RoutingHandler();
routingHandler.get("/health", applicationComponent.healthHandler());
Undertow.Builder builder = Undertow.builder();
builder.setIoThreads(2);
builder.setWorkerThreads(10);
builder.addHttpListener(8080, "0.0.0.0");
builder.setHandler(routingHandler);
Undertow server = builder.build();
server.start();
}
}
Line #11 - Initializes the Dagger dependency injection subsystem. Note the class DaggerApplicationComponent
is generated during compilation of the application.
Line #13 and 14 - Creates a RoutingHandler instance and that bounds an endpoint to a Singleton instance of HttpHandler
implementation.
Line #16 to 22 - Sets up the Undertow server subsystem and bootstraps the application.
Let's review the changes required in build.gradle
build file
buildscript {
repositories {
mavenCentral()
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
maven { url 'https://oss.jfrog.org/oss-snapshot-local' }
}
dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:4.0.4"
classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0"
classpath "net.ltgt.gradle:gradle-apt-plugin:0.21"
}
}
apply plugin: "java"
apply plugin: "idea"
apply plugin: "application"
apply plugin: "com.github.ben-manes.versions"
apply plugin: "com.github.johnrengelman.shadow"
apply plugin: "net.ltgt.apt-idea"
repositories {
mavenLocal()
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
maven { url 'https://oss.jfrog.org/oss-snapshot-local' }
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
sourceSets {
main.java.srcDirs += 'src/main/java'
}
def jacksonVersion='2.10.1'
def log4jVersion='2.12.1'
mainClassName = "com.undertow_articles.article02.Application"
shadowJar {
archiveBaseName = 'article02'
archiveClassifier = null
archiveVersion = null
mergeServiceFiles()
}
dependencies {
annotationProcessor "com.google.dagger:dagger-compiler:2.25.2"
compile "com.google.dagger:dagger:2.25.2"
compile "io.undertow:undertow-core:2.0.28.Final"
compile "javax.inject:javax.inject:1"
compile "commons-io:commons-io:2.6"
compile "org.apache.commons:commons-lang3:3.9"
compile "org.apache.commons:commons-collections4:4.4"
compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
compile "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
compile "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}"
compile "com.lmax:disruptor:3.4.2"
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
compile "org.apache.logging.log4j:log4j-core:${log4jVersion}"
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}"
compile "org.apache.logging.log4j:log4j-jcl:${log4jVersion}"
}
Line #12 - Gradle apt plugin is required for processing Dagger annotations in IntelliJ IDEA.
Line #21 - Applies Gradle apt plugin to this project.
Line #54 - Instructs Dagger compiler to be used as the annotation processor for this application.
Closing Thoughts
The purpose of this article is not to show that the Spring framework is not feature-rich. It is converse since Spring has glue for almost everything that is needed by most enterprise applications. However, sometimes when you want to build a microservice low in profile (suited for lightweight containers) and doesn’t dictate a certain programming paradigm (non-opinionated) than it is better to start with an extremely barebone toolkit. That barebone toolkit could be built on top of Undertow, Netty, or Grizzly and none of them dictates how to write your microservices.
What's Next
In the next article in this series, we’ll cover how to have scalable configuration management for Undertow based microservices. Beyond the next article, the topics that’ll be touched upon includes talking with database, schema management via flyway, working with messaging system, advanced and scalable handler system and many more. Stay tuned!
Further Reading
Configuring Security Headers in Undertow
Building Microservices With[out] Spring Boot
Using Camel-Undertow Component for Supporting an http2 Connection
Opinions expressed by DZone contributors are their own.
Comments