|
From Vert.x in Action by Julien Ponge This article talks about what verticles are and delves a bit into how they work and how to write them. |
Take 37% off Vert.x in Action. Just enter fccponge into the discount code box at checkout at manning.com.
Relevant source code for this article can be found here: https://github.com/jponge/vertx-in-action/tree/master/chapter2
A verticle is the fundamental processing unit in Vert.x. The role of a verticle is to encapsulate a technical functional unit for processing events such as exposing an HTTP API and responding to requests, providing a repository interface on top of a database, or issuing requests to a third-party system. Much like components in other technologies such as Enterprise Java Beans, verticles can be deployed and they have a life-cycle.
Readers familiar with the actor concurrency model will find similarities between Vert.x verticles and actors. Actors are a model where autonomous entities (the actors) exclusively communicate with other entities by sending and responding to messages. The similarities between Vert.x verticles and actors isn’t fortuitous coincidence: verticles have a private state that may be updated when receiving events, they can deploy other verticles, and they can communicate via message-passing. Verticles don’t necessarily follow the orthodox modern definition of actors, but it’s fair to consider Vert.x at least as being inspired by actors.
Let’s now dive into writing verticles.
Writing a verticle
Because verticles are a key concept in Vert.x, we’ll look into how they work. Before that, let’s write a small verticle that processes two types of events: periodic timers and HTTP requests.
Preparing the project
We’ll use a common project for all samples in this article, using the Gradle project descriptor of listing 1.
Listing 1. Gradle build.gradle.kts
for the samples
plugins { java } repositories { mavenCentral() } dependencies { implementation("io.vertx:vertx-core:3.8.0") ❶ implementation("ch.qos.logback:logback-classic:1.2.3") ❷ } tasks.create<JavaExec>("run") { ❸ main = project.properties.getOrDefault("mainClass", "chapter2.hello.HelloVerticle") as String classpath = sourceSets["main"].runtimeClasspath systemProperties["vertx.logger-delegate-factory-class-name"] = "io.vertx.core.logging.SLF4JLogDelegateFactory" ❹ } java { sourceCompatibility = JavaVersion.VERSION_1_8 }
❶ This is the Vert.x core library dependency.
❷ The logback-classic
dependency provides the SLF4J
logger API and the logback
implementation.
❸ This allows running samples with Gradle from the command line.
❹ This ensures that Vert.x itself also uses SLF4J
logging.
The Gradle build is a simple one for a Java project. Because we have several examples to run, we don’t rely on the Gradle application
plugin but define our own custom run
task where we can pass the name of the class to execute. We also take advantage of it to ensure that logging is properly configured and unified to SLF4J.
Listing 2. Logback configuration to reduce Netty verbosity
<configuration> ❶ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%level [%thread] %logger{0} - %msg%n</pattern> ❷ </encoder> </appender> <logger name="io.netty" level="warn"/> ❸ <root level="debug"> <appender-ref ref="STDOUT"/> </root> </configuration>
❶ This defines an appender to send events to the console.
❷ The pattern defines how the log events look like.
❸ We drop Netty log events which are more verbose than warnings.
Vert.x uses Netty and logging in Netty is quite verbose with the default Logback configuration. We can reduce the amount of log entries by creating a src/main/resources/logback.xml
file and adding the configuration as in listing 2. To make the log samples shorter in this article we also removed event dates and shortened logger class names ($logger{0}
). Please refer to the Logback documentation to understand how to configure it [LogbackDoc].
The verticle class
The whole verticle and application fits in the Java class of listing 3.
Listing 3. A sample verticle
package chapter2.hello; import io.vertx.core.AbstractVerticle; import io.vertx.core.Vertx; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloVerticle extends AbstractVerticle { private final Logger logger = LoggerFactory.getLogger(HelloVerticle.class); private long counter = 1; @Override public void start() { vertx.setPeriodic(5000, id -> { ❶ logger.info("tick"); }); vertx.createHttpServer() .requestHandler(req -> { ❷ logger.info("Request #{} from {}", counter++, req.remoteAddress().host()); req.response().end("Hello!"); }) .listen(8080); logger.info("Open http://localhost:8080/"); } public static void main(String[] args) { Vertx vertx = Vertx.vertx(); ❸ vertx.deployVerticle(new HelloVerticle()); ❹ } }
❶ This defines a periodic task every five seconds.
❷ The HTTP server calls this handler on every request.
❸ We need a global Vert.x instance.
❹ This is the simplest way to deploy a verticle.
This verticle defines two event handlers: one for periodic tasks every five seconds, and one for processing HTTP requests in a HTTP server. The main
method instantiates a global Vert.x instance, and deploys an instance of the verticle.
Defining a verticle code in Java is typically done by specializing the AbstractVerticle
class. There exists a Verticle
interface that one could in theory implement, but AbstractVerticle
provides all the event processing, configuration and execution plumbing that Vert.x users need.
Because Vert.x is a library and not a framework, you can create a Vert.x instance from a main
method, or from any other class and then deploy verticles.
The life-cycle of a verticle is made of start and stop events. The AbstractVerticle
class provides start
and stop
methods that can be overridden. By default, these methods do nothing.
- The
start
method typically contains setup and handlers’ initialization, like setting a periodic task handler and starting a HTTP server in listing 3. - The
stop
method is implemented when housekeeping tasks are required, such as closing open database connections.
Running and first observations
The application can be launched as a regular Java application by running the main
method either from an integrated development environment or from the command-line. To run on the command line using Gradle, you can use the following command:
gradle run -PmainClass=chapter2.hello.HelloVerticle
In some of the remaining samples we shortened class definitions. We removed package definitions, imports, and main
methods which are similar to that of listing 3. When in doubt, please consult the full source code of the samples.
Once the application runs, we can perform a few HTTP requests at http://localhost:8080/ with a web browser or by using command-line tools such as cUrl
and HTTPie
. The logs shall be similar to that of listing 4.
Listing 4. Sample log output when running HelloVerticle
INFO [vert.x-eventloop-thread-0] HelloVerticle - Open http://localhost:8080/ ❶ INFO [vert.x-eventloop-thread-0] HelloVerticle – tick ❷ INFO [vert.x-eventloop-thread-0] HelloVerticle - Request #1 from 0:0:0:0:0:0:0:1 ❸ INFO [vert.x-eventloop-thread-0] HelloVerticle - Request #2 from 0:0:0:0:0:0:0:1 INFO [vert.x-eventloop-thread-0] HelloVerticle - Request #3 from 0:0:0:0:0:0:0:1 INFO [vert.x-eventloop-thread-0] HelloVerticle - Request #4 from 0:0:0:0:0:0:0:1 INFO [vert.x-eventloop-thread-0] HelloVerticle – tick
❶ The HTTP server is now ready.
❷ A periodic task event log.
❸ A HTTP request event log.
The Logback configuration that we use shows the name of the thread associated with an event. We can already check an important property of Vert.x verticles in log entries: event processing happens on a single event-loop thread. Both the periodic tasks and HTTP requests processing happen on a thread that appears as vert.x-eventloop-thread-0
in the logs.
An obvious benefit with this design is that a verticle instance is always executing event processing on the same thread, and there’s no need for using thread synchronization primitives. In a multi-threaded design updating the counter
field requires either a synchronized
block or the usage of java.util.concurrent.AtomicLong
. No such issues here and a plain long
field can be safely used.
Preparation methods such as createHttpServer
or setTimer
may be called from a non-Vert.x thread. This may happen when directly using a Vertx
object without a verticle, or when writing unit tests. This isn’t a problem as usage of the Vertx
class methods is thread-safe.
Figure 1. Execution of listing 3
Figure 1 shows the (simplified) interactions between the verticle, the handlers, Vert.x and the event sources. Each arrow represents a method call between the participants. For instance, HelloVerticle
creates a periodic task handler by calling setPeriodic
on the Vertx
object, which in turns creates a periodic task using a Vert.x-internal timer. In turn, the timer periodically calls back the timerHandler
handler in HelloVerticle
.
Note that we represented the calls to requestHandler
and listen
as being to the Vertx
object as a shortcut, but in reality they’re on an object that implements the HttpServer
interface. The class is internal to Vert.x, and because it doesn’t serve the diagram to add another participant we merged it into Vertx
.
That’s all for this article.
If you want to learn more about the book, check it out on our browser-based liveBook reader here and see this slide deck.