Building bridges: Connecting to a third-party service
How to create a REST service to request information from the open weather map and avoid unnecessary calls.
Hi,
You will find some new and cool spring feature implementations, focusing on practical examples and learn some insights in the meantime. We will use cache spring module, it’s easy to implement and very useful.
Today, we will create a service to get current weather information by zip code and country code (country code list).
To get the weather information we will use OpenWeatherMap API. It’s a great API with a free registration that we can use and learn how to consume third party services.
We recommend making calls to the API no more than one time every 10 minutes for one location (city / coordinates / zip-code). This is due to the fact that weather data in our system is updated no more than one time every 10 minutes.
The first recommendation they give is to avoid repeating a call to the same location within a 10 minute gap, so we will use the spring cache module to store information from each location and return the cache information avoiding repeated requests to the weather API and clean the cache every 10 minutes to keep control of the cache storage since it could grow very large in a short time.
What you will learn in this article
- Build a REST service.
- Request information from a third party REST service.
- Caching information to speed up and avoid unnecessary requests.
- Gain a better understanding of spring-boot and its components.
- How to make integration tests using Mockito.
- Use of JUnit 5 with parameterized tests.
- Package your service for delivery with Docker.
- How to handle properties production ready.
Pre-requirements
- Create an OpenWeatherMap account
- Get Java 8
- Have Gradle 4+
- Git installed
- Lastly, an IDE of your choice; I’m currently using Visual Studio Code
Configuration
We need to generate an appid in open weather map to request weather information, then add a new property called weather.appid with the appid we just generate and using the @Value(“${weather.appid}”) to get the value we set injected in our service.
If you want to know more about spring properties, here you have a link to it.
Model objects with Lombok
To reduce time coding models for the weather API (I could create a common lib, what do you think?) I used several Lombok annotations that will create the code needed for getters, setters and a builder pattern very useful when mock responses.
Project Lombok is a java library that automatically plugs into your editor and builds tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully-featured builder, Automate your logging variables, and much more.
If you don’t know Project Lombok I really encourage to check it out, it avoids repeated code and most importantly it leaves the code clean. Since I read “Clean code” by Robert Martin for the first time, I would never see code the same. Now, code should be like a blog post, well organized and self-described with this library helps you to do just that.
Requesting weather
Once you created your account in open weather map and you have your appid, is as simple as use RestTemplate to get current weather by zip code with this URL:
https://api.openweathermap.org/data/2.5/weather?zip=%s,%s&appid=%s&units=metric
The “%s” that you see in the URL is used to be replaced by the values you need % is to specify needs format and the ‘s’ is to convert any value invoking toString() and the units are in a metric system so we could get the temperature in Celsius.
So the first parameter will be a zipcode, second will be the country code and last your appid you just generate.
For more information about String.format check the documentation
What is a cache?
A cache is a component that stores data so that future requests for the same data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of the data stored elsewhere, in our case a third party service.
As spring very well specify, the terms “buffer” and “cache” tend to be used interchangeably; note however they represent different things. A buffer is used traditionally as an intermediate temporary store for data between a fast and a slow entity. As one party would have to wait for the other affecting performance, the buffer alleviates this by allowing entire blocks of data to move at once rather than in small chunks. The data is written and read-only once from the buffer. Furthermore, the buffers are visible to at least one party which is aware of it.
A cache on the other hand by definition is hidden and neither party is aware that caching occurs. Also, improves performance but does it by allowing the same data to be read multiple times in a fast fashion.
So, When we’ll want to use a cache?
In general, for optimization, we want to avoid costly process to be executed when for the same input we will get the same output.
If you have a high pressure service, like thousands of requests per minute and each request is a costly process we want to resolve faster those requests we already process, or we want to avoid a call to an external source when we know that data didn’t change from last time we checked.
For caching declaration, the abstraction provides a set of Java annotations:
@Cacheable
triggers cache population@CacheEvict
triggers cache eviction@CachePut
updates the cache without interfering with the method execution@Caching
regroups multiple cache operations to be applied on a method@CacheConfig
shares some common cache-related settings at class-level
As we saw in the configuration section you can notice some extra annotations, first, @CacheConfig used to store in specific key inside our cache for our cached methods. You will notice @Cacheable annotation used to store in a cache the method selected by saving a copy of the data returned by the 2 specific methods.
When this annotation is present on a given class, it provides a set of default settings for any cache operation defined in that class.
Lastly, we have 2 annotations @CacheEvict(allEntries = true, value = {“weather”}) used to clean all entries in the weather key cache, and this method will be executed every 10 minutes by spring @Scheduled(fixedDelay = 1000 * 60 * 10) annotation.
NOTE: As a default Spring will auto-configure a ConcurrentHashMap as a cache, but if this service needs to scale we will need to change the cache to an external storage like Redis, but as I always say “optimize only when is needed”.
Mockito
Before going to the specifics of the test I wrote, we need to talk about Mockito, sounds more as a little mucus in Spanish than a drink based on tequila, Mockito is a framework that helps you imitate responses from anything very easily, so we can control the outcome of a call without actually doing it and validate or test the behavior of our service accordingly.
In our case, we are using Mockito to mock REST calls to open weather map. We could even use Mockito to mock a database, but if you are following my post you will notice that use another approach to mock a database by using an embedded database.
So, Why I didn’t use Mockito before? Well, the rule of thumb is don’t use Mockito unless you have a reason to use it. And the reason is, we don’t want to execute an actual request to the weather API for testing, we also want to have some control over the REST response so we can accomplish the main goal to make sure our service is robust even from any error that the call could return.
Test
If it’s needed, change the extension class we use for this test, we need Mockito to inject the objects to mock and where to inject it by specifying @ExtendWith(MockitoExtension.class) annotation.
We will tell Mockito we need imitation of RestTemplate, so instead of the actual implementation, Mockito can response to whatever we specify by using the @Mock annotation.
And lastly, we will tell Mockito to Inject any mock we created and is used by this component with @InjectMocks annotation.
Ok, but how we tell Mockito what we want for a specific call, with the help of a couple of verbose static methods Mockito provides.
Mockito.when(template.getForEntity(String.format(WeatherService.WEATHER_API_URL, “000”, “us”, “null”),Weather.class))
.thenReturn(new ResponseEntity<Weather>(weather, HttpStatus.OK));
Basically, we are saying when our template executes the getForEntity method with this params, you will return a weather object we created with a status ok. So when we execute getByZip method from our service with the same params that will end up calling the template we should get the weather object we specify to Mockito.
Conclusion
Today we learned how to call an external API using rest template, how to cache information using @Cacheable to optimize our service and avoid unnecessary process and on top of that how to use Mockito to test these cases to imitate external service behavior.
A cache is good to have, but don’t rush to use it everywhere, as anything, think of using a cache if it’s worth for your service. Consider that cache is in memory so you will need a server that supports the memory needed to maintain the cache to your specifications.
Something worth mentioning is, you can update your cache with @CachePut annotation in the method that update information that could be in your cache. Also, you are not forced to use a cache in memory, you can adapt to any sort of storage like a database.