Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Sharing Routes and Running Multiple Java Services in a Single JVM With Undertow

DZone's Guide to

Sharing Routes and Running Multiple Java Services in a Single JVM With Undertow

In this article, we show some options for sharing endpoints across services as well as run multiple services in a single JVM.

· Java Zone
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

Microservices has become an industry standard term and everyone is jumping aboard the hype train. There are definitely pros and cons in both building and maintaining microservices, however, they will not be discussed in this article, just remember you are not Google. We are going to show some options for sharing endpoints across services as well as run multiple services in a single JVM.

Services

Since we are talking about code reuse, let's grab our RouteHandler's from two previous examples to demonstrate. We will be using our lightweight REST server example as well as the simple content types example. To start we are going to run each of them as their own stand alone service on their own port.

Undertow.builder()
        .addHttpListener(8080, "0.0.0.0", Middleware.common(RestServer.ROOT))
        .build()
        .start();

View on GitHub

curl -X POST "localhost:8080/users" -d '
{
  "email": "user1@test.com",
  "roles": ["USER"]
}
';
{
  "email" : "user1@test.com",
  "roles" : [ "USER" ],
  "dateCreated" : "2017-09-04"
}

curl -X GET "localhost:8080/users/user1@test.com"
{
  "email" : "user1@test.com",
  "roles" : [ "USER" ],
  "dateCreated" : "2017-09-04"
}
Undertow.builder()
        .addHttpListener(8081, "0.0.0.0", Middleware.common(ContentTypesServer.ROUTES))
        .build()
        .start();

View on GitHub

curl localhost:8081/helloWorldText
Hello World

Everything looks good; we are in business.

Combining Routinghandlers

A simple use case for shared routes would be ping / healthchecks / server status that all of your services might share. We don't have one prepared so use your imagination with our existing services. With RoutingHandler.addAll we can combine both RoutingHandler's into a single RoutingHandler and have a server listen to all of the routes on a single port. This allows us to combine common code across routing handlers or even run multiple micro services together under a single port (see next section for some reasons why you might want to do this).

RoutingHandler combinedHanlder = new RoutingHandler().addAll(RestServer.ROUTES)
                                                     .addAll(ContentTypesServer.ROUTES);
Undertow.builder()
        .addHttpListener(8082, "0.0.0.0", Middleware.common(combinedHanlder))
        .build()
        .start();

View on GitHub

curl -X POST "localhost:8082/users" -d '
{
  "email": "user1@test.com",
  "roles": ["USER"]
}
';
{
  "email" : "user1@test.com",
  "roles" : [ "USER" ],
  "dateCreated" : "2017-09-04"
}

curl -X GET "localhost:8082/users/user1@test.com"
{
  "email" : "user1@test.com",
  "roles" : [ "USER" ],
  "dateCreated" : "2017-09-04"
}
curl localhost:8082/helloWorldText
Hello World

Once again everything is working but under a single port this time.

One Server Listening on Multiple Ports

Testing microservices, especially ones that depend on other services to be up, can be quite the pain. You either need to run N services in your IDE/command line or possibly set up something like Docker so it is easy to reproduce. With each of these, sometimes you need to remember which order to start each service in, yikes lots of fun. If all of our services are Java-based and use the same dependencies, we have another option. We already showed that the services could be combined into a single RoutingHandler. Maybe this doesn't meet your needs and each service is split by port and expects everything to work that way. Undertow has you covered, we can run multiple ports in a single undertow instance.

Note: If you have proper loose coupling, both of the servers shouldn't be accessible from the same Maven/Gradle project. So what do we do? Just make a new module that includes both server modules. This is not ideal and not necessarily recommended for prod since there could be some dependency issues. However, if the dependencies line up across services (not such a bad thing to enforce, especially on smaller teams/code bases) we can make such a shortcut. Whether you run this way in prod to reduce OPS work or only run this mode in development to save time is up to you. If you have a low traffic app, running them together might make perfect sense. You would essentially have microservice ready code which can easily be split out into its own service when needed, but for now, it just shares the same CI pipeline, deploy script, and servers.

Undertow.builder()
        .addHttpListener(8083, "0.0.0.0", Middleware.common(RestServer.ROOT))
        .addHttpListener(8084, "0.0.0.0", Middleware.common(ContentTypesServer.ROUTES))
        .build()
        .start();

View on GitHub

curl -X POST "localhost:8083/users" -d '
{
  "email": "user1@test.com",
  "roles": ["USER"]
}
';
{
  "email" : "user1@test.com",
  "roles" : [ "USER" ],
  "dateCreated" : "2017-09-04"
}

curl -X GET "localhost:8083/users/user1@test.com"
{
  "email" : "user1@test.com",
  "roles" : [ "USER" ],
  "dateCreated" : "2017-09-04"
}
curl localhost:8084/helloWorldText
Hello World

Surprise, it's all working under a single Undertow instance but listening on multiple ports.

Microserviceservice

Let's take it one step too far just for fun. What if our service itself spun up new services inside the same JVM listening on new ports. Totally useless? Probably. Possible? Totally!

Undertow.builder()
        .addHttpListener(8085, "0.0.0.0", exchange -> {
    Integer port = Exchange.queryParams()
                           .queryParamAsInteger(exchange, "port")
                           .orElse(null);
    if (null != port) {
        try {
            HttpHandler handler = new ConstantStringHandler("web server with port " + port);
            Undertow.builder()
                    .addHttpListener(port, "0.0.0.0", handler)
                    .build()
                    .start();
        } catch (Exception e) {
            String message = "error trying to create web sertver with port " + port;
            Exchange.body().sendText(exchange, message);
            return;
        }
        Exchange.body().sendText(exchange, "server with port " + port + " created");
        return;
    }
    Exchange.body().sendText(exchange, "port cannot be null");
})
.build()
.start();

View on GitHub

curl localhost:9000
curl: (7) Failed to connect to localhost port 9000: Connection refused
curl localhost:8085/?port=9000
server with port 9000 created
curl localhost:9000
web server with port 9000

Bonus

All of the above examples are inside of a single main method and all run together in the same JVM. Click any of the GitHub links to view the source.

Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:
java ,undertow ,microservice

Published at DZone with permission of Bill O'Neil. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}