Five ways to optimize your backend APIs in a microservice architecture

Written By: Akinola Ayomide
Edited and reviewed by: Owolabi Olutola

In the beginning, there was the monolith. And it was good. You had one application, with a single code base, and it did everything you needed.

But then your business grew, and so did your code base. It started to get hard to manage and maintain, so you split it into multiple services. Then you split those up into even smaller services because each of those new services had their own code base and database.

This is what we call microservice architecture. It's an architectural style where your application is made up of lots of small services (microservices) that work together. Each microservice has its database, programming language, and the team responsible for maintaining it — but they don't have access to any other microservices in the system.

Building a backend system in your microservice architecture is froth with a few problems, so in this article, you are going to learn how to optimize your APIs in a backend service, and address some problems you are likely going to face.

What is API optimization?

API optimization is a process that can be used to improve the performance of your application. Optimizing your API can help reduce latency, decrease response times and improve the user experience.

Why should you optimize your API?

By optimizing your API, you will be able to:

  1. Shorten the amount of time it takes developers to integrate with your API.

  2. Reduce the amount of code required to integrate. This will save you money and increase developer productivity.

  3. Reduce the number of bugs in your API to improve user satisfaction and save money on support costs.

  4. Serve a large number of users who are using your API with various devices, browsers, and networks.

Five ways to optimize your backend APIs in a microservice architecture

When developing your backend APIs in a microservice architecture, some optimizations must be made to improve application performance. Here are some ideas to help you get there.

1. Use the APIGateway pattern instead of directing clients to multiple microservices.

Direct client to multiple microservices patterns

The direct client to multiple microservices patterns allows one client to communicate with multiple microservices by connecting directly to their backend APIs.

One significant disadvantage of this pattern is that it can be difficult to manage the overall architecture. This is because you must configure all of the communication between each of your clients and each service, which can be time-consuming and error-prone.

Another disadvantage is that it does not scale well. When there are too many microservices, it becomes more difficult for your clients to communicate with each one, slowing their ability to send requests and receive responses from each one. This may imply that your system isn't capable of handling as much traffic as it could. Also, security may be an issue in this pattern because all of your microservices are exposed to external API access, exposing your application to multiple points of attack.

API gateway pattern

An API gateway pattern, on the other hand, is a microservice pattern that enables different microservices to work collaboratively in a distributed system. The API gateway pattern is made up of two parts: the gateway and the API clients. The gateway is in charge of receiving client requests, processing them, and responding to them. The client is in charge of sending requests to and receiving responses from the gateway.

The pattern allows you to build an API gateway that receives calls from other microservices and responds to them on their behalf. The API gateway has its own set of rules and policies that it enforces to ensure that only the appropriate calls are made to the appropriate microservices.

With this pattern, all of your microservices are protected within an internal API system, with only the API Gateway exposed to the outside world. It improves each microservice's autonomy, scalability, and deployment flexibility.

2. Use one database per service instead of a shared-database pattern.

Shared-database pattern

The shared-database pattern is a microservice architecture that employs a shared database to establish a single point of data truth. Because the same database is used across the microservices, you can perform complex queries on the data. While this works well for small applications, it can become complicated for larger ones. For example, if one microservice changes its database schema, other microservices may need to be notified so that they can update their database schema accordingly. There is also a single point of failure for your application because if the database fails, all of your services fail. Because all database workload from all of your microservices is routed to a single database, your API performance may suffer.

Database-per-service pattern

The Database-per-Service pattern is one in which each microservice has its database that is configured to be isolated from the others. Each microservice has its database, which can only be accessed by the microservice to which it belongs. This prevents the various databases from interfering with each other or becoming mixed up, making it easier to manage and keep them clean. This pattern directs all database workloads to their respective databases, making it easier to maintain and scale.

Data from each service can only be accessed through the API provided by the service. While this may be fine for backend systems that do not require complex data queries across multiple microservice data sets, it can lead to "API Hell" for those that do.

3. Avoid “API Hell”.

API Hell is a microservice API architecture design pattern in which a single data query action requires multiple API requests to multiple microservices. The network latency that exists when making network requests to other microservices, as well as the n+1 issues that may arise when combining queries for different microservices, make this approach a bad idea.

In an e-commerce application, for example, you might want to get all users' orders and order delivery information. The User microservice holds your users' data, the Order microservice holds your orders, and the Delivery microservice holds your delivery information. To obtain the final data, first, request the User microservice to obtain the users (n), then for each user, obtain their orders from the Order microservice (n+1), and finally, obtain their delivery information for each order (n+2). Keep in mind that each data request requires an API call. While this may not be noticeable when dealing with small amounts of data, it can cause a significant drop in API performance as your data grows. So, how do you solve this?

Here are some approaches you can take to deal with this:

- Duplicate your data:

You can choose to make a copy of the database table that contains related data between microservices. Create a system that updates each copy of the database table when the original table is updated. This allows you to easily combine those data and create your complex query.

The problem with this approach is that it does not ensure data consistency and integrity. Data updates will not be real-time, and any problems that cause a failure in the update of other copies of a database will cause them to be out of sync with inconsistent data.

- Directly read microservices databases from your API Gateway.

You can delegate the heavy lifting to your API gateway. For SQL databases used by microservices, you can create a new read-only database user that reads and combines database tables from different microservices and performs complex join queries at the database level. You can avoid the n+1 problem and data inconsistency issues while still retaining the flexibility and scalability that microservices provide. Data integrity can be ensured because ApiGateway access is read-only, and updates to the database can only be made by the microservice that owns it.

The temptation to move some business logic to the APIGateway is a problem with this approach. You should avoid this at all costs because you undermine the purpose of microservices. Only queries that are not microservices specific should be directed to your APIGateway's database query modules. Database write actions (update, create, and delete) and complex microservices-specific actions are still routed through the API to the appropriate microservice.

4. Implement event-based actions.

Microservices rely heavily on event-based actions. They enable the microservice to sync with other services as needed, reducing overall system latency. This is especially important when multiple services are involved in an interaction, or when working on an application that requires quick action and response times. Client requests to your API Gateway can trigger events, which are then handled by the corresponding microservice.

5. Implement Cache

Caching can be used in a microservice architecture to improve performance and scalability by reducing the number of API calls made to backend services. It's especially useful if your application receives a lot of traffic or needs to handle a lot of requests per second. Requests to other microservices that do not return frequently changing data, such as a list of countries or regions, can be cached on your APIGateway. Instead of making API requests to the microservice every time, your APIGateway can easily get this data from the cache.

Conclusion

So far, you’ve learned about API optimization for microservice architecture. You understood why you need to optimize your APIs and got introduced to five ways to optimize your APIs which include:

  1. Use the APIGateway pattern instead of directing clients to multiple microservices.

  2. Use one database per service instead of a shared-database pattern.

  3. Avoid “API Hell”.

  4. Implement event-based actions.

  5. Implement Cache.

You can connect to me on Twitter @drayfocus, and Linkedin Akinola Ayomide

Cheers!