Passing query parameters through your WebClient

Reading Time: 5 minutes

Hi all, i hope you all are doing well and are ready to go deep with the spring WebClient way of invoking a third party service from within your dependent service. This blog we’ll pay attention to the way we include query parameters to be passed to the 3rd party service to invoke it.

First thing to notice is that we are talking about working with Spring’s Asynchronous way to invoke a 3rd-party service. Spring-boot and webflux provide an asynchronous mechanism: `a Web-Client` for invoking the 3rd-party service. A Web-Client as per the spring documentation is:

A non-blocking, reactive client to perform HTTP requests, exposing a fluent, reactive API over underlying HTTP client libraries such as Reactor Netty.

Now few may ask what is the reason for writing such a blog? The reason why i am writing this blog is because recently i understood that there are multiple ways to invoke a 3rd party service from your web-client. Depending upon the practice you have you used to invoke the service can have different meanings when a service call is made. In this blog i’ll walk you through the instance i faced and how it changed my approach to web client. So let’s get started.

Scenario:

I have a 3rd party service which is Service B, which helps me by providing me the route between any two coordinates on the sea area. It takes two path parameters from the user: 1. Longitude and latitude coordinate for origin point, and 2. Longitude and latitude coordinate for destination point. Depending upon these two it responds back with a route from the origin to destination. But there is good possibility that this route is calculated by passing over the land areas in between the origin and destination. To avoid such a route the 3rd party service requires a query parameter to be passed during its invocation which is `avoidInLand=true/false`. Depending upon the value received for this query param we get the route from our third-party service. My service let say Service A is used to pass on a call to service B, let’s see how it’s done in what ways.

Case-1:

Well let us go through the below implementation for my service A which invokes service B using a WebClient instance.

Well the above code structure looks good and it works fine as well. Though there is one big thing we forgot to take care of. I hope we all know the difference between a path parameter and a query parameter. A path parameter is the one you cannot skip, you must provide a value for it. On the other hand a query param is something which is optional, either you provide it in the URL with its value or you don’t even mention it in the URL. So if i say that i have a rest endpoint that gets me the orders for customers, it get me orders for all the customers or it can get me the orders for a particular customer. The Rest path would look similar to this: HTTP- GET /v1/orders?customerId. This can then be able to acknowledge the two different type of requests, one that gets all orders for all customers viz., /v1/orders and the second one that gets orders for particular customer viz., /v1/orders?customerId=custom1. That is how using the query param can change the meaning of a rest endpoint.

Coming back to our code block above. Here one big mistake that we did is that we have ignored the basics of using a queryparam 🙁 . What i mean is when we wrote this code:

.uri(uriBuilder -> uriBuilder

                        .path("/third-party/origin/lat:{lat},lon:{lon}/dest/lat:{lat},lon:{lon}/route") //Base-path for invoking the 3rd party service.

                        .queryParam("avoidInLand", avoidInLand.orElse(null))

                        .build(origin.get(0), origin.get(1), destination.get(0), destination.get(1)))

                .headers(httpHeaders -> {

                    httpHeaders.add(LOCALE, "en");

                 })

we unintentionally forced the queryParam `avoidInLand` to be a part of every request that is made through the webClient to the 3rd party service. This means that you cannot made a call to the 3rd party without adding the query param avoidInLand and its value to the url. This is not a desired invocation for the 3rd party because if they don’t ask us to pass the query param with every call why should our web client impose any such restrictions? This thing looks small in the beginning but can cause much trouble. I personally felt that this webclient code should be modified in such a way that we pass query param only when we have received some value for it from the original request. Lets now take a look at it’s alternative.

Case-2:

In this approach we are going to leverage the `queryParams()` method of the UriBuilder class found in `package org.springframework.web.util;`. Let’s see the code block first.

So in the above block we first create a MultiValueMap<String, String> queryParams to store the query param and their values if any were received in the original request. We then create:

.uri(uriBuilder -> uriBuilder
                        .path("/third-party/origin/lat:{lat},lon:{lon}/dest/lat:{lat},lon:{lon}/route") //Base-path for invoking the 3rd party service.
                        .queryParams(queryParams)
                        .build(origin.get(0), origin.get(1), destination.get(0), destination.get(1)))
)

using the queryParams() method of the UriBuilder class we append the query parameters received in the original request if any otherwise we do not append anything to the base path of the 3rd party service.

With this second approach we were easily able to pass on the query params if they were present in the original request. If there weren’t any query param received then we did not attempt to append them to our 3rd party service basepath unlike the case-1 webclient. So using this web client we can invoke the 3rd party service with both the below mentioned endpoints without any trouble.

  1. https://<some-third-party-domain>/third-party/origin/lat:-80,lon:26/dest/lat:-78.2560,lon:25.9741/route, and,
  2. https://<some-third-party-domain>/third-party/origin/lat:-80,lon:26/dest/lat:-78.2560,lon:25.9741/route?avoidInLand=true/false

It is worth noticing that with case-1 webclient we were forced to invoke the third-party only with the endpoint#2 mentioned above. We lost the ability to invoke 3rd party service with endpoint#1 using the webclient specified in case-1.

I hope this blog will help you decide which webclient build you should prefer because in my case i want to invoke the 3rd party service with query params once and then without the query params as well. Thus it was an obvious choice for me that i have to go with the webclient created in Case-2. I would really love to hear back from you guys any feedback any comments are more than welcome. For doubts and QA please leverage the comments section below and i’ll try to get them sorted out for you as soon as possible. Thanks. 🙂

Conclusion:

We saw that we can use spring-boot:webflux’s WebClient to invoke a third-party service from within our spring boot application. This is asynchronous and reactive method supplied by spring for invoking of unmanaged services. We also saw how we can pass query params when invoking a 3rd party service and which case to chose with our requirements. Be cautious with what you are using and always document what you want and what you created so that things like these can be caught early during the development phase.

References:

  1. https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client

Knoldus-blog-footer-image

Written by 

Prashant is a Senior Software Consultant having experience more than 5 years, both in service development and client interaction. He is familiar with Object-Oriented Programming Paradigms and has worked with Java and Scala-based technologies and has experience working with the reactive technology stack, following the agile methodology. He's currently involved in creating reactive microservices some of which are already live and running successfully in production, he has also worked upon scaling of these microservices by implementing the best practices of APIGEE-Edge. He is a good team player, and currently managing a team of 4 people. He's always eager to learn new and advance concepts in order to expand his horizon and apply them in project development with his skills. His hobbies include Teaching, Playing Sports, Cooking, watching Sci-Fi movies and traveling with friends.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading