API Gateway with .NET Core
Mohamed Sameh
Full Stack .NET Developer | C# | Asp.net Core | Angular | SQL | Software Developer | Web Development | Backend Developer
Direct client-to-microservice communication
Before delving into the intricacies of an API gateway, let's first explore the typical scenario where users seek to interact with various microservices.
The figure below describes microservices architecture, users often interact directly with individual microservices to perform specific actions or access particular functionalities. This direct communication model involves users reaching out to the various microservices that collectively form the application's backend.
The journey begins when a user, through a client application (e.g., web browser, mobile app), initiates a request. This request could be to retrieve information, submit data, or execute a specific function within the application.
Depending on the user's request, the client might need to communicate with multiple microservices. This results in separate requests being sent to each relevant microservice to fulfill the user's needs.
Side effects of the previous pattern:
API Gateway Introduction
Given your awareness of the potential drawbacks associated with direct client-to-microservice communication, you may be contemplating the optimal approach for facilitating such interactions!
This is where the API gateway comes into play, stepping in to fulfill its pivotal role.
Now, what is the API Gateway?
An API Gateway is a server that acts as an API front-end, receiving API requests, enforcing throttling and security policies, passing requests to the back-end service, and then passing the response back to the requester.
It acts as an intermediary between clients and microservices, providing several benefits for managing, securing, and optimizing the communication between different components of a software application.
The figure below shows how a custom API Gateway can fit into a simplified microservice-based architecture with just a few microservices:
The API gateway sits between the client apps and the microservices. It acts as a reverse proxy, routing requests from clients to services. It can also provide other cross-cutting features such as authentication, SSL termination, and cache.
API Gateway Features:
Here's some key concepts and functionalities of an API Gateway that we are going to implement in our demo:
In our scenario, we will leverage Identity Server 6 (Duende Server) for the authentication and authorization processes.
Load balancing means to distribute the request across multiple microservices using algorithms like Round Robin Algorithm
I will demonstrate the concept of Quality of Service later in our demo app.
What is Ocelot?
Ocelot is an open-source API Gateway for .NET designed to work with microservices architectures. It provides a set of features that simplify the process of building, deploying, and managing APIs in a microservices environment.
Our Demo Application structure
Our application will feature two straightforward ASP.NET Core Web APIs, namely "MoviesAPI" and "SeriesAPI". These APIs will provide random responses with movie and series names. To access these APIs, we'll employ our API Gateway, generated through Ocelot configurations. Furthermore, we'll enhance the security of our APIs by implementing Identity Server 6.
The figure below demonstrates our application structure:
In our scenario, the Postman application will function as the client.
Microservices implementation
After creating an empty solution, I added an ASP.NET Core Web API project, naming it MoviesAPI. Similarly, I added another project for SeriesAPI.
[ApiController]
[Route("[controller]")]
public class MoviesController : ControllerBase
{
private static readonly string[] Movies = new[]
{
"Die Another Day", "Top Gun", "Grease", "Dil Bechara",
"Jurassic Park"
};
[HttpGet]
public ActionResult Get()
{
var rng = new Random();
return Ok(Movies[rng.Next(Movies.Length)]);
}
}
[ApiController]
[Route("[controller]")]
public class SeriesController : ControllerBase
{
private static readonly string[] Series = new[]
{
"Dark Tourist", "Love is Blind", "Dracula", "Troy", "Bandish Bandits"
};
[HttpGet]
public ActionResult Get()
{
var rng = new Random();
return Ok(Series[rng.Next(Series.Length)]);
}
}
Ocelot configurations
We need to install the "Ocelot" package, along with the following packages: "Ocelot.Cache.CacheManager" and "Ocelot.Provider.Polly" which are essential for caching and QoS. Configurations.
Now, let's create a JSON file named "ocelot.json" to encompass all the API gateway configurations.
Our JSON file will feature a primary object containing two key-value pairs. The first pair holds global configurations, while the second comprises an array of routes, each representing a microservice configuration.
To enable the various configurations mentioned later, add the following line to the "program.cs" file in our ApiGateway:
builder.Configuration.AddJsonFile("ocelot.json",optional: false,reloadOnChange: true);
"GlobalConfiguration": {
"BaseUrl": "https://localhost:7105"
}
BaseUrl: represents the URL where your API gateway is hosted and operational.
"Routes": [
{MoviesAPI configurations...}, {SeriesAPI configurations...}
],
"UpstreamPathTemplate": "/mymovies",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/movies",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 7161
}
]
UpstreamPathTemplate: Represents the route data that will be appended to the Ocelot URL to access our target URL.
UpstreamHttpMethod: Represents the action verbs permitted for use in our microservice.
In our scenario, only "Get" is allowed as we aim to retrieve data. However, depending on your business requirements, you can include multiple action verbs.
DownstreamPathTemplate: Represents the specific route data we intend to access. This route data must exist as a valid action method within our microservice.
DownstreamScheme: Represents our running protocol.
DownstreamHostAndPorts: Represents an array of objects, with each object containing information about the hosting server for your microservice.
You might have multiple instances of your microservice running on different hosts and ports.
With that in place, our API gateway is now ready to communicate with the microservice through its URL and provided route data.
Test case 1
As you can observe, we have supplied the Ocelot URL (not the microservice URL) to access the data in MoviesAPI, incorporating the "UpstreamPathTemplate" into this URL.
Test result
Excellent! We've successfully obtained a random movie name from our designated microservice.
Ocelot Features
Now, let's explore some of the powerful features that Ocelot offers. These capabilities include securing your APIs, controlling and limiting incoming requests, aggregating results from multiple microservices, and much more!
Rate Limiting
As mentioned earlier, we have the ability to manage the flow of requests to our microservices by setting the rate limits for each client (specify the number of the requests per specific period of time). These configurations can be defined as follows:
"RateLimitOptions": {
"EnableRateLimiting": true,
"Period": "15s",
"PeriodTimespan": 5,
"Limit": 3
}
EnableRateLimiting: This property is meant to enable the rate limiting feature.
Period: Represents specific period of time that you want to set for maximum requests to be sent within it.
Limit: Represents the maximum number of requests allowed within the specified time before...
PeriodTimespan: Represents the time interval, in seconds, that a client must wait after reaching the limit of allowed requests before being able to send new requests.
Test case 2
Now, I will make the same request multiple times, exceeding the limit of three requests, to observe the outcome.
领英推荐
Test result
Now, as evident from the response, it indicates that we have surpassed the defined limit for the number of requests.
Authentication and Authorization
One of the most compelling features of Ocelot is its ability to incorporate a security layer by authenticating clients seeking access to a microservice.
This is achieved through integration with Identity Server, enabling Ocelot to validate the token provided by the client upon request. If the token is valid, the user is granted access to the requested microservice.
To achieve this, I will establish a new ASP.NET Web API project specifically designed to incorporate all Identity Server 6 configurations.
For a comprehensive guide on implementing Identity Server 6, refer to this well-documented article .
Now, let's integrate these configurations into our JSON file:
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": [
"api1"
]
}
AuthenticationProviderKey: Represents the key we've defined in our program.cs file within the ApiGateway project.
AllowedScopes: Represents an array of all the scopes we've defined in our IdentityServer.
With that in place, let's now run our next test without providing any access token.
Test case 3
Test result
As evident from the response, we received a status code 401, indicating that the user is not authenticated to access this API.
Now, let's run the same test but with adding the access token so let's get it.
Here is the complete set of information required to obtain our access token from Identity Server.
Great! Now that we have obtained our access token, we can include it in the request header to Ocelot.
Test case 4
I'll make the same request to our default URL but now with providing our access token to the request headers.
Test result
Excellent! We have successfully received the response with a randomly generated movie name.
Caching
One valuable feature offered by Ocelot is its caching mechanism, allowing you to cache responses for a specified period. This can be achieved by adding the following configurations:
"FileCacheOptions": {
"TtlSeconds": 5,
"Region": "Development"
}
EnableRateLimiting: Represents the caching duration in seconds for the response data.
Region: Allows you to logically partition your cache into different regions based on your application's needs. Each region can store a separate set of cached data.
Before running your test, remember to add these configurations to your "program.cs" file. These configurations are related to caching and Quality of Service (QoS), which we will explain next.
builder.Services.AddOcelot(builder.Configuration)
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
})
.AddPolly();
Now, let's proceed with the next test to confirm that caching is enabled.
Test case 5
I'll make the same request to our default URL...
Response 1
Response 2
As evident from the last two responses, the time taken to obtain the API response has significantly reduced.
You can further validate this by inserting a breakpoint into your microservice and observing whether the request entered it multiple times or not.
Quality of Service
The concept of Quality of Service (QoS), as mentioned earlier, revolves around monitoring all requests arriving at the API gateway, with the goal of avoiding any unnecessary overhead.
For instance, if a request takes an extended period to process, QoS specifies a duration for each request, and if this limit is exceeded, it will block the request for a specified period.
Moreover, in case the server encounters an error, QoS can block all incoming requests until the error or exception is resolved.
This makes it a crucial and valuable feature provided by Ocelot.
Now, let's set our QoS configuration in our JSON file.
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 1,
"DurationOfBreak": 10000,
"TimeoutValue": 10000
}
ExceptionsAllowedBeforeBreaking: This parameter determines how many exceptions (errors) are allowed to occur before the circuit breaker is triggered. Once the number of exceptions surpasses this value, the circuit breaker "opens" and prevents further requests from being processed for a specified duration.
DurationOfBreak: Represents the duration, in milliseconds, for which the circuit breaker remains in the "open" state.
TimeoutValue: Represents the maximum time allowed for a request to be processed before considering it as a failure.
Now, let's run our test. To test the Quality of Service (QoS), I will make our main thread sleep for a duration greater than the value specified in TimeoutValue to observe whether the request will be blocked or not.
[HttpGet]
public ActionResult Get()
{
var rng = new Random();
Thread.Sleep(15000);
return Ok(Movies[rng.Next(Movies.Length)]);
}
Test case 6
I'll make the same request to our default URL...
Test result
Great! As you can notice, we received a 503-status code, indicating that the request timed out, and the circuit breaker is now enabled.
Aggregation
The last feature we'll cover in our article is aggregation, which allows the API gateway to consolidate results from multiple microservices into a single response object with specified keys provided to each microservice.
Let's add the necessary configurations for that:
Firstly, we'll assign a key for each microservice. In our case, I will use 'Movie' for the MoviesAPI microservice and 'Serie' for the SeriesAPI microservice."
"Key": "Movie"
"Key": "Serie"
Secondly, I will incorporate the main aggregation configurations into our primary object:
"Aggregates": [
{
"UpstreamPathTemplate": "/agg",
"RouteKeys": [
"Movie",
"Serie"
]
}
]
UpstreamPathTemplate: Represents the upstream path template, i.e., the path on the API Gateway (Ocelot) that will trigger this aggregate route. In this case, requests to the path "/agg" will be processed by this aggregate route.
RouteKeys: It's an array of keys that represent the different microservices or routes that this aggregate route will call. In this example, it mentions "Movie" and "Serie" as route keys.
Now, let's run our last test.
Test case 7
This time, I will access this URL, noting the modification in the route data to 'agg'.
Test result
Great! Now, I have received an object with the keys provided for each microservice, and for each key, I obtained response data from the corresponding microservice.
Now that we've covered most of the essential features of Ocelot, it's important to note that there are many other features you can explore and implement in your project based on your business needs.
The key is to have a good understanding of the concept of API gateways.
You can discover all of Ocelot's features in their comprehensive documentation .
Thanks for reading. I hope this was helpful!
The example code is available on GitHub.
Cloud Solution Architect/ Solution Architect
11 个月D?ng Nguy?n Anh
Senior Software Engineer @ Link Development | C# | Asp.net Core | React | Angular | SQL | Software Developer | Web Development | Backend Developer
1 年Totally Appreciated ????
software Engineer | .Net Developer
1 年Great Article ??
.NET Developer | CS & ITI Graduate
1 年Great effort ??
Software Engineer
1 年Is ocelot's maintainance support back?