DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Building Scalable AI-Driven Microservices With Kubernetes and Kafka
  • Fast Deployments of Microservices Using Ansible and Kubernetes
  • 5 Ways a Service Mesh Can Better Manage App Data Sharing
  • Optimizing Kubernetes Costs With FinOps Best Practices

Trending

  • The Perfection Trap: Rethinking Parkinson's Law for Modern Engineering Teams
  • A Guide to Auto-Tagging and Lineage Tracking With OpenMetadata
  • Agile’s Quarter-Century Crisis
  • The Future of Java and AI: Coding in 2025
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Event-Driven Microservices With Vert.x and Kubernetes (Part 1 of 3)

Event-Driven Microservices With Vert.x and Kubernetes (Part 1 of 3)

Learn how to set up an event-driven microservices architecture using Vert.x's Verticle class. This introductory post covers how to use and configure Vert.x.

By 
Andy Moncsek user avatar
Andy Moncsek
·
Sep. 12, 16 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
27.4K Views

Join the DZone community and get the full member experience.

Join For Free

This is the first in a series of articles where we will build an entire microservice architecture using Vert.x and Kubernetes. Part one will focus on Vert.x and how to create microservices communicating via messages/events in a local environment. In part two, we will move the example from Part 1 to Kubernetes and we will learn how to configure Vert.x and its cluster provider Hazelcast to run in a Kubernetes cluster. Part three will extend the example with a more "enterprise-ready" architecture by mixing event-driven and “classical” REST (micro-)services using service-discovery and cluster partitioning. In part 3, I will also introduce vxms, a Vert.x extension with a JAX-RS like endpoint definition style, which provides many useful extensions to create resilient microservices on top of Vert.x.

Part 1: Create an Event-Driven Architecture With Vert.x

Before we start, I’ll give you a brief overview of the example we will build and deploy in Part 1 & 2. To keep this example simple, I decided to reuse the Vert.x/AngularJS example from the Vert.x example GitHub repository. This example is a small User admin page with one Verticle (see Vert.x intro) hosting the static content and handling the REST requests.

Architecture Overview

The original (Vert.x/AngularJS) example handles everything in one Verticle, so I decided to split it up to three different (micro-)service projects. Usually, we would divide our microservice across domains; because we have only one domain here (the User), we will create a UserRead service, a UserWrite service and a Gateway service. The Gateway service will also host the static content and act as a REST gateway to the other Verticles by using events for internal communication.


Image title

Before we proceed, let’s talk about the reasons to use Event-driven (micro-)services. One might be the asynchronous behavior and the absence of an active service discovery. Additional advantages are, that we can dynamically add new subscribers/services and easily add resilient aspects to our code and the whole architecture. In reality, you shouldn’t focus only on one architecture style. In many cases, a simple request/response is the way to go and for many other cases handling REST calls is the proper approach.

Traditionally event-driven architectures are handled by a message broker. The approach we pursue here is to use an in-memory data grid with its capabilities for auto-discovery, shared data, and events.

Image title

In a scenario in which a message-broker is involved, you need to ensure its reliability and each client must know the address of the broker. In contrast to an in-memory data grid approach, were each component is scanning for nodes in the network. As soon as one remote peer was found, the node knows also the rest about the topology. This approach fits quite well in a cloud scenario where new nodes/containers are often created and destroyed.

A Short Introduction to Vert.X

This in-memory data grid approach is used by Vert.x (by the help of Hazelcast), a toolkit for reactive applications on the JVM. Vert.x is event-driven, non-blocking, lightweight, and fast. It is polyglot and you can use it for many different scenarios from embedded to web framework.

In this article series, we will use Vert.x as a web framework and deploy so-called "Verticles" to handle REST-requests, static content and messages. A Verticle is comparable to a Servlet, while the Servlet-request will be handled in a request thread (taken from a pool), each Verticle instance has its own event-loop thread, which should not be blocked. Since not all Java APIs are non-blocking, there are ways to execute blocking code inside Verticles or to deploy the instances in a worker thread mode.

Create a Verticle

A Verticle Class extends an AbstractVerticle:

public class MyVerticle extends AbstractVerticle {
    @Override
    public void start(Future<Void> start) throws Exception {
    start.complete();
    }
}


Optionally you can overwrite the *start* method and complete the deployment manually. This gives you the ability to coordinate other tasks during startup, with the goal to finish the whole deployment when other asynchronous tasks are done.

Deploy a Verticle

The deployment of a Verticle is very easy. During development you may prefer to add a *main* method to deploy the Verticle:

public static void main(String[] args) {
        // simple deploy
Vertx.vertx().deployVerticle(MyVerticle.class.getName());

        // or with options and 10 instances
Vertx.vertx().
deployVerticle(MyVerticle.class.getName(),new DeploymentOptions().
setConfig(new JsonObject().
put("foo", "bar")).setInstances(10));
 } 


Since we need the clustered environment in this article series, the deployment looks like this:

public static void main(String[] args) {
    Vertx.clusteredVertx(new VertxOptions().setClustered(true), cluster -> {
        if (cluster.succeeded()) {
            final Vertx result = cluster.result();
            result.deployVerticle(UsersReadFromMongo.class.getName());
        }
    }
}


Once the development is finished, you typically create a fat-jar (using maven shade) and deploy it by using command line:

java -jar MyVerticle.jar -instances=4 -cluster


Using the Event Bus

The event bus is the nervous system of Vert.x, and each Vert.x instance creates one single event bus instance. The event bus allows different parts of your application to communicate with each other irrespective of what language they are written in, and whether they’re in the same Vert.x instance, or in a different one. In clustered mode, the event bus forms a distributed peer-to-peer messaging system spanning multiple server nodes and multiple browsers.

To register an event handler in Vert.x, we can do following:

EventBus eb = vertx.eventBus();
eb.consumer("my.endpoit.one", message -> {
    System.out.println("I have received a message: " + message.body());
});


This code can be embedded in any Java application, but, be aware that the handler will be executed on the event loop thread of Vert.x (UI, transactions,…).

The event bus supports publish/subscribe, but also point to point / request-response messaging. Messages are published to an address and routed to one of the registered handlers (in case of p2p). If more than one handler is registered, a single handler will be selected by a non-strict round robin mechanism. Optionally you can define a reply handler when sending a message, so the receiver can reply directly.

The Receiver
EventBus eb = vertx.eventBus();
eb.consumer("my.endoit.one", message -> {
  message.reply(„hello“);
});


The Sender
eventBus.send("my.endoit.one", „Hi!“, ar -> {
  if (ar.succeeded()) {
    System.out.println("Received reply: " + ar.result().body());
  }
});


Create a REST Service Endpoint

In Vert.x, you can easily create different types of sockets (TCP, UDP, and HTTP(S)). For a REST service, we need to create a HTTP server to handle all requests directly. Because this might be quite low-level to work with, we use the Vert.x-Web extension to define REST services in Vert.x:

    @Override
    public void start(Future<Void> startFuture) throws Exception {
        Router router = Router.router(vertx);
        // define some REST API
        router.get("/api/users/:id").handler(handler -> {
            String id= handler.request().getParam("id");
            handler.response().end("OK");
        });
        vertx.createHttpServer().
              requestHandler(router::accept).
              listen(8080,"0.0.0.0");
        startFuture.complete();
    }


Bring it all Together

Now that we have learned some basics of Vert.x (needed for this article), we create three Maven projects for the Read-, Write-, and Gateway-Verticle (or clone the complete code from here). You can create a Vert.x project by using a maven *java quickstart project* like this:

mvn archetype:create 
    -DgroupId=org.jacpfx.demo
    -DartifactId=read
    -DarchetypeArtifactId=maven-archetype-quickstart

Now add following dependencies:

<dependencies>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-mongo-client</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-hazelcast</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>${vertx.version}</version>
        </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>${vertx.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                      implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <Main-Class>io.vertx.core.Launcher</Main-Class>
                                        <Main-Verticle>org.jacpfx.demo.service.ReadVerticle</Main-Verticle>
                                    </manifestEntries>
                                </transformer>
                            </transformers>
                            <artifactSet/>
                            <outputFile>${project.build.directory}/${project.artifactId}-fat.jar
                            </outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


Create the ReadVerticle

The *ReadVerticle* uses a Vert.x MongoDB-Client to access the user data. Furthermore, it defines two consumers to retrieve all users and users by id. We simply pass the user ID, retrieved from the REST call, and pass it inside the message body as a string to the *ReadVerticle*. In a more sophisticated example, you should use a transfer object and pass JSON or binary values.

public class ReadVerticle extends AbstractVerticle {
    private MongoClient mongo;


    @Override
    public void start(Future<Void> startFuture) throws Exception {
        mongo = …;
        vertx.eventBus().consumer("/api/users", getAllUsers());
        vertx.eventBus().consumer("/api/users/:id", getUserById());
        startFuture.complete();
    }

…
    private Handler<Message<Object>> getUserById() {
        return handler -> {
            final String id = handler.body().toString();
            mongo.findOne("users", new JsonObject().put("_id", id), null, lookup -> getDBResultAndReply(handler, lookup));
        };
    }

    private void getDBResultAndReply(Message<Object> handler, AsyncResult<JsonObject> lookup) {
        if (lookup.failed()) {
            handler.fail(500, "lookup failed");
            return;
        }
        JsonObject user = lookup.result();
        if (user == null) {
            handler.fail(404, "no user found");
        } else {
            handler.reply(user.encode());
        }
    }

}


The *getDBResultAndReply* methods are called when the MongoDB search operation is finished. If no result was found or the lookup failed, we respond with an error reply (500 or 404) to the caller, otherwise we reply with a message containing the requested User.

Build the Gateway and Add the UI

The *GatewayVerticle* serves the static content (AngularJS) and provides a REST API, used by the front end. All REST requests are passed from the gateway to the Read- / Write-Verticle. The response from those Verticles will be written to the REST response. Keep in mind that Vert.x is reactive, so you define handlers, which are called on a specific event, while the entire process is non-blocking (no active waiting).

public class GatewayVerticle extends AbstractVerticle {

    @Override
    public void start(Future<Void> startFuture) throws Exception {
        Router router = Router.router(vertx);
        // define some REST API
        router.get("/api/users/:id").handler(this::getUserById);
…
        // enable static contant handling 
       router.route().handler(StaticHandler.create());
   vertx.createHttpServer().
   requestHandler(router::accept).
   listen(9090,"0.0.0.0");

        startFuture.complete();
    }

    private void getUserById(RoutingContext ctx) {
       // route REST request to event bus
        vertx.eventBus().
        send("/api/users/:id", ctx.request().getParam("id"),
        responseHandler -> defaultResponse(ctx, responseHandler));
    }
…   
    private void defaultResponse(RoutingContext ctx, AsyncResult<Message<String>> responseHandler) {
        if (responseHandler.failed()) {
            ctx.fail(500);
        } else {
           // respond to REST request
            final Message<String> result = responseHandler.result();
            ctx.response().end(result.body());
        }
    }
}


With *router.route().handler(StaticHandler.create());* we tell Vert.x to serve all static contents from the *webroot* folder, typically located in *src/main/resources*. The AngularJS application itself uses relative paths to access the REST endpoints. If your UI runs from a different host you can define a CORSE handler in Vert.x too.

The interesting part here is the *defaultResponse* method. It checks whether the event bus request fails or succeeds. On a success, you pass the result message to the REST response. On an error, you return an HTTP 500 response;

But what is the error case here? Remember the error response we defined in the *ReadVerticle* when the MongoDB lookup fails or no result was found? In this case, the responseHandler here would fail, and you return an HTTP 500 response. The other case (relevant for cloud deployments) is that no consumer was registered in the cluster (or the node is not accessible). In that case, then the responseHandler would also fail.

To summarize it, we can handle all failures starting from a MongoDB "connection error", "no results error," to "no service available error." It is up to you to decide what you do with the ability to handle those errors. Remember, resilient means not only the capability technical wise, it means you must know what you want to do instead.

Run all Verticles in a Local Cluster

Now that we’ve finished the implementation (or cloned it from GitHub), we want to run the application and all the services. For local testing, I assume you have a running MongoDB and built all three projects with *mvn clean package*. If you cloned the GitHub repo you can try the *main* method to start each verticle. If you prefer the command line, do following:

cd frontend-verticle && mvn clean package && java -jar target/frontend-verticle-fat.jar -cluster -conf local.json 


The parameter *-conf local.json* is specific to this showcase to handle the local MongoDB access. If you have configured the MongoDB connection on your own, you don’t have to specify this. The *-cluster* parameter activates the cluster deployment in Vert.x, so *Hazelcast* will be activated, using multicast to find other nodes in the network.

When all your Verticles are started, you should see a log output like this:

Members [3] {
    Member [172.20.46.23]:5702
    Member [172.20.46.23]:5701
    Member [172.20.46.23]:5703 this
}


In this log output, we see, that Hazelcast has found three node members (the gateway, the read-, and the write-Verticle). Now you can open the browser and test the application (http://localhost:8181).

As long as you are operating in a multicast-enabled network, you do not need to provide additional configurations to run Vert.x/Hazelcast in a clustered setup.

In container environments like Kubernetes multicast is not always available, so you have to provide additional steps to run this demo. In part two, I will proceed with a brief introduction of Hazelcast and Kubernetes and show all steps to run this demo in Kubernetes.

Vert.x Kubernetes microservice

Opinions expressed by DZone contributors are their own.

Related

  • Building Scalable AI-Driven Microservices With Kubernetes and Kafka
  • Fast Deployments of Microservices Using Ansible and Kubernetes
  • 5 Ways a Service Mesh Can Better Manage App Data Sharing
  • Optimizing Kubernetes Costs With FinOps Best Practices

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!