The RESTful web service

In this section, we will explore how to expose the RESTful API.

The main unit in Vert.x is the verticle—using this class, we will be able to handle, always in an asynchronous way, requests for our data.

We will create two classes: a helper needed to build the responses associated with specific HTTP verbs, and the verticle class, which exposes the API.

The ActionHelper class is an easy utility class that helps us build responses:

public class ActionHelper {
/**
* Returns a handler writing the received {@link AsyncResult} to the routing
* context and setting the HTTP status to the given status.
*
* @param context the routing context
* @param status the status
* @return the handler
*/
private static <T> Handler<AsyncResult<T>> writeJsonResponse(RoutingContext context, int
status) {
return ar -> {
if (ar.failed()) {
if (ar.cause() instanceof NoSuchElementException) {
context.response().setStatusCode(404).end(ar.cause().getMessage());
} else {
context.fail(ar.cause());
}
} else {
context.response().setStatusCode(status).putHeader("content-type",
"application/json;charset=utf-8")
.end(Json.encodePrettily(ar.result()));
}
};
}

public static <T> Handler<AsyncResult<T>> ok(RoutingContext rc) {
return writeJsonResponse(rc, 200);
}

public static <T> Handler<AsyncResult<T>> created(RoutingContext rc) {
return writeJsonResponse(rc, 201);
}

public static Handler<AsyncResult<Void>> noContent(RoutingContext rc) {
return ar -> {
if (ar.failed()) {
if (ar.cause() instanceof NoSuchElementException) {
rc.response().setStatusCode(404).end(ar.cause().getMessage());
} else {
rc.fail(ar.cause());
}
} else {
rc.response().setStatusCode(204).end();
}
};
}

private ActionHelper() {
}
}

This class uses an instance of RoutingContext to handle requests and build a response, with HTTP return code and an object representation, based on the HTTP verb used by the request.

This helper class will be used by the verticle class, in our case FootballPlayerVerticle, which implements routes and exposes APIs.

Let's start analyzing it. In the first part of code, we will perform the following:

  1. Create an HTTP server that listens to the 8080 port.
  1. Make it available to handle requests to the path to our APIs.
  1. Also, define a way to create our database tables, and preload data:
public class FootballPlayerVerticle extends AbstractVerticle {

private JDBCClient jdbc;

@Override
public void start(Future<Void> fut) {
// Create a router object.
Router router = Router.router(vertx);

// Point 2
router.route("/").handler(routingContext -> {
HttpServerResponse response = routingContext.response();
response.putHeader("content-type", "text/html")
.end("<h1>Football Player Vert.x 3 microservice application</h1>");
});

router.get("/footballplayer").handler(this::getAll);
router.get("/footballplayer/show/:id").handler(this::getOne);
router.route("/footballplayer*").handler(BodyHandler.create());
router.post("/footballplayer/save").handler(this::addOne);
router.delete("/footballplayer/delete/:id").handler(this::deleteOne);
router.put("/footballplayer/update/:id").handler(this::updateOne);

// Point 3
ConfigStoreOptions fileStore = new ConfigStoreOptions().setType("file")
.setFormat("json").setConfig(new JsonObject().put("path",
"src/main/conf/my-application-conf.json"));

ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);

// Start sequence:
// 1 - Retrieve the configuration
// |- 2 - Create the JDBC client
// |- 3 - Connect to the database (retrieve a connection)
// |- 4 - Create table if needed
// |- 5 - Add some data if needed
// |- 6 - Close connection when done
// |- 7 - Start HTTP server
// |- 8 - we are done!
ConfigRetriever.getConfigAsFuture(retriever).compose(config -> {
jdbc = JDBCClient.createShared(vertx, config, "Players-List");
FootballPlayerDAO dao = new FootballPlayerDAO();
return dao.connect(jdbc).compose(connection -> {
Future<Void> future = Future.future();
createTableIfNeeded(connection).compose(this::createSomeDataIfNone)
.setHandler(x -> {
connection.close();
future.handle(x.mapEmpty());
});
return future;
})

// Point 1
.compose(v -> createHttpServer(config, router));
})
.setHandler(fut);
}

// Point 1
private Future<Void> createHttpServer(JsonObject config, Router router) {
Future<Void> future = Future.future();
vertx.createHttpServer().requestHandler(router::accept)
.listen(config.getInteger("HTTP_PORT", 8080),
res -> future.handle(res.mapEmpty()));
return future;
}

// Point 3
private Future<SQLConnection> createTableIfNeeded(SQLConnection connection) {
FootballPlayerDAO dao = new FootballPlayerDAO();
return dao.createTableIfNeeded(vertx.fileSystem(), connection);
}

// Point 3
private Future<SQLConnection> createSomeDataIfNone(SQLConnection connection) {
FootballPlayerDAO dao = new FootballPlayerDAO();
return dao.createSomeDataIfNone(vertx.fileSystem(), connection);
}

...
}

In the code described previously, I have set the // Point X comment to highlight where we have implemented the points described in the preceding list. As usual, all operations are asynchronous and return a Future type.

For all paths handled by the HTTP server, there is a method that is responsible for intercepting the request, contacting the database, performing the operation, and returning a response.

For example: the invocation to the /footballplayer/show/:id path is mapped in this way:

router.get("/footballplayer/show/:id").handler(this::getOne);

That means that there is a method, getOne, which will handle the request and return a response:

private void getOne(RoutingContext rc) {
String id = rc.pathParam("id");
FootballPlayerDAO dao = new FootballPlayerDAO();
dao.connect(jdbc).compose(connection -> dao.queryOne(connection, id)).setHandler(ok(rc));
}

The behaviour is simple: the DAO class will query the database, using the queryOne method, and, using the ok method of our utility helper class, will build a response with the 200 HTTP code and the JSON representation of our football player.

Let's try to do it—start the application using this command:

$ java -jar $PROJECT_HOME/target/footballplayermicroservice-1.0.0-SNAPSHOT-fat.jar

And then invoke the API:

$ curl http://localhost:8080/footballplayer/show/1 | json_pp

You will retrieve your football player:

{
"team" : "Paris Saint Germain",
"id" : 1,
"name" : "Gianluigi",
"age" : 40,
"price" : 2,
"surname" : "Buffon",
"position" : "goalkeeper"
}
..................Content has been hidden....................

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