In the “3rd level RESTful API with Spring” post I explained how to leverage Spring in order to build an API for an component in a distributed system so other components can communicate with it. Now let’s imagine we have such a distributed system and a client which wants to perform some operations against it. Such operations may involve one or more components of distributed system and then the question is if client should talk directly to each component or there should be an intermediate chain?
It is common to think that nowadays client software, for example it can be browser or mobile application, is so powerful that it can perform multiple requests in parallel and combine the results without any performance issues. Partially it is true, for example, modern CPU, even if we talk about mobile phone, is powerful enough to process retrieved data into final result in a way, that it goes unnoticed for the user. Things are a bit worse when it comes to network though: while mobile networks are already fast enough, traffic may still be expensive, especially in roaming, which can actually prevent user from using an application that load to much data. And the weakest element is battery charge: each time a mobile device makes a request via network or uses its multi core CPU to combine intermediate results it consumes energy and unfortunately mobile devices come with batteries that can be drained rather fast.
Taking that into consideration it may already look not that ideal to move integration complexity to front-end developers that will use for example AngularJS to wire up all the back-end components as that will lead to a lot of data being transmitted to and processed at client side. In many cases it is better to remove this complexity from the client side and do the preprocessing required by the client at back-end. That’s where API gateway comes in place. API gateway does exactly the same as Facade pattern in object-oriented programming: it provides a simplified high-level interface to a larger system, hence makes it easier to use. Each component remains accessible so if client needs something beyond facade interface it is still able to submit direct requests.
If necessary there may be as many API gateways as many different types of clients you have. Let’s say you may have one gateway for desktop applications, another for web clients, yet another for web clients on mobile devices and yet another for native (Android, iOS) mobile applications. Those gateways may be different in terms of domain model, request/response structure, encoding, protocol, etc., for example, you can maintain SOAP API gateway for some legacy clients while all your components in distributed system already expose 3rd level RESTful API. Another advantage here is decoupling business level components from specific client needs, which may often be mutually exclusive when dealing with multiple clients. Having back-end that is adaptive and simplified for the specific client type rather than forcing every client to accept the shared and broad API is definitely an evidence of back-end team strength and usually means faster delivery of the features to the end users.
As an example let’s look at how our API gateway hides the distributed system setup from an API client:
As it appears from the diagram we have a distributed system that runs on Hadoop (check out why we use Hadoop by the way) with each component having it’s own data storage, in our case all the components share the same domain model, but even if it wasn’t the case, it would not matter much as the gateway has own domain model and only relevant to the client data is mapped from component model to gateway model. The gateway itself has a data storage connection where it stores information about clients, e.g. which operations clients are permitted to perform, as our API clients must authorize themselves.
As you may have noticed, we don’t have an exposed REST API for each component, which makes the gateway essential, not just convenient for the client. In order to connect gateway with our distributed system we leverage ActiveMQ: whenever request comes to API gateway it decides which component(s) have to be involved in order to fulfil it and puts task(s) to be done into the corresponding queue(s). Each component listens to its queue and when there is a new task it executes it and submits a result back through a temporary queue, which is created by ActiveMQ specially for this purpose. As opposite to some other messaging frameworks (e.g. Apache Kafka) that only support “Fire And Forget” pattern, ActiveMQ supports request-response behavior (both pseudo synchronous and async with callback) out of the box.
There is also a very helpful Spring JMS library that provides the core functionality for using JMS. It contains JMS template classes that simplify the use of the JMS by handling the creation and release of resources, much like the JdbcTemplate does for JDBC. With that in place task submission from Gateway to ActiveMQ looks like this:
While at component side in order to listen for new tasks we annotate listener endpoints with @JmsListener:
String destination = “queue1”;
Task task = … (construct new task)
Message response = jmsTemplate.sendAndReceive(destination, session -> session.createObjectMessage(task));
Result result = (Result) jmsTemplate.getMessageConverter().fromMessage(response);
The task in this case is quite generic object, which contains information which method on which bean with which parameters to call, this makes it suitable for use by different components, what they have to do is to retrieve the given bean from application context, call the given method with given parameters and send the result back to gateway. Result can be an actual object returned by the triggered method or an exception if one has occurred during task execution. After Gateway receives results from all the involved components it merges them into the final response and hands over to the client.
This setup allows each component to focus on its direct tasks without knowing anything about the client, that is why the concept of API gateways is very popular in cloud native architectures, where one has many microservices but hides them behind a facade in order to make system easier to use and allowing more flexibility for the development.
@JmsListener(destination = "queue1")
public Result execute(Task task)
Result result = … (execute task)