Managing the Vert.x core API

For the purpose of learning about the Vert.x core API, we will be using a modified version of our basic customer service application, which we discussed in Chapter 4, Adding Web Interfaces to Quarkus Services. You can find the source code for this example of the Chapter09/core/customer-service folder in this book's GitHub repository. We recommend that you import the project into your IDE.

Now, let's dive straight into the code. Since the Vert.x core API is based around the callback mechanism, in order to leverage asynchronous and non-blocking APIs, for our customer service example we have added two functions that will read and write a list of customers from the filesystem in JSON format. Where shall we write our customer list? The answer is into the application.properties file, which defines a property named file.path, which is where the customer list will be written:

file.path=/tmp/customer.json

Now, let's look at the code. The core class that's responsible for providing customer data is CustomerRepository. There we'll inject an instance of io.vertx.core.Vertx at this point. We will also inject the path where the data will be stored:

public class CustomerRepository {
@Inject io.vertx.core.Vertx vertx;
@ConfigProperty(name = "file.path" )
String path;

Now comes the interesting bit, that is, writing a method that uses the vertx instance to flatten our list of customers on the filesystem:

public CompletionStage<String> writeFile( ) {

JsonArrayBuilder jsonArray = javax.json.Json.createArrayBuilder();
for (Customer customer:customerList) {
jsonArray.add(javax.json.Json.createObjectBuilder().
add("id", customer.getId())
.add("name", customer.getName())
.add("surname", customer.getSurname()).build());
}

JsonArray array = jsonArray.build();
CompletableFuture<String> future = new CompletableFuture<>();

vertx.fileSystem().writeFile(path, Buffer.buffer(array

.toString()), handler -> {
if (handler.succeeded()) {
future.complete("Written JSON file in " +path);
} else {
System.err.println("Error while writing in file:
"
+ handler.cause().getMessage());
}
});
return future;
}

The first thing you may have noticed is the CompletionStage method's signature. If you have been programming async Java code, you may be familiar with the java.util.concurrent.Future API. It's used to do the following:

  • Check whether the execution has completed via the isDone() method
  • Cancel the execution using the cancel() method
  • Fetch the result of the execution using the blocking get() method

The major limitation of this approach is that the caller cannot manually complete the task, nor can it chain multiple Future executions.

On the other hand, CompletionStage is based on the concept of stages, which are thought of as multiple intermediate computations and may or may not be asynchronous. In any case, we have to complete them before we reach the final result. These intermediate computations are known as completion stages.

By using the CompletionStage stage, you can easily address the java.util.concurrent.Future API's limitations by doing the following:

  • Manually completing CompletableStage using complete(T value)
  • Chaining multiple CompletableStage in a block

Let's get back to our example. Once we have created JsonArray out of our customer list, we can access our FileSystem using the Vert.x core API. We can also register a handler that is in charge of completing our CompletionStage as soon as the file has been successfully written.

Let's take a look at the readFile method, which is in charge of reading the file that contains the customer list:

public CompletionStage<String> readFile() {
CompletableFuture<String> future = new CompletableFuture<>();
long start = System.nanoTime();

// Delay reply by 100ms
vertx.setTimer(100, l -> {
// Compute elapsed time in milliseconds
long duration = MILLISECONDS.convert(System.nanoTime() -
start, NANOSECONDS);

vertx.fileSystem().readFile(path, ar -> {
if (ar.succeeded()) {
String response = ar.result().toString("UTF-8");
future.complete(response);
} else {
future.complete("Cannot read the file: " +
ar.cause().getMessage());
}
});

});

return future;
}

The readFile method is intentionally a bit more complex. As a matter of fact, we have chained two different stages into it. The first one executes a one-time timer that will fire the next execution in 100 ms. Timers are a core construct of Vert.x and should be used wherever you want to delay the execution of some code or execute it repeatedly:

vertx.setPeriodic(1000, l -> {
// This code will be called every second
System.out.println("timer fired!");
});

In any case, timers are how you can delay the execution in Vert.x terms in place of other mechanisms, such as Thread.sleep, which would block the event loop and therefore should never, ever be used in the Vert.x context. 

If you forget our gentle warning, Vert.x will remind you each time you attempt to use a blocking code in the Vert.x context with a log message similar to Thread vertx-eventloop-thread-1 has been blocked for 22258 ms.

The remaining part of the readFile method does exactly the opposite of the writeFile method; that is, it reads the JSON file and completes the stage as soon as the file has been read.

In order to expose this feature to the client application, we have added two wrapper methods to our CustomerEndpoint class in order to expose the functions via the REST API:

@GET
@Path("writefile")
@Produces("text/plain")
public CompletionStage<String> writeFile() {
return customerRepository.writeFile();
}

@GET
@Path("readfile")
public CompletionStage<String> readFile() {
return customerRepository.readFile();
}

It's worth noting that the writeFile method produces text information since it's supposed to return a simple text message to the caller. On the other hand, the readFile method relies on the class' default application/json format to display the JSON text file.

Now, let's move on to the client-side. We can easily capture the CompletionStage event using two more AngularJS handlers, which will capture the result as soon as it's available:

$scope.writefile = function () {

$http({
method: 'GET',
url: SERVER_URL+'/writefile'
}).then(_successStage, _error);
};

scope.readfile = function () {

$http({
method: 'GET',
url: SERVER_URL+'/readfile'
}).then(_successStage, _error);
};

function _successStage(response) {
_clearForm()
$scope.jsonfile = JSON.stringify(response.data);
}

Both functions will be triggered with the addition of two simple buttons to our home page:

<a ng-click="writefile()" class="myButton">Write File</a>  
<a ng-click="readfile()" class="myButton">Read File</a>

Besides doing this, we have also added a div section to our HTML schema, which is where information will be displayed:

<div ng-app="displayfile"  >
<span ng-bind="jsonfile"></span>
</div>

Without further ado, let's build and run the application with the following command:

mvn install quarkus:dev

The following is our new UI, which includes the Read File and Write File buttons. We have just saved a set of Customer objects, as shown in the following screenshot:

Conversely, if we hit the Read File button, its content will be displayed in the lower div of the page in JSON format:

We have completed the first round with Vert.x core. Now, let's move on and look at using Vert.x with ReactiveX (RxJava).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.79.63