An Introduction to Microservices With Undertow
Undertow can be a great tool in your microservices tool-built. In the first article of this series, we discuss just why that is.
Join the DZone community and get the full member experience.
Join For FreeIntroducing Undertow
This is a series of articles trying to cover a powerful and yet an uncommon Java-based NIO server called Undertow. Over the sequence of these articles, I’ll try to cover what Undertow is and how you can leverage it to build a full-featured Java-based production quality network service that provides a REST-based interface and supports modern messaging systems such as Kafka. These articles will purposefully avoid topics on using Servlet APIs, as Undertow follows a simple programming model based around a single abstract interface (SAM) called HttpHandler.
Litany of Distributed Computing Concepts in Java
If you’ve been building Java-based distributed systems, then you’d agree that no other platform has seen so many techniques of building distributed systems as Java has. In fact, most Java developers have used more than a couple of the following programming models for building such systems:
- Remote Method Invocation (RMI)
- Java Messaging Service (JMS)
- CORBA
- Enterprise Java Bean (EJB)
- Java Servlet
- Spring
- SOAP
- REST
- GRPC
- Modern messaging framework (Kafka, RabbitMQ)
- Actor framework (Akka)
- Netty
Rise of Microservice Frameworks
Before microservice became ubiquitous as a concept we had service-oriented architecture (SOA) that worked with both SOAP- and REST-based multiple monolithic services. However, these monolithic services showed limitations in terms of how large they could grow before becoming unmaintainable and compromising the software release cycle.
Microservices as an architectural style was first coined in May 2011 and officially agreed upon a year after in May 2012. Since then, Java has seen myriads of microservice frameworks and the list keeps on growing every day. Some of the most popular ones you may have heard of are:
- Spring Boot
- DropWizard
- Jooby
- SparkJava
- Vertx
- Ninja Framework
- Play Framework
- Lagom
- Micronaut
- Various MicroProfile implementations
Most of these microservice frameworks follow an opinionated way of programming and are built on top of one or more of the following Java containers or toolkits:
- Tomcat
- Jetty
- Grizzly
- Netty
- Undertow
One of the commonalities across all these frameworks is that they all follow the Uber-jar (also called fat-jar) model of deployment and they are launched like a Java application.
What Is Undertow?
Undertow is an NIO server built and maintained by the Red Hat JBoss team. Beginning with Wildfly version 8, Undertow became Wildfly's default web container. Before that, the JBoss team was relying on Apache Tomcat for handling web requests.
Undertow uses another JBoss project called XNIO for its NIO capabilities.
Although Undertow is an NIO server, it is designed to handle both blocking and non-blocking requests.
The first version of Undertow was released in February 2014. As of writing this article, Undertow is at version 2.0.17 and can optionally support the full Servlet 4.0 spec.
Some microservice frameworks such as Spring Boot and Jooby also support Undertow as a web container. The Wildfly team built their MicroProfile implementation, called Thorntail, on top of Undertow.
We can visualize Undertow as a barebones Java-based Http Server that is fully programmable using its HttpHandler
interface. We can use it for building any kind of HTTP-oriented services.
What it Isn’t
Most of the major microservice frameworks support multiple capabilities such as:
- Dependency injection.
- HTML template engines.
- Automatic wiring of messaging systems (Kafka, RabbitMQ).
- Automatic HTTP request and response lifecycle handling.
- Annotation-based URL routing.
- Data binding (SQL, NoSQL) and migration support (Flyway, Liquibase).
- Circuit breakers and service discovery.
Undertow supports none of the above capabilities out of the box and everything needs to be programmed. With this definition, Undertow is not a microservice framework and it doesn’t try to be one.
Perhaps the biggest question is why one must use Undertow (and in what use cases) if none of the above capabilities are supported out of the box.
Considering the extremely slim profile of Undertow, it frees developers from following any convention when using third-party libraries. So, in a way, your microservices actually follow the model of BYOL (bring your own library).
Over the course of these articles, I’ll cover the various options available when solving problems where you need to talk to external systems and services such as Redis, RDBMS, and other microservices so that you don’t end up writing extremely low-level code.
Undertow Architecture
Assembling Undertow-based applications is straightforward. Undertow is composed like a standard Java application where server configuration can be specified through both internal or external configuration such as properties files, etcd, consul, AWS KMS, etc.
The following is how various components interact in an Undertow application:
Here are various lifecycle steps:
- The server receives an HTTP request through an HTTP handler, such as the routing handler, that understands how to resolve route to a specific HTTP handler.
- The request is passed on to the HTTP connector.
- The connector routes it to the XNIO worker instance.
- If tje request is blocking, then it is desirable to hand it off to worker pool; if this is not availble then we'd hand it off to the IO pool.
JBoss XNIO
XNIO is another project started at JBoss that is a building block of Undertow. This is a low-level IO framework that provides channel-based abstractions similar to Java NIO. However, it adds the notion of callbacks and thread and resource management making it easier to access lower level networks and IO files. This makes it easier for programmers to build high-performance network services. The detailed discussion of XNIO is out of scope for these series of articles, as most developers will rarely need to directly use XNIO low-level APIs.
Undertow's Handler Interface
Undertow's request and response cycles are managed via an instance of the HttpHandler
interface. HttpHandler
can be chained and any handler in the chain can generate a response and end the conversation. This is very similar to how it is done in Golang via its HttpHandler
interface.
To compose a request handler, one must implement the HttpHandler
interface. This is a SAM interface with a signature like the one below:
package io.undertow.server;
public interface HttpHandler {
void handleRequest(HttpServerExchange exchange) throws Exception;
}
A sample application towards the end of this article shows how to implement this method and define an HTTP route. Usually, one implementation of a handler per API is sufficient. However, services may have more than one implementation of handlers per API if multiple handlers exist in the chain of execution.
Core Undertow Concepts
Undertow is a configurable server with sane default settings. Application developers may want to customize those settings to fit the needs of their application. Some of the configurations to keep in mind are:
Worker Thread Pool: Worker thread pools manage blocking tasks. All blocking API calls must chain their handler within
BlockingHandler
to ensure the worker threads are used. This pool size can be much higher than the IO thread pool. The general recommendation is around ten per CPU core. Note: In your log file for a given API call, if the thread name is shown asXNIO-1 task-{some-thread-Id}
this it means the worker thread is used for handling such calls.IO Thread Pool: The IO thread pool manages threads that perform non-blocking tasks. Any API call that requires external blocking systems such as a database, a Redis instance, or API gateways, are blocking in nature and should not be executed on IO threads. Such calls must be offloaded to worker threads. To offload those calls to a worker thread pool,
BlockingHandler
can be used. The IO thread pool size of two per CPU core is a reasonable recommendation. Note: In your log file for a given API call, if the thread name is shown asXNIO-1 I/O-{some-thread-Id}
this means that the IO thread is used for handling such calls.
A Simple Undertow-Based REST Service
package com.undertow_articles.article01;
import io.undertow.Undertow;
import static io.undertow.Handlers.path;
public class Application {
public static void main(String[] args){
Undertow.Builder builder = Undertow.builder();
builder.setIoThreads(2);
builder.setWorkerThreads(10);
builder.addHttpListener(8080, "0.0.0.0");
builder.setHandler(path()
.addPrefixPath("/", exchange -> {
exchange.getResponseSender().send("Hello World");
})
);
Undertow server = builder.build();
server.start();
}
}
Explanation of server configuration:
Line #10 - Sets the server IO threads.
Line #11 - Sets the server worker threads.
Line #12 - Defines the HTTP listener at port
8080
and any network address. If the network address is left empty, then it defaults tolocalhost
so the service will not be accessible via the IP address.Line #13 - Defines the path handler. This can be chained to specify multiple path patterns.
Line #14 - This is the handler that listens to the application root path (
"/"
). This may be used as catch-all handler.Line #15 -
HttpServerExchange
generates a response that may be consumed by callers (browsers, services).Line #19 - This command starts the server as a daemon thread.
What's Next
In next several articles, I’ll show how to build a production quality microservice with Undertow using all the standard patterns used by popular microservice frameworks, including externalized configurations via a configuration file, Consul, or etcd; data access and caching; messaging with Kafka, Metrics, Service Discovery, and Circuit Breaker.
Stay tuned!
Resources
Opinions expressed by DZone contributors are their own.
Comments