In this chapter, we will go into the details of building synchronous microservices with Java EE 8. We will learn how to implement server-side REST APIs using basic JAX-RS annotations, implement sub-resource locators for nested REST APIs, and use HTTP status codes and exception mappers for exception handling. You will also learn how to implement the client side using JAX-RS client APIs, and finally, we will have a look at different test strategies for Java EE web services.
By the end of this chapter, we'll have implemented a small library microservice that offers a REST API for books, authors, and loans. We'll implement the library client as a standalone application and use the Jersey Test Framework and the Test Containers framework to test our REST API.
In this section, we're going to take a look at how to implement a REST resource using basic JAX-RS annotations. I'll show you how you can inject and use CDI beans in your JAX-RS resource implementation and show you how to properly use HTTP methods to model CRUD semantics, and of course we'll be running the web service within a Docker container:
Conceptual view of this section
We'll implement a REST API to get a list of books so that we'll be able to create new books, get a book by ISBN, update books, and delete a book.
We will create a basic project skeleton and prepare a simple class, which is called BookResource, and we will use this to implement the CRUD REST API for our books. So first up, we need to annotate our class using the proper annotations. We will use the @Path annotation to specify the path for our books API, which is "books", and we make a @RequestScoped CDI bean. Now, to implement our business logic, we want to use another CDI bean, thus we need to get it injected into this one. This other CDI bean is called bookshelf, and we'll use the usual CDI @Inject annotation to get a reference to our bookshelf. Next up, we want to implement a method to get hold of a list of all books, so let's do that. What you see here is we have a books method, which is @GET annotated, and it produces MediaType.APPLICATION_JSON and returns a JAX-RS response. You can see that we construct a response of ok, which is HTTP 200; as the body, we use bookshelf.findAll, which is a collection of books, and then we build the response. The BookResource.java file should look as follows:
@Path("books")
@RequestScoped
public class BookResource {
@Inject
private Bookshelf bookshelf;
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response books() {
return Response.ok(bookshelf.findAll()).build();
} Next up, we want to implement a GET message to get a specific book. To do that, again we have a @GET annotated method, but this time we have the @Path annotation with the "/{isbn}" parameter. To get hold of this parameter, which is called the isbn, we use the @PathParam annotation to pass the value. We use bookshelf to find our book by ISBN and return the book found using the HTTP status code 200, that is, ok:
@GET
@Path("/{isbn}")
public Response get(@PathParam("isbn") String isbn) {
Book book = bookshelf.findByISBN(isbn);
return Response.ok(book).build();
}
Next up, we want to create books. In order to create something, it's a convention to use HTTP POST as a method. We consume the application JSON and we expect the JSON structure of a book, we call bookshelf.create with the book parameter, and then we use UriBuilder to construct the URI for the just-created book; this is also a convention. We then return this URI using Response.created, which matches the HTTP status code 201, and we'll call build() to build the final response:
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response create(Book book) {
if (bookshelf.exists(book.getIsbn())) {
return Response.status(Response.Status.CONFLICT).build();
}
bookshelf.create(book);
URI location = UriBuilder.fromResource(BookResource.class)
.path("/{isbn}")
.resolveTemplate("isbn", book.getIsbn())
.build();
return Response.created(location).build();
}
Next up, we'll implement the update method for an existing book. To update things, again it's a convention to use the HTTP method PUT. We update this by putting in a specific location. Again, we use the @Path parameter with a value of "/{isbn}". We give a reference to this isbn here in the update method parameter, and we have the JSON structure of our book ready. We use bookshelf.update to update the book and in the end we return the status code ok:
@PUT
@Path("/{isbn}")
public Response update(@PathParam("isbn") String isbn, Book book) {
bookshelf.update(isbn, book);
return Response.ok().build();
}
Finally, we're going to implement the delete message, and as you might expect, we use the HTTP method DELETE on the path of an identified ISBN. Again, we use the @PathParam annotation here, we call bookshelf.delete, and we return ok if everything went well:
@DELETE
@Path("/{isbn}")
public Response delete(@PathParam("isbn") String isbn) {
bookshelf.delete(isbn);
return Response.ok().build();
}
This is our CRUD implementation for our book resource. I told you that we're going to use a Docker container and the Payara Server micro edition to run everything. We will copy our WAR file to the deployments directory and then we're up and running:
FROM payara/micro:5-SNAPSHOT
COPY target/library-service.war /opt/payara/deployments
Let's see if everything's running on our REST client (Postman).
First up, we get a list of books. As you can see here, this works as expected:
If we want to create a new book, we issue the POST and create new book request, and you'll see a status code of OK 200. We get the new book by using GET new book; this is the book we just created, as shown in the following screenshot:
We can update the book by using Update new book, and we'll get a status code of OK 200. We can get the updated book again by using GET new book; we get the updated title, as shown in the following screenshot:
Finally, we can delete the book. When we get the list of books again, our newly created book is not part of the list of books anymore.
In the next section, we're going to have a look at how we can use sub-resources and sub-resource locators.
In this section, we're going to take a look at how to implement simple sub-resource locator methods. We'll have a look at how you can obtain CDI sub-resource instances from the root resource, and we're going to have a look at how you can pass context information from the root to the sub-resources:
Conceptual view of this section
Books have authors, and they can be lent out. In this section, what we'll do is provide specific REST endpoints to obtain the author of a book and the loan details of the books. We have prepared the skeleton of the project, as shown in the following screenshot:
Let's start ...