About SOA or MSOA
It is not new that projects were moved from big monoliths to independent services that could interoperate to build a big system. It provided many benefits like independent scalability, independent deployment, best suitable technologies for each need, team workload distribution, etc. but also it brought some complexity: how to manage changes in the contracts of the services and how to offer the required methods and data for heterogeneous clients.
Regardless of all those complexities, the benefits they offer are very big. In our case, the reasons that made us implement our functionality using independent vertical services are:
Independent scalability with independent hardware requirements.
Independent deployment having each service in a different Docker image.
Labs services: quick time-to-market for some of the features we want to test and remove if they are not successful.
Assignment of specific services to our external developers so they can work independently and we can add the services they implement to our application easily.
About the APIs
The API is the public interface that will expose our data and the actions that can be performed in our system. One of the complex challenges in software engineering is how to define a proper API.
In a perfect world, we would provide an interface that is oriented to different clients (Web, Mobile, 3rd party services, etc) that have different requirements, we should evolve it making non-breaking changes and it should be always available.
One of the complex things is how to make an API offer exactly what every client needs. Each client will have different use cases and different requirements in terms of what information they can send, what data they want to receive, how they want to receive it and even in what volumes. Proper interfaces should be defined based on client needs, in other words, clients should define the API they need and not adapt them to the APIs that are available in a given system; even more when both clients and APIs are being implemented for the same project.
There are a lot of articles about Consumer-driven contracts and all of them are very complex. Maintaining and versioning an API can easily become one of the bottlenecks of a project.
About the UI
In our case, and I guess in most cases nowadays, the UI is constantly evolving. We are changing it very often to see how users react to those changes and refining it to provide the best experience.
One of the keys of having a good user experience is performance, and one way of getting a good performance is letting the UI to decide how much data they want to get on each request and to limit the number of requests to the server.
It would not make sense to optimize the services and make the UI to manage the complexity of the underlying structure.
For example, the UI could need data from different services to render a specific component. It means that UI would need to know where to find each service, what interface they have, the format of the data each service returns, etc. It would also need to synchronize the requests and it could cache the result in the client, but a different client rendering the same component would need to execute the same exact request-synchronize-merge-cache flow.
Additionally, the UI could request data to the same end point from different components or even have different UIs and therefore they could have very different needs. To manage that, the service could return all data needed for all clients, so each client would be over fetching data, or the service could offer different endpoints for each client needs, but it would make UI less agile since it would need to request an API for each new feature and the service will end up with a lot of endpoint and difficulties to know what endpoints are being used and what endpoints could be removed.
About the API Gateway
In order to optimize the way we serve data to the UI and to limit the complexity that the different clients should tackle we decided to create an API Gateway. The main benefits of an API gateway are:
Abstracting UI from the complexity of the server-side.
Allowing server-side cache of aggregations of data coming from different services.
Abstract the implementation or changes in each individual service(s) from the UI.
Still, by only adding an API Gateway we are not tackling all the problems we had in terms of UI changing requirements. We still need to define the endpoints for the UI and each client could have different needs for the same data request.
To solve that and improve the abstraction between UI and services we decided to use GraphQL.
From their site:
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.
By using GraphQL the relation between clients and API changes dramatically. The UI does not need to adapt to the services API and the services do not need to fulfill each client they might have. With a GraphQL API Gateway, the UI requests what it needs for a given component to be rendered and the services will just return the resulting data without thinking of formatting it or optimizing it.
Of course, there is no magic and someone needs to connect both client and service. But the benefits we have are much more than the small effort it requires.
Although it looks cumbersome the flow is very simple.
Imagine we have a website for renting vehicles and we want to show a list of all the vehicles we have. The UI would make a request to API gateway to get the names and images of the vehicles we have. For retrieving this information the API Gateway will request data to Cars and Bikes services, merge the content and serve it to the UI. If the UI needs also the Description for a given feature or component, the flow will be exactly the same and if the UI does not need the image, the services or API Gateway do not need to change a single line of code.
Performance wise, we need to remember that API Gateway and the Services are on the server side, so the requests are much faster than performing them from the client and the data received by the UI is just what it needs, no more, so the data usage will be optimized.
Additionally, the data can be cached at the API Gateway level, so popular requests will not need fetching data to the different services since the merged data will be cached.
Since performance is a high priority in our application we also cache data in the client-side and optimize images through an image-proxy.
In future posts, we will give more details about GraphQL, and we will talk about inter-service communication and performance in our service.