The urge towards adopting microservices architecture has been a welcome trend in the software development industry. Microservices architecture has been one of the most talked-about technologies in recent times – it has been embraced by many leading organizations worldwide. Originally developed to solve the limitations of monolithic systems, microservices architecture has seen a significant increase in popularity over the last several years, mainly due to increased scalability, flexibility, and performance.
Since microservices-based applications comprise several different services, you often need a common interface or gateway to call these services so that you define and manage all concerns in one place rather than replicate them across all downstream services. This is precisely where an API Gateway comes in. This article briefly discusses the concepts around microservices architecture and how you can work with API Gateways to have a consistent way to connect to the microservices. Let’s get started!
What Is Microservices Architecture?
Microservices architecture is a service-oriented architecture (SOA) variant in which an application comprises a collection of lightweight, loosely coupled, modular services. These services can be built to execute on various platforms and independently developed, tested, deployed, and managed.
Microservices architecture can replace long-running, complicated monolithic systems requiring considerable resource and management overhead. Microservice is a word that refers to a service having a limited set of functionalities rather than referring to the length of the code used to create it.
What Is an API Gateway?
When building microservices-based applications, an API Gateway is needed to have a central place where authentication, throttling, orchestration, etc., is implemented. Without an API Gateway in place, you might typically implement each of these in each service, and hence maintaining them for each service would be a daunting task at hand. An API Gateway decouples the service producer from its consumer, providing a security layer since you need not expose your microservices directly.
As soon as it receives a request, it breaks it into multiple requests (if needed) and then routes them to the appropriate downstream microservice. You can take advantage of an API Gateway to centralize, manage, and monitor the non-functional requirements of an application, orchestrate the cross-functional microservices, and reduce roundtrips. By managing requests consistently, an API Gateway can help reduce the latency and improve security.
The figure below illustrates an API Gateway used to connect to two downstream microservices named Customer and Product.
Usually, the service consumers or clients of a microservice don’t communicate with it directly. Instead, an API Gateway provides a single entry point for directing traffic to various microservices, as shown in the figure above. Hence the clients don’t have any direct access to the services and cannot exploit the services. If your API Gateway is behind the firewall, you can add an extra layer of protection around the attack surface.
An API Gateway pattern corresponds to two famous Gang of Four design patterns: Facade and Adapter. Like the Facade design pattern, an API Gateway provides an API to the consumers while encapsulating the internal architecture. An API Gateway enables communication and collaboration like in the Adapter design pattern, even if the interfaces are incompatible.
Why Do We Need an API Gateway?
A microservices-based application comprises many different microservices built using homogenous or heterogeneous technologies. An API Gateway provides a centralized point of entry for external consumers, regardless of the number or composition of the downstream microservices. An API Gateway can often contain an additional layer of rate-limiting and security.
Here are the main benefits of an API Gateway:
API Gateways and reverse proxies
There is a lot of confusion around a reverse proxy and an API Gateway. While there are similarities between them, there are subtle differences between the two as well.
Reverse proxy servers typically sit behind a firewall and route requests from the client to the appropriate back-end server. A reverse proxy is a lightweight API Gateway that comprises a few basic security and monitoring capabilities. So, if you need an API Gateway with basic features, a reverse proxy server should suffice. Note that a reverse proxy is incapable of performing transformation or orchestration.
An API gateway sits between the client and a set of back-end services and provides much more extensive security and monitoring capabilities than a reverse proxy server. An API Gateway provides support for comprehensive service orchestration, transformation, and mediation. It also offers extensive support for transport security – much more than a simple proxy can provide.
Introducing Ocelot
In this article, we are going to use Ocelot API Gateway. It is a lightweight, open-source, scalable, and fast API Gateway based on .NET Core and specially designed for microservices architecture. Basically, it is a set of middleware designed to work with ASP.NET Core. It has several features such as routing, caching, security, rate limiting, etc.
The Order Processing Microservices-Based Application
Let’s now put the concepts we’ve learned thus far into practice by implementing a concrete example. We’ll build an order processing application that illustrates how an API Gateway can be used to invoke each service to retrieve customer and product data using the Customer and Product microservice, respectively.
Typically, an order processing microservices-based application comprises microservices such as Product, Customer, Order, OrderDetails, etc. In this example, we’ll consider a minimalistic microservices-based application. This application will contain an API Gateway and two microservices – the Product and Customer microservice. The application would be simple so that we can focus more on building the API Gateway.
To execute the code examples shown in this article, here are the minimum requirements you should have installed in your system:
The solution structure
The application you are going to build will comprise the following projects as part of a single Visual Studio solution:
The Customer microservice project will comprise the following classes and interfaces:
The Product microservice project will contain the following types:
The following picture shows how the solution structure of the completed
application will look like:
Create the projects for the Order Processing application
Open a command shell and enter the following commands to create the three ASP.NET projects we need:
While the OrderProcessing
project is an empty ASP.NET project, the other two projects are WebAPI projects. Ensure that you delete the default controller and entity classes from these two projects as we don’t need them.
Create the Customer microservice
Create a new file named Customer.cs
at the root of the OrderProcessing.Customer
project with the following code in there:
Create the CustomerRepository class
Create an interface named ICustomerRepository
in a file named ICustomerRepository.cs
at the root of the OrderProcessing.Customer
project with the following code in there:
Create the CustomerRepository
class that implements the ICustomerRepository
interface at the root of the OrderProcessing.Customer
project as shown in the following code snippet:
In the Controllers
folder of the OrderProcessing.Customer
project, create an API controller named CustomerController
and replace the default code with the following:
Create the Product microservice
Create a new file named Product.cs
at the root of the OrderProcessing.Product
project with the following code in there:
Create the ProductRepository class
Next, you should create a new file called IProductRepository.cs
in the OrderProcessing.Product
project and write the following code to create the IProductRepository
interface.
Create the ProductRepository
class that implements the IProductRepository
interface at the root of the OrderProcessing.Product
project as shown in the following code snippet:
Create the ProductController class
In the Controllers
folder of the OrderProcessing.Product
project, create an API controller named ProductController
and replace the default code with the following:
Implement the API Gateway Using Ocelot
Now that the projects have been created with the necessary files in them, let’s implement the API Gateway using Ocelot.
Before going any further, you should be aware of the terms upstream and downstream. While upstream refers to the request sent by the client to the API Gateway, downstream is related to the request that the API Gateway sends to a particular microservice.
Install the required package
To work with Ocelot, you must install it in your ASP.NET Core project. In our case, you will install Ocelot in the OrderProcessing
project. You can do it by using the NuGet Package Manager inside Visual Studio IDE. Alternatively, you can execute the following command at the Package Manager Console window:
Implement routing
An Ocelot API Gateway accepts an incoming HTTP request and forwards it to a downstream service. Ocelot makes use of routes to define how a request is routed from one place to another. Add a new file named ocelot.json
to this project with the following content in there:
The above configuration specifies the downstream and upstream metadata (scheme, path, ports) for the customer and product microservices. So, while use the upstream metadata to call the endpoints specified here, the request is routed to the appropriate downstream service as specified in the downstream metadata. In other words, the downstream metadata is used to specify the internal service URL to redirect a request to when the API Gateway receives a new request.
You should add Ocelot to the service container by calling the AddOcelot
method in the ConfigureServices
method of the Startup
class as shown below:
Next, you should enable Ocelot in the Configure
method of the Startup
class by calling the UseOcelot
extension method as shown here:
Run the projects
Now make sure that you’ve made all three projects as startup projects. To do this, follow these steps:
Press the F5 key to run the application. Now send an HTTP Get request to the following URL from Postman or any other HTTP client of your choice:
The HTTP Get method of the Customer controller will be executed and the output will look like this:
Send an HTTP Get request from Postman to the following URL:
The request first goes to the API Gateway. Next, the API Gateway routes the request to the correct downstream microservice as specified in ocelot.json
. The HTTP Get method named GetAllProducts
of the ProductController
will be called, and the output will look like this:
Implement rate limiting
Rate limiting is a technique for controlling network traffic. It sets a limit on how many times you can perform a specific activity within a given period – for example, accessing a particular resource, logging into an account, etc. Typically, rate-limiting keeps track of the IP addresses and the time elapsed between requests. The IP address helps determine the source of a particular request.
A rate-limiting solution is adept at tracking the time elapsed between each request and the total number of requests in a particular period. If a single IP address makes excessive requests within a specific timeframe, the rate-limiting solution will reject the requests for a specified period.
In order to prevent your downstream services from being overburdened, Ocelot enables rate-limiting of upstream requests. The following configuration illustrates how you specify rate-limiting in Ocelot:
Let us now examine each of these options briefly:
Let us assume that rate limiting is applied to the Product microservice only. The updated ocelot.json
file will now look like this:
Now, run the application and send frequent requests (more than 1 per 5sec) and you’ll see the following error:
Implement caching
Caching is a widely popular technique used in web applications to keep data in memory so that the same data may be quickly accessed when required by the application. Ocelot provides support for basic caching. To take advantage of it, you should install the Ocelot.Cache.CacheManager
NuGet package as shown below:
Next, you should configure caching using the following code in the ConfigureServices
method:
Lastly, you should specify caching on a particular route in the route configuration using the following settings:
Here, we’ve set TtlSeconds
to 30 seconds which implies that the cache will expire after this time has elapsed. Note that you should specify your cache configuration in the FileCacheOptions
section. The Region
setting identifies the area within the cache that will contain the data. This way you can clear that area by using the Ocelot’s administration API.
To test this, you can set a breakpoint on the HTTP Get method named GetAllCustomers
in the CustomerController
class. When you execute the application and send an HTTP Get request to the endpoint, the breakpoint will be hit as usual. However, all subsequent calls to the same endpoint within 30 seconds (this is the duration we’ve specified) will fetch data, but the breakpoint will not be hit anymore.
Implement correlation ID
Ocelot enables a client to send a request Id in the header to the server. Once this request Id is available in the middleware pipeline, you can log it along with other information. Ocelot can also forward this request Id to the downstream services if required. A correlation ID is a unique identifier attached to every request and response and used to track requests and responses in a distributed application. You can use either a request Id or a correlation ID when working with Ocelot to track requests.
The primary difference between a request Id and a correlation ID is that while the former uniquely identifies every HTTP request, the latter is a unique identifier attached to a particular request-response chain. While you can use Request-Id
for every HTTP request, you can use X-Correlation-Id
for an event chain of requests and responses. X-Correlation-Id
is the name of the HTTP header attached to the downstream requests used to track HTTP requests that flow through multiple back-end services.
Ocelot must know the URL that it is running on in order to perform certain administration configurations. This is the BaseUrl
specified in the ocelot.json
file. Note that this URL should be the URL that your clients will see the API Gateway running on.
Here’s the complete source code of the ocelot.json
file for your reference:
Choosing an exemplary architecture for the needs of your business is the first and foremost step in building applications that are flexible, scalable, and high performant. One of the most significant advantages of microservices architecture is its support for heterogeneous platforms and technologies.
Your API Gateway can manage concerns such as security, rate limiting, performance, and scalability. However, you should be aware of handling the complexity it brings in and the risk of a single point of failure. Besides, there is a learning curve when you’re building microservices-based applications using an API Gateway. Possible performance degradation is yet another concern that you must handle.
The complete source code of the OrderProcessing application built throughout this article is available here.
Aside: Securing ASP.NET Core with Auth0
Securing ASP.NET Core applications with Auth0 is easy and brings a lot of great features to the table. With Auth0, you only have to write a few lines of code to get a solid identity management solution, single sign-on, support for social identity providers (like Facebook, GitHub, Twitter, etc.), and support for enterprise identity providers (like Active Directory, LDAP, SAML, custom, etc.).
On ASP.NET Core, you need to create an API in your Auth0 Management Dashboard and change a few things on your code. To create an API, you need to sign up for a free Auth0 account. After that, you need to go to the API section of the dashboard and click on “Create API”. On the dialog shown, you can set the Name of your API as “Books”, the Identifier as “http://books.mycompany.com”, and leave the Signing Algorithm as “RS256”.
After that, you have to add the call to services.AddAuthentication()
in the ConfigureServices()
method of the Startup
class as follows:
In the body of the Configure()
method of the Startup
class, you also need to add an invocation to app.UseAuthentication()
and app.UseAuthorization()
as shown below:
Make sure you invoke these methods in the order shown above. It is essential so that everything works properly.
Finally, add the following element to the appsettings.json
configuration file:
Note: Replace the placeholders YOUR_DOMAIN
and YOUR_AUDIENCE
with the actual values for the domain that you specified when creating your Auth0 account and the Identifier you assigned to your API.
This content was originally published here.