Exposing microservices with an API gateway and the API composition pattern

July 17, 2020

Microservices are a widely spread pattern in modern software architectures. Tools like Docker, Kubernetes and modern CI/CD pipelines have enabled software teams to build highly scalable distributed systems in which each microservice can be operated by a small team. In fact, the implicit notion of microservices is to be self-contained and focused on a single purpose. Thus, implemented correctly, microservices enable your system to be highly flexible and your teams to operate independently. While this is a major upside for the development process itself, it comes with certain challenges when you want to expose your system to external users.

Check out this well-written blog series of NGINX, if you want to get deeper into microservices architecture.

Flexibility leads to complexity

Don't get me wrong. The microservices pattern takes away a lot of complexity introduced by highly cohesive and monolithic systems, but it introduces a new kind of complexity. The fact that teams are flexible in the way they build their microservices - even if you have certain guidelines in place - makes it more difficult for your clients to access your system. They possibly need to

  • talk with all the different services
  • understand which service provides the data they need
  • communicate via different protocols.

In terms of user experience, this is not optimal and might lead to frustration on the client side. The complexity increases the learning curve of your system.

microservices

A simplified overview of clients A,B and C which require different data from individual services X,Y, and Z of a microservice architecture. All are accessible via different protocols: REST, GraphQL and gRPC.

The picture above illustrates such a microservice architecture. Even if your microservices do not use different communication protocols such as in this example: Talking to multiple hosts might still require multiple ways of authentication, understanding different styles of API design, etc..

An API gateway as the single entry point

An API gateway might help you with this complexity. As the term "gateway" implies, the main purpose of an API gateway is to be the single entry point for your microservice architecture. It is build on top of your individual services and routes requests of clients to these services. It does not contain or know of any business or domain logic of the individual services.

api-gateway

An API gateway sitting between your microservices and the clients who want to access these services. The API gateway provides a unified interface to the clients and hides implementation details of the services X, Y, and Z.

Upsides of API gateways

As the illustration shows, an API gateway makes it possible to easily hide implementation details of our microservice architecture and unify the external interface. This enables clients to have a single point of entry to your system, thus, making it more accessible. Besides these UX improvements, the API gateway can be used to centralize and handle topics such as

  • authentication
  • permission handling
  • API security
  • policies (rate limits, allow-listing, etc.)

This enables the individual microservice teams to concentrate on the core business/domain logic of their service and improves development speed. The API gateway might further support your system by providing extensive (access) logging and collecting performance as well as business related metrics of your clients.

Downsides of API gateways

Introducing a new part in your architecture always comes with certain challenges and so does an API gateway. The API gateway is another piece of software in our system that has to be developed, deployed and maintained. While this was also one of the upsides listed above, a single point of entry to our application might also become a bottleneck in two different ways.

First, it has to be highly scalable. Since it is another layer between every request of the client and our microservices, it needs to have as little impact on the request latency as possible. Second, it can slow down development, since every microservice has to be exposed via the gateway.

The API composition pattern

So far, we have introduced the API gateway simply to route each request of a client to a microservice and back. With microservices being self-contained and the owner of their domain and data models, they often fail the expectations of clients on how to consume the data. Clients might want to query data of multiple sub-domains at the same time or just a small portion of the data a single microservice offers. To fit these needs, the API composition pattern can be applied.

api-composition

Overview of an API gateway with API composition. Composition 1 composes data of service X and Y to a single resource, while Composition 2 composes data of service Y and Z to a single resource.

Even though the API composition now manipulates the payloads of one or multiple microservice(s), an API composition still remains free of any domain or business logic. Each composition could also be defined as an aggregate of the microservices behind the API gateway to fit the clients need and improve the developer experience. This pattern introduces some more advantages. With API composition in place, we suddenly can

  • make up for bad design decisions and apply a new shiny interface
  • hide legacy systems and replace these whenever we have the time for it
  • enable specialists for API design and developer experience to create a consistent design of the overall API.

On the other hand, as with the API gateway itself, we introduce another layer in our overall system. Development teams have to be careful that API composition does not evolve to a replacement for good API design of the individual microservices or mixing up the domain logic and models within the API gateway.

Conclusion

API gateways and API composition are a useful tool to reduce complexity of microservice architectures. They can help with handling access and monitoring of the overall system, as well as enabling clients to request data as they expect it. Nevertheless, one has to be careful when introducing a new layer to a system, since it inevitably leads to more dependencies in the development chain.

What do you think of this approach? Have you faced similar challenges? If yes, how did you solve them? If you want to talk more on the topic of APIs and microservices, feel free to contact me on Twitter or via e-mail.