Introducing MicroProfile Rest Client 1.0
The Liberty application server now offers the mpRestClient-1.0
feature. This is the implementation of the MicroProfile Rest Client 1.0 API spec released in December 2017. You can try out the MicroProfile Rest Client in our Open Liberty guide.
The MicroProfile Rest Client builds on JAX-RS 2.0 client APIs to provide a type-safe approach for invoking RESTful services. This means writing client applications with more model-centric code and less 'plumbing'. Although these are client APIs, they are deployed in a server.
Here is a quick run-down of how to create your own REST clients:
The interface
First, we start off with an interface that represents the remote service. The methods of the interface should match the RESTful APIs of the endpoint. So if we want to access a service that provides online music and allows us to view and modify playlists, we might create an interface that looks like this:
@Path("/playlist")
@Consumes("application/json")
public interface MusicPlaylistService {
@GET
List<String> getPlaylistNames();
@GET
@Path("/{playlistName}")
List<Song> getPlaylist(@PathParam("playlistName") String name)
throws UnknownPlaylistException;
@POST
@Path("/{playlistName}")
long newPlayList(@PathParam("playlistName") String name, List<Song> playlist)
throws PlaylistAlreadyExistsException;
@PUT
@Path("/{playlistName}")
long updatePlayList(@PathParam("playlistName") String name, List<Song> playlist)
throws UnknownPlaylistException;
}
Just like JAX-RS on the server side, this interface uses annotations like @Path
, @Consumes
, @GET
and @PathParam
. From this, we know that when a user invokes the getPlaylistNames
method, the MicroProfile Rest Client implementation sends a GET request to the endpoint at <baseUrl>/playlist
. The GET request accepts the application/json
media type of a list of strings indicating the available names of playlists.
ResponseExceptionMappers
To see what songs are in a given playlist, invoke the getPlaylist
method. Notice that this method throws an UnknownPlaylistException
- this situation might be indicated on the remote service as returning an HTTP 404 response. In order to convert this response to a specific exception, we need a ResponseExceptionMapper
like this:
@Provider
public class PlaylistResponseExceptionMapper implements
ResponseExceptionMapper<BasePlaylistException> {
@Override
public boolean handles(int statusCode, MultivaluedMap<String, Object> headers) {
return statusCode == 404 // Not Found
|| statusCode == 409; // Conflict
}
@Override
public BasePlaylistException toThrowable(Response response) {
switch(response.getStatus()) {
case 404: return new UnknownPlaylistException();
case 409: return new PlaylistAlreadyExistsException();
}
return null;
}
}
This code assumes that both UnknownPlaylistException
and PlaylistAlreadyExistsException
are both sub-classes of BasePlaylistException
. Notice that the toThrowable
returns an instance of the throwable rather than throwing it.
Implementation
Now that we have the interface and response exception mapper written, we just need to build the implementation and then invoke it. There are two ways to build the implementation: using the RestClientBuilder
API or using CDI and MicroProfile Config. We’ll start with the RestClientBuilder
- it is a little more verbose but can come in handy in environments where CDI is not available, like testing.
RestClientBuilder
...
URL apiUrl = new URL("http://localhost:9080/onlineMusicService");
MusicPlaylistService playlistSvc =
RestClientBuilder.newBuilder()
.baseUrl(apiUrl)
.register(PlaylistResponseExceptionMapper.class)
.build(MusicPlaylistService.class);
List<String> playlistNames = playlistSvc.getPlaylistNames();
for (String name : playlistNames) {
List<Song> songs = playlistSvc.getPlaylist(name);
if (hasSongBy(songs, "Band of Horses")) {
coolPlaylists.add(name);
}
}
...
First we create a new instance of the RestClientBuilder
. We must specify the baseUrl
value for the remote endpoint - this is required before building the client. Next, we register the response exception mapper. If we need to register other provider classes like MessageBodyReaders
or MessageBodyWriters
, filters, interceptors, etc., we would do that here with the register
method. Then we build the client, passing in the interface class. After that we can invoke methods on the client like it was any other Java object.
CDI and MicroProfile Config integration
It is also possible to let CDI instantiate the client. For all of the code we have covered so far, the user only needs to install the mpRestClient-1.0
feature in the Liberty server.xml
. In order for this CDI approach to operate, we need to add two more features: cdi-1.2
(or 2.0) and mpConfig-1.1
(or 1.2). First, we need to update the MusicPlaylistService
with some new annotations:
@Path("/playlist")
@Consumes("application/json")
@Dependent
@RegisterRestClient
@RegisterProvider(PlaylistResponseExceptionMapper.class)
public interface MusicPlaylistService {
...
The @Dependent
and @RegisterRestClient
annotations declare that this interface is to be managed by CDI. The @RegisterProvider
annotation tells the MicroProfile Rest Client implementation code to register the specified provider class. This annotation can be repeated for as many providers as necessary. Now we can inject the client in another managed object like this:
@WebServlet(urlPatterns = "/PlaylistServlet")
public class PlaylistServlet extends HttpServlet {
@Inject
@RestClient
private MusicPlaylistService playlistService;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List<String> names = playlistService.getPlaylistNames();
...
}
The @Inject
combined with the @RestClient
decorator tells CDI that we want to inject an instance of the MusicPlaylistService
interface. There is still one more step… we need to tell the MicroProfile Rest Client implementation the baseUrl
value for the remote endpoint. For that, we use MicroProfile Config. The config property to use is <fullyQualifiedInterfaceName>/mp-rest/url
. So you could specify this as a system property in the jvm.options
file like this:
-Dcom.mypkg.MusicPlaylistService/mp-rest/url=http://localhost:9080/onlineMusicService
CDI injection makes things a lot simpler when it comes to bootstrapping the client and, with MicroProfile Config, it is possible to use different URLs for different environments; for example, use one URL for test and another URL for production, without needing to change code.
Additional Information
For more information on the MicroProfile Rest Client, see the MicroProfile Rest Client 1.0 spec
Get involved in the MicroProfile community at: https://microprofile.io