API Gateway with .NET Core

API Gateway with .NET Core

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:

  • Tight Coupling: Direct communication can result in tight coupling between the client and individual microservices. Any changes in the microservices' APIs or functionalities may necessitate corresponding adjustments in the client application.
  • Complex Client Logic: As the client interacts with multiple microservices, it must manage complexities such as error handling, data transformation, and the coordination of responses. This often leads to intricate client-side logic, especially as the number of microservices increases. not preferred for Single Page Applications (SPA)
  • Security Concerns: Direct communication exposes microservices to potential security risks, especially if security mechanisms such as authentication and authorization are not consistently applied across all microservices.
  • Scalability Concerns: Direct communication may lead to uneven load distribution among microservices, affecting scalability.


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:

  • Routing: It's the API Gateways main feature which state of handling the routing of requests to the appropriate microservices based on the requested endpoint. This enables the organization of microservices behind a unified API, simplifying the client's interaction.
  • Authentication and Authorization: API Gateways often handle authentication and authorization, ensuring that only authorized users or systems can access certain APIs. This includes features like API key validation, OAuth token verification, and user role-based access control.

In our scenario, we will leverage Identity Server 6 (Duende Server) for the authentication and authorization processes.

  • Load Balancing and Scaling: API Gateways can distribute incoming requests across multiple instances of microservices, providing load balancing. This improves the scalability and resilience of the system.

Load balancing means to distribute the request across multiple microservices using algorithms like Round Robin Algorithm

  • Rate Limiting and Throttling: API Gateways can enforce rate limits and throttling to prevent abuse, control traffic spikes, and ensure fair usage of resources. This helps in maintaining system stability and preventing denial-of-service attacks.
  • Aggregation: An API Gateway can aggregate multiple API requests into a single request, reducing the number of roundtrips between the client and the server. This is especially useful when a client needs data from multiple microservices to fulfill a single user request.
  • Caching: To improve performance, API Gateways can cache responses and serve them directly to clients for repeated requests. This reduces the load on the microservices and improves response times.
  • Quality of Service: API Gateways supports Quality of Service (QoS). by using a circuit breaker when making requests to any microservice.

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.

  • MoviesAPI controller:

    [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)]);
        }
    }        

  • SeriesAPI controller:

    [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);        

  • Global Configuration

    "GlobalConfiguration": {
        "BaseUrl": "https://localhost:7105"
    }        

BaseUrl: represents the URL where your API gateway is hosted and operational.


  • RoutesThis key will have an array value containing objects, where each microservice is represented by an object. In our case, with "MoviesAPI" and "SeriesAPI," we will have two objects in this array.

    "Routes": [
       {MoviesAPI configurations...}, {SeriesAPI configurations...} 
    ],        

  • Microservice Configurations:The following configurations constitute the primary setup for each microservice, detailing how the access URL (URL provided by ocelot) will correspond to the actual URL (microservice URL), along with the running host and port for our microservice.

  "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.

Ex. "https://localhost:7105/mymovies "

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.

Viet Pham

Cloud Solution Architect/ Solution Architect

11 个月
回复
Ayman Kheireldeen

Senior Software Engineer @ Link Development | C# | Asp.net Core | React | Angular | SQL | Software Developer | Web Development | Backend Developer

1 年

Totally Appreciated ????

Mohamed salem

software Engineer | .Net Developer

1 年

Great Article ??

Mohammed Alaa

.NET Developer | CS & ITI Graduate

1 年

Great effort ??

Mahmoud Tarek

Software Engineer

1 年

Is ocelot's maintainance support back?

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了