Lab 13: Calling External REST APIs
Description¶
To support different currencies for the coffee order's total price, we'll call to an external currency conversion API
using Spring's RestTemplate
. This will be done in two steps:
-
Create a stub service that returns a hard-coded value, to make sure it's integrated properly.
-
A real,
RestTemplate
-based implementation that calls out to the API service.
Goals¶
- Learn about using stubs to ease development
- Understand the basics of
RestTemplate
- See how
@Profile
can be used to switch among services used - Learn about handling HTTP query parameters with
@RequestParam
a. Stub Service¶
You'll create a Stub version of the currency conversion service. Remember that a stub returns a hard-coded value.
-
Create a new interface named
CurrencyConversionService
in the domain package with the following method:int convertToBritishPound(int amount);
Note that this is a domain service, so has no annotations.
-
Create a new package,
com.tedmyoung.coffeekiosk.adapter.out.currency
and in that package, create a class namedStubCurrencyConversionService
that implementsCurrencyConversionService
and returns a hard-coded value of1234
. -
As this is an adapter service, add Spring's
@Service
annotation to the stub implementation.
b. Add Query Parameter¶
To support optionally returning the price in a different currency, we'll add support for a query parameter.
Open the CoffeeOrderController
class and make the following changes:
-
Inject the
CurrencyConversionService
interface as an additional parameter of the existing constructor and save it as a final instance field. -
Change the
coffeeOrder()
method to accept an optional Query Parameter by changing the method signature to:public ResponseEntity<CoffeeOrderResponse> coffeeOrderInfo( @PathVariable("id") long orderId, @RequestParam(value = "currency", defaultValue = "usd") String currency) {
This will assign any incoming Query Parameter (e.g.,
?currency=gbp
) to thecurrency
variable.The
defaultValue="usd"
means that if no query parameter is sent,currency
will default to"usd"
.
c. Failing Test¶
Not all tests with Spring controllers have to use Spring support, we can write a unit test that instantiates the controller directly and test it as if Spring weren't involved.
-
Create a new
CoffeeOrderControllerTest
class and add the following test:@Test public void getWithPoundQueryParamConvertsPriceToPounds() throws Exception { // Given: an order is in the repository CoffeeOrderRepository coffeeOrderRepository = new InMemoryCoffeeOrderRepository(); // empty coffee order is fine as the price will be ignored for this test CoffeeOrder coffeeOrder = new CoffeeOrder("Spring", LocalDateTime.now()); coffeeOrder.setId(12L); // need to have an id so we know we get an existing order coffeeOrderRepository.save(coffeeOrder); CoffeeOrderController controller = new CoffeeOrderController(coffeeOrderRepository, new StubCurrencyConversionService()); // When: we do a GET with "gbp" currency CoffeeOrderResponse response = controller.coffeeOrder(coffeeOrder.getId(), "gbp").getBody(); // Then: price will always be 1234 assertThat(response.getTotalPrice()) .isEqualTo("1234"); }
-
This test should fail, but all other tests should still pass.
d. Use the Service¶
In the CoffeeOrderController
class, implement the following behavior in the coffeeInfo
method:
-
If
currency
parameter is"gbp"
, then:-
Take the
totalPrice
from the coffee order (after it's retrieved from the repository) -
Use the
CurrencyConversionService
to convert the price into Pounds -
Store the converted price into the
CoffeeOrderResponse
as the price.
-
If implemented properly, the tests should pass.
e. Try It Out¶
Try it out using the browser, curl
, or Postman, both with and without the query parameter of ?currency=gbp
:
f. Increase Prices¶
To make the examples work better (since we're working with whole numbers),
change the pricing in the CoffeeItem.price()
method so that it's multiples of 100.
Instead of 1, 2, and 3, change to 100, 200, and 300.
g. Real Currency Conversion Service¶
Now you will implement the CurrencyService
so it will call out to the remote API.
Refer to the example below as needed for the later sections.
The Weather API Example
This is the code and response DTO from the Weather example:
Learning from examples is great, but don't just blindly copy-n-paste...
// instantiate a RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// create a Template URL for the API
String weatherUrl = "https://basic-weather.herokuapp.com/api/zip/{zip}";
// put the variables in a Map
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("zip", "94404");
// call out to the remote API and get back a DTO
WeatherResponse response =
restTemplate.getForObject(weatherUrl,
WeatherResponse.class,
uriVariables);
This is the DTO that's used above:
public class WeatherResponse {
private String location;
private String updated;
private Float temp;
private String condition;
// getters and setters go here
}
h. DTO for Response¶
Create a DTO ConvertedCurrency
that represents the returned JSON.
The properties must match the JSON key names below,
i.e., it will have 2 properties: the currency string, and the converted result as a double
.
The JSON returned from this API looks like:
{
"currency": "GBP",
"converted": 71.00
}
i. Using RestTemplate¶
You'll call out to the remote API like this:
https://jitterted-currency-conversion.herokuapp.com/convert?from=USD&to=GBP&amount=100
-
Create a new class,
HttpCurrencyConversionService
in thecom.tedmyoung.coffeekiosk.adapter.out.currencyconversion
package.This class will implement the
CurrencyConversionService
interface, using the remote API to do the conversion. -
Since you'll now have two
@Service
s implementating the same interface, you'll need to use the Spring Profile feature to select among the implementations:- Add
@Profile("dev")
as an annotation for theStubCurrencyConversionService
class - Add
@Profile("prod")
as an annotation for theHttpCurrencyConversionService
class - In the
application.properties
file, add a new line:spring.profiles.active=prod
- Add
-
In the
convertToBritishPound
method, create a URI Template string for the remote API:- The base part of the URI is
http://jitterted-currency-conversion.herokuapp.com/convert
- There are three query parameters:
from
- the source currency, e.g.,USD
to
- the converted currency, e.g.,GBP
amount
- the amount to convert, e.g., 10
Example
To convert 100 in USD to GBP, the URI would look like this:
http://jitterted-currency-conversion.herokuapp.com/convert?from=USD&to=GBP&amount=100
Think about what parts of the string need to become template variables. A URI Template Variable looks like
{amount}
. - The base part of the URI is
-
Instantiate a
RestTemplate
and use thegetForObject()
method, passing in these 3 parameters:- The URI template string from above
ConvertedCurrency.class
- The query parameters in the map
-
Calling
getForObject()
returns an instance ofConvertedCurrency
, so take theconverted
value (which is adouble
) and return it as anint
(you can just cast it or use theintValue()
method).
j. Try It Out¶
Start the application and try it with the browser, curl
, or Postman, both with and without the query parameter of ?currency=gbp
:
What happens if you change the profile in the application.properties
file to dev
?