Vertices is a Scala library that provides extension methods for the Eclipse Vert.x APIs.
The basic idea of this library is to provide replacements for Vert.x methods which accept callbacks. This makes it easier to use the diverse functionality provided by the Vert.x libraries while writing idiomatic Scala code.
The new methods make use of the Task type from the excellent Monix library.
The Vert.x library provides a SharedData object which we can use to store and retrieve named AsyncMap objects.
Using the original Vert.x APIs we would write code to access this data like so:
import io.vertx.core._
import scala.concurrent.{ Await, Promise }
import scala.concurrent.duration._
val vertx = Vertx.vertx
// vertx: io.vertx.core.Vertx = io.vertx.core.impl.VertxImpl@69ffdaa8
val resultPromise = Promise[String]()
// resultPromise: scala.concurrent.Promise[String] = Future(<not completed>)
val sharedData = vertx.sharedData
// sharedData: io.vertx.core.shareddata.SharedData = io.vertx.core.shareddata.impl.SharedDataImpl@70439c3
sharedData.getAsyncMap[String, String]("example", getMapResult => {
if (getMapResult.succeeded) {
val asyncMap = getMapResult.result
asyncMap.put("key", "value", putResult => {
if (putResult.succeeded) {
asyncMap.get("key", getResult => {
if (getResult.succeeded) {
resultPromise.success(getResult.result)
} else {
resultPromise.failure(getResult.cause)
}
})
} else {
resultPromise.failure(putResult.cause)
}
})
} else {
resultPromise.failure(getMapResult.cause)
}
})
Await.result(resultPromise.future, 20.seconds)
// res1: String = value
As you can see this is a perfect demonstration of "callback hell".
Using this library we can write the code above as follows:
import monix.execution.Scheduler
import vertices._
import vertices.core._
implicit val scheduler: Scheduler = new VertxScheduler(vertx)
// scheduler: monix.execution.Scheduler = vertices.core.VertxScheduler@4131f6db
val resultTask = for {
asyncMap <- sharedData.getAsyncMapL[String, String]("example")
_ <- asyncMap.putL("key", "value")
value <- asyncMap.getL("key")
} yield value
// resultTask: monix.eval.Task[String] = Task.FlatMap$482031947
Await.result(resultTask.runToFuture, 20.seconds)
// res2: String = value
We can also convert Vert.x ReadStream objects to Monix Observables.
The example below uses the Vert.x Event Bus to define an event bus consumer that echoes messages back to the sender in all-caps:
import cats.syntax.apply._
// import cats.syntax.apply._
val messageStream = vertx.eventBus.consumer[String]("echo")
// messageStream: io.vertx.core.eventbus.MessageConsumer[String] = io.vertx.core.eventbus.impl.HandlerRegistration@32107bba
val echoMessagesExuberantly = for {
messageObservable <- messageStream.toObservable(vertx)
_ <- messageObservable.foreachL(msg => msg.reply(msg.body.toUpperCase))
} yield ()
// echoMessagesExuberantly: monix.eval.Task[Unit] = Task.FlatMap$936177086
echoMessagesExuberantly.runToFuture
// res3: monix.execution.CancelableFuture[Unit] = Async(Future(<not completed>),monix.eval.internal.TaskConnection$Impl$$anon$1@2b009051)
val sendAMessage = vertx.eventBus.
requestL[String]("echo", "hello").
foreachL(msg => println(msg.body))
// sendAMessage: monix.eval.Task[Unit] = Task.Map$368375378
val demoTask =
sendAMessage *> vertx.closeL
// demoTask: monix.eval.Task[Unit] = Task.FlatMap$543433178
Await.result(demoTask.runToFuture(Scheduler.global), 20.seconds)
// HELLO
The library is published for Scala 2.12 and 2.13.
The artifact names resemble those of the original Vert.x artifacts.
They are listed below using the categories defined in the Vert.x Documentation.
SBT dependency coordinates:
val verticesVersion = "0.1.2"
// Vert.x core
"io.github.davidgregory084" %% "vertices-core" % verticesVersion
// Vert.x web
"io.github.davidgregory084" %% "vertices-web" % verticesVersion
"io.github.davidgregory084" %% "vertices-web-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-web-api-contract" % verticesVersion
// Data access
"io.github.davidgregory084" %% "vertices-mongo-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-redis-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-cassandra-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-sql-common" % verticesVersion
"io.github.davidgregory084" %% "vertices-jdbc-client" % verticesVersion
// Microservices
"io.github.davidgregory084" %% "vertices-service-discovery" % verticesVersion
"io.github.davidgregory084" %% "vertices-circuit-breaker" % verticesVersion
"io.github.davidgregory084" %% "vertices-config" % verticesVersion
// MQTT
"io.github.davidgregory084" %% "vertices-mqtt" % verticesVersion
// Authentication and Authorisation
"io.github.davidgregory084" %% "vertices-auth-common" % verticesVersion
"io.github.davidgregory084" %% "vertices-auth-oauth2" % verticesVersion
"io.github.davidgregory084" %% "vertices-auth-mongo" % verticesVersion
// Messaging
"io.github.davidgregory084" %% "vertices-stomp" % verticesVersion
"io.github.davidgregory084" %% "vertices-rabbitmq-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-amqp-bridge" % verticesVersion
// Integration
"io.github.davidgregory084" %% "vertices-kafka-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-mail-client" % verticesVersion
"io.github.davidgregory084" %% "vertices-consul-client" % verticesVersion
// Event Bus Bridge
"io.github.davidgregory084" %% "vertices-tcp-eventbus-bridge" % verticesVersion
// Devops
"io.github.davidgregory084" %% "vertices-health-check" % verticesVersion
Mill dependency coordinates:
def verticesVersion = T { "0.1.2" }
// Vert.x core
ivy"io.github.davidgregory084::vertices-core:${verticesVersion()}"
// Vert.x web
ivy"io.github.davidgregory084::vertices-web:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-web-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-web-api-contract:${verticesVersion()}"
// Data access
ivy"io.github.davidgregory084::vertices-mongo-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-redis-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-cassandra-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-sql-common:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-jdbc-client:${verticesVersion()}"
// Microservices
ivy"io.github.davidgregory084::vertices-service-discovery:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-circuit-breaker:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-config:${verticesVersion()}"
// MQTT
ivy"io.github.davidgregory084::vertices-mqtt:${verticesVersion()}"
// Authentication and Authorisation
ivy"io.github.davidgregory084::vertices-auth-common:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-auth-oauth2:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-auth-mongo:${verticesVersion()}"
// Messaging
ivy"io.github.davidgregory084::vertices-stomp:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-rabbitmq-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-amqp-bridge:${verticesVersion()}"
// Integration
ivy"io.github.davidgregory084::vertices-kafka-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-mail-client:${verticesVersion()}"
ivy"io.github.davidgregory084::vertices-consul-client:${verticesVersion()}"
// Event Bus Bridge
ivy"io.github.davidgregory084::vertices-tcp-eventbus-bridge:${verticesVersion()}"
// Devops
ivy"io.github.davidgregory084::vertices-health-check:${verticesVersion()}"
The naming strategy for extension methods follows that of Monix: the new methods which return Task are suffixed with the letter L
since the underlying task is not executed right away (in other words that it is "lazy").
// Instead of the io.vertx.core.file.AsyncFile method
def write(data: Buffer, handler: Handler[AsyncResult[Void]]): AsyncFile
// We can use this extension method from vertices.core
def writeL(data: Buffer): Task[Unit]
// Instead of the io.vertx.core.dns.DnsClient method
def resolveMX(name: String, handler: Handler[AsyncResult[List[MxRecord]]]): DnsClient
// We can use this extension method from vertices.core
def resolveMXL(name: String): Task[List[MxRecord]]
Since it's not possible to decorate a Java class with new static methods, replacements for static methods reside within a companion object named after the original class with Functions
appended to the end. For example, io.vertx.core.Vertx.clusteredVertx
has a matching vertices.core.VertxFunctions.clusteredVertxL
function.
// Instead of the io.vertx.ext.auth.oauth2.providers.GoogleAuth static method
def create(vertx: Vertx, url: String, handler: Handler[AsyncResult[OAuth2Auth]]): Unit
// We can use this function from vertices.auth.GoogleAuthFunctions
def createL(vertx: Vertx, url: String): Task[OAuth2Auth]
Extension methods are made available by importing from the package corresponding to each module. The package names are selected to resemble those used by the original APIs.
// Vert.x core
import vertices.core._
// Vert.x web modules
import vertices.web._
import vertices.web.client._
import vertices.web.api.contract._
// Vert.x data access
import vertices.mongo._
import vertices.redis.client._
import vertices.cassandra._
import vertices.sql._
import vertices.jdbc._
// Vert.x microservices
import vertices.servicediscovery._
import vertices.circuitbreaker._
import vertices.config._
// Vert.x MQTT
import vertices.mqtt._
// Vert.x authentication and authorisation
import vertices.auth._
import vertices.auth.oauth2._
import vertices.auth.mongo._
// Vert.x messaging
import vertices.stomp._
import vertices.rabbitmq._
import vertices.amqpbridge._
// Vert.x integration
import vertices.kafka.client._
import vertices.mail._
import vertices.consul._
// Vert.x event bus bridge
import vertices.eventbus.bridge.tcp._
// Vert.x devops
import vertices.healthchecks._
The root package vertices
also provides some useful extension methods and type class instances for Vert.x types.
Q. Why is <some module>
missing?
A. The stable modules that have Handler
operations have been added. If there are new modules that you need please raise a PR.
Q. Why is <some method>
missing from the generated code?
A. The Vert.x code generation process relies on annotations in the original Java code. Sometimes these annotations are missing for Handler
methods that could be wrapped by vertices. The solution is to raise a PR against the corresponding Vert.x project to add the annotations (see example).
Contributors are expected to follow the Scala Code of Conduct while participating on Github and any other venues associated with the project.
Thanks are due to Alexandru Nedelcu (@alexandru) for the Monix library, which makes writing asynchronous code in Scala an absolute pleasure.
All code in this repository is licensed under the Apache License, Version 2.0. See LICENSE.