Getting started with Dropwizard: Connecting to external REST Web-services using Jersey Client
Here is a link to my Getting Started with Dropwizard course on Udemy. Only $10!
Imagine a situation that you would like to create a currency conversion API using Dropwizard, but exchange rates are fickle and change on daily if not hourly basis. So, you need an external source of exchange rate data and luckily, Google search returns a list of such APIs. In this tutorial will connect to Open Exchange Rates which has a free option.
To use the API one needs to obtain a free API key which is an alphanumeric string supplied as part of request string when accessing the API. Here is the description of how to obtain the key. From this moment it is supposed that you have obtained the key, otherwise you will be unable to follow the examples from this tutorial.
Let’s play a little with the API before we start writing the actual code. First, one can obtain the list of supported currencies, this can be accomplished by navigating the following URL
https://openexchangerates.org/api/currencies.json?app_id=<Your API key>
from your browser, where <Your API key> should be replaced with the actual key you obtained earlier. In the first column there are currency symbols that will be used in the api we will build to show the initial currency and the currency of conversion result.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"AED": "United Arab Emirates Dirham", | |
"AFN": "Afghan Afghani", | |
"ALL": "Albanian Lek", | |
"AMD": "Armenian Dram", | |
"ANG": "Netherlands Antillean Guilder", | |
"AOA": "Angolan Kwanza", | |
"ARS": "Argentine Peso", | |
"AUD": "Australian Dollar", | |
"AWG": "Aruban Florin", | |
"AZN": "Azerbaijani Manat", | |
"BAM": "Bosnia-Herzegovina Convertible Mark", | |
"BBD": "Barbadian Dollar", | |
... | |
} |
Second, to obtain conversion rates one should navigate the browser to the following URL
https://openexchangerates.org/api/latest.json?app_id=<Your API key>,
where again the string <Your API key> should be replaced with your key. In addition to rates, there is some supplementary information that we’ll not use in our code. As to the rates data, each row provides the information about how much US Dollar costs in a particular currency. There is also base field in JSON data, which shows relative to which currency the rates are calculated. In the premium version of Open Exchange Rates API one can change the base, but in the free version we’ll have to rely on US Dollar and if we want to convert a sum in Euro to a sum in Canadian dollars, we should calculate the exchange rate based on how much US Dollar costs in Euros and in Canadian Dollars.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"disclaimer": "Exchange rates are provided for informational purposes only, and do not constitute financial advice of any kind. Although every attempt is made to ensure quality, NO guarantees are given whatsoever of accuracy, validity, availability, or fitness for any purpose - please use at your own risk. All usage is subject to your acceptance of the Terms and Conditions of Service, available at: https://openexchangerates.org/terms/", | |
"license": "Data sourced from various providers with public-facing APIs; copyright may apply; resale is prohibited; no warranties given of any kind. Bitcoin data provided by http://coindesk.com. All usage is subject to your acceptance of the License Agreement available at: https://openexchangerates.org/license/", | |
"timestamp": 1449208861, | |
"base": "USD", | |
"rates": { | |
"AED": 3.672912, | |
"AFN": 66.879999, | |
"ALL": 126.962099, | |
"AMD": 484.589999, | |
"ANG": 1.78875, | |
"AOA": 134.989332, | |
"ARS": 9.692687, | |
"AUD": 1.366368, | |
"AWG": 1.793333, | |
"AZN": 1.048463, | |
"BAM": 1.798054, | |
... | |
} |
Finally, let’s discuss how one can access the API we build. It is supposed, that to obtain the result of conversion one should provide an amount to convert, the currency from which we convert and the one to which we convert. The example code for this tutorial returns the answer if one supplies the browser with a URL like the one below.
localhost:8080/converter/1.5?from=EUR&to=CAD
The API key necessary to call the external API is hidden inside of our code.
Now let’s turn to writing actual code, which can be found in repository here. All explanations of how to create a Dropwizard application and how to expose a REST API are given in this tutorial. In addition, the aforementioned tutorial discusses Jersey client, although there it was used to test the API.
The first step is to add dependency to the pom.xml file of the application.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>io.dropwizard</groupId> | |
<artifactId>dropwizard-client</artifactId> | |
<version>${dropwizard.version}</version> | |
</dependency> |
After that we can add the URL of the external API and its key as well as some client settings to the application YAML configuration file. We discussed earlier how to work with Dropwizard configuration. Here is an excerpt from config.yml file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#API settings | |
#API URL | |
apiURL: https://openexchangerates.org/api/latest.json | |
#API key | |
apiKey: <Your API key> | |
#Jersey client settings | |
jerseyClient: | |
#The maximum idle time for a connection, once established. | |
timeout: 512ms | |
#The size of the work queue of the pool used for asynchronous requests. | |
#Additional threads will be spawn only if the queue is reached its maximum size. | |
workQueueSize: 16 |
The first two parameters are necessary to connect to the Open Exchange Rates API, and you should provide your API key here. In addition, there is group of parameters to configure Jersey client which are not required and were merely added to show how to change the default settings. All parameters have sensible defaults which can be seen in HttpClientConfiguration and JerseyClientConfiguration classes. The full list of settings for the client can be found here. It should be noted, that the HttpClient set and Jersey client set are both applicable to Jersey client.
Then we have to modify the Configuration class and the necessary changes are shown in the snippet below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DWGettingStartedConfiguration extends Configuration { | |
... | |
/** | |
* The URL to access exchange rate API. | |
*/ | |
@NotEmpty | |
private String apiURL; | |
/** | |
* The key to access exchange rate API. | |
*/ | |
@NotEmpty | |
private String apiKey; | |
/** | |
* Jersey client default configuration. | |
*/ | |
@Valid | |
@NotNull | |
private JerseyClientConfiguration jerseyClientConfiguration | |
= new JerseyClientConfiguration(); | |
/** | |
* | |
* @return Jersey Client | |
*/ | |
@JsonProperty("jerseyClient") | |
public JerseyClientConfiguration getJerseyClientConfiguration() { | |
return jerseyClientConfiguration; | |
} | |
/** | |
* A getter for the URL of currency rates the API. | |
* | |
* @return the URL of currency rates the API. | |
*/ | |
@JsonProperty | |
public String getApiURL() { | |
return apiURL; | |
} | |
/** | |
* A getter for the API key of currency rates the API. | |
* | |
* @return the API key of currency rates the API. | |
*/ | |
@JsonProperty | |
public String getApiKey() { | |
return apiKey; | |
} | |
... | |
} |
Please note that related parameters, such as those to configure the client, are grouped into a class, in JerseyClientConfiguration our case.
After adding client to our application, one can start writing the code to communicate with the Open Exchange Rates API. First of all, we’ll need a representation class that will be used to store the exchange rate date and supplementary information received from the external API. The fields of the class are the same as in the JSON representation above.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CurrencyData { | |
/** | |
* A string with a disclaimer. | |
*/ | |
private String disclaimer; | |
/** | |
* A string with a license. | |
*/ | |
private String license; | |
/** | |
* A timestamp. | |
*/ | |
private long timestamp; | |
/** | |
* The currency relative to which exchange rates are returned. In a free | |
* version of the API one cannot change the base and it is US Dollar by | |
* default. | |
*/ | |
private String base; | |
/** | |
* A map with exchange rate with keys like USD for US Dollar, EUR for Euro, | |
* CAD for Canadian Dollar etc. | |
*/ | |
private Map<String, Double> rates = new HashMap<>(); | |
/** | |
* A no-argument constructor. | |
*/ | |
public CurrencyData() { | |
} | |
/** | |
* A constructor needed for testing. | |
* | |
* @param disclaimer A string with a disclaimer. | |
* @param license A string with a license. | |
* @param timestamp A timestamp. | |
* @param base The currency relative to which exchange rates are returned. | |
*/ | |
public CurrencyData(String disclaimer, String license, long timestamp, String base) { | |
this.disclaimer = disclaimer; | |
this.license = license; | |
this.timestamp = timestamp; | |
this.base = base; | |
} | |
//Getters and setters. | |
... | |
} |
We omitted auto-generated getters and setters here.
Now let’s create a resource class which will serve the incoming requests, call the currency API and carry out some calculations. The snippet below shows the code except the method that is used for computations. The latter, along with some ideas of how to test the resource using jUnit and Mockito, can be found in the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Path("/converter") | |
public class ConverterResource { | |
/** | |
* The name of the path parameter for the API key. | |
*/ | |
public static final String APP_ID = "app_id"; | |
/** | |
* A part of an error message. | |
*/ | |
public static final String UNKNOWN_CURRENCY = "Unknown currency "; | |
/** | |
* Error message when no amount to convert was presented. | |
*/ | |
public static final String AMOUNT_IS_NECESSARY = "Amount is necessary."; | |
/** | |
* A Jersey client to connect to external API to get exchange rate. | |
*/ | |
private final Client client; | |
/** | |
* The URL to access exchange rate API. | |
*/ | |
private final String apiURL; | |
/** | |
* The key to access exchange rate API. | |
*/ | |
private final String apiKey; | |
/** | |
* A constructor. | |
* | |
* @param client Jersey client. | |
* @param apiURL the URL to access exchange rate API. | |
* @param apiKey he key to access exchange rate API. | |
*/ | |
public ConverterResource(Client client, String apiURL, String apiKey) { | |
this.client = client; | |
this.apiURL = apiURL; | |
this.apiKey = apiKey; | |
} | |
/** | |
* A subresource method used to convert currencies. | |
* | |
* The URI to access it looks like | |
* <em>localhost:8080/converter/1.5?from=EUR&to=CAD</em> where a double | |
* number is provided as a path parameter and initial and final currencies | |
* are provided as query parameters. | |
* | |
* @param amount a whole number, an amount to convert. | |
* @param from the currency to be converted. | |
* @param to the currency to which to convert | |
* @return the result of conversion rounded to Cents. | |
*/ | |
@GET | |
@Path("/{amount}") | |
@Produces(MediaType.TEXT_PLAIN) | |
@Consumes(MediaType.TEXT_PLAIN) | |
//localhost:8080/converter/1.5?from=EUR&to=CAD | |
public double convert( | |
@PathParam("amount") Optional<Double> amount, | |
@DefaultValue("USD") @QueryParam("from") String from, | |
@DefaultValue("EUR") @QueryParam("to") String to | |
) { | |
//A variable to store convertion rate of initial currency to US Dollar. | |
Double fromRate; | |
//A variable to store convertion rate of the desired currency | |
//to US Dollar. | |
Double toRate; | |
//A string to store an error message if an incorrect currency | |
//was provided. | |
String errorMessage; | |
//Check that amount to convert is present. | |
if (!amount.isPresent()) { | |
throw new BadRequestException(AMOUNT_IS_NECESSARY); | |
} | |
//Obtain data from external API. | |
CurrencyData currencyData = client | |
// returns WebTarget | |
.target(apiURL) | |
// returns WebTarget | |
.queryParam(APP_ID, apiKey) | |
//Can be done only in Enterprise and unlimited versions. | |
//.queryParam("base", to) | |
//returns Invocation.Builder | |
.request(MediaType.APPLICATION_JSON) | |
//returns CurrencyData | |
.get(CurrencyData.class); | |
//Obtain rates. | |
fromRate = currencyData.getRates().get(from); | |
toRate = currencyData.getRates().get(to); | |
//Check that valid currency symbols were provided. | |
if (fromRate == null || toRate == null) { | |
errorMessage = UNKNOWN_CURRENCY | |
+ (fromRate == null ? from : to); | |
throw new BadRequestException(errorMessage); | |
} | |
//Convert and return the result. | |
return convert(fromRate, toRate, amount.get()); | |
} | |
... |
All the information about how to create a resource class and process the parameters from the URI provided to the class can be found in this tutorial. In the method, that processes request, we check that the amount to convert was provided, after that we extract conversion rates from the external API and obtain the data for the pair of currencies. The next step is to check if the values for the pair were extracted, in other words, whether the correct currency symbols were provided. And the final step is to calculate the result and returned it to the user.
The last leg of our trip is to register the resource in the run() method of the Application class. The snippet of code is shown below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DWGettingStartedApplication | |
extends Application<DWGettingStartedConfiguration> { | |
... | |
@Override | |
public void run(final DWGettingStartedConfiguration configuration, | |
final Environment environment) { | |
... | |
//Create Jersey client. | |
final Client client = new JerseyClientBuilder(environment) | |
.using(configuration.getJerseyClientConfiguration()) | |
.build(getName()); | |
//Register authenticator. | |
... | |
//Register a resource using Jersey client. | |
environment.jersey().register( | |
new ConverterResource( | |
client, | |
configuration.getApiURL(), | |
configuration.getApiKey()) | |
); | |
} | |
} |
We created an instance of client based on configuration and provided API settings to the resource as well.
In this tutorial we learned how to access an external API from a Dopwizard application. Also we learned how to configure Jersey client and got acquainted with a free version of an API that provides currency exchange rates.
Resources
- HTTP response codes
- Why not use Double or Float to represent currency?
- How to Use Java BigDecimal: A Tutorial
- Dropwizard Client
Comments
Post a Comment