ASP.NET Core: Implementing API Gateway using Ocelot
In this article, we will learn about creating API gateways for ASP.NET Core Applications using Ocelot. In my previous article, I have explained the process of creating resilient microservices where I have explained its need for application development. Microservices architectures are proven as a backbone of modern high availability-based application architectures. Since microservices provide a Techo-Agnostic approach to designing and developing microservices, it's important to make sure that instead of exposing the public endpoints of each microservice to the client the architecture must create an API gateway so that the client will be provided access to all microservices using a single gateway endpoint.
What is API Gateway?
API Gateway acts as a FRONT-DOOR for all backend services. This provides a single access point for the business logic, and data from the backend services. The API Gateway has the following advantages
- Isolates the client application from knowing about the actual address of each of the Microservices.
- Isolates the client application from knowing about the actual application partitioning across various Microservices.
- Since the API gateway offers a single endpoint for client applications, the client app is free from making multiple frequent calls to fetch data.
- The client application does not need to know about the actual protocols used by Microservices for internal communication.
- API Gateway must be designed, developed, deployed, and managed separately.
- The stability of API Gateway must be taken care of because this is the only endpoint that is known to the client for communications.
- If the internal Microservices communication is taking time to process the request the response from the Gateway will be delayed.
Figure 1: API Gateway
As shown in Figure 1, the API Gateway offers a single-entry point to all actual back-end services against all requests received from the various client applications.
API Gateway implementation using Ocelot
Ocelot is designed to work with ASP.NET Core. The Ocelot provides a JSON-based configuration to define routes to actual backend Microservices. The sample basic configuration is shown in Listing 1
{ "Routes": [ { "DownstreamPathTemplate": "/api/Read", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/api/read", "UpstreamHttpMethod": [ "Get","Post" ] }, { "DownstreamPathTemplate": "/api/Write", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8002 } ], "UpstreamPathTemplate": "/api/write", "UpstreamHttpMethod": [ "Get" ] } ], "GobalConfiguration": { "BaseUrl": "http://localhost:8000" } }
Listing 1: Sample configuration for routes
The Route configuration contains the Routes array and the GlobalConfiguration sections. The most important part of GlobalConfiguration is the BaseUrl. This is the URL of the Gateway service host. The Ocelot must know the URL so that it can accept requests from client apps and perform various administrative operations e.g. reading received request headers. The Routes section contains the following parameters in it:
- DownstreamPathTemplate: This is the API endpoint of the actual backend service.
- DownstreamScheme: This is the Protocol used to communicate to the actual backend service.
- DownstreamHostAndPorts: This is an array that has the Host representing the actual host of the backend service, and the Port representing the access point of the actual backend service.
- UpstreamPathTemplate: This represents the API endpoint URL exposed by the API Gateway to accept requests from client applications.
- UpstreamHttpMethod: This represents HTTP request methods that are accepted from the client applications by API Gateway.
public class Product { public int ProductId { get; set; } public string? ProductName { get; set; } public int Price { get; set; } public int CategoryId { get; set; } }
public class Products : List<Product> { public Products() { Add(new Product() { ProductId=101,ProductName="P1",Price=1000,CategoryId=1}); Add(new Product() { ProductId = 102, ProductName = "P2", Price = 1000, CategoryId = 2 }); Add(new Product() { ProductId = 103, ProductName = "P3", Price = 1000, CategoryId = 3 }); Add(new Product() { ProductId = 104, ProductName = "P4", Price = 1000, CategoryId = 1 }); Add(new Product() { ProductId = 105, ProductName = "P5", Price = 1000, CategoryId = 2 }); Add(new Product() { ProductId = 106, ProductName = "P5", Price = 1000, CategoryId = 3 }); } }
public class ProductRepoService { Products prds = new Products(); public IEnumerable<Product> GetProducts() { return prds; } public IEnumerable<Product> AddProduct(Product p) { prds.Add(p); return prds; } }
...... builder.Services.AddScoped<ProductRepoService>(); ......
[Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { ProductRepoService prdServ; public ProductController(ProductRepoService s) { prdServ = s; } [HttpGet] public IActionResult Get() { return Ok(prdServ.GetProducts()); } [HttpPost] public IActionResult Post(Product c) { var result = prdServ.AddProduct(c); return Ok(result); } }
{ "Routes": [ { "DownstreamPathTemplate": "/api/Product", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/api/product/read", "UpstreamHttpMethod": [ "Get","Post" ] }, { "DownstreamPathTemplate": "/api/Category", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8002 } ], "UpstreamPathTemplate": "/api/category/read", "UpstreamHttpMethod": [ "Get" ] } ], "GobalConfiguration": { "BaseUrl": "http://localhost:8000" } }
using Ocelot.DependencyInjection; using Ocelot.Middleware; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); // THis will Load the App Configuration for Gateway builder.Services.AddOcelot(builder.Configuration); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthorization(); // Middleware //HTTP Pipeline app.UseOcelot().Wait(); app.MapControllers(); app.Run();