The easy way to create API Gateway
Serhii Korol
Posted on November 15, 2024
Hi! Nowadays, Microservices are widely used in software development. When you need to use several clients for API, the best solution is to use API Gateway. I'll show you how fast and straightforward it is to implement API Gateway.
What is this API Gateway?
The API Gateway is a service that communicates between clients and business logic. In other words, clients don't have direct access to business logic and their API. The API Gateway handles all input requests and redirects them to the appropriate microservice. Why is this complication needed? The API Gateway gives additional security and can handle and validate input requests, modify requests and responses, route requests, handle exceptions, and log them in one place.
In real life, the API Gateway is complicated enough to implement. However, there is an existing, straightforward approach to implementing it. You only need to adjust it according to your needs and requirements.
Implementing
You must create a separate project and install this AspNetCore.ApiGateway
NuGet package for implementation. The base implementation consists of only one method, which should receive the IApiOrchestrator
argument. This argument uses three main methods: AddApi
, AddHub
, and AddEventSource
. I'll be using a more straightforward API.
public static class OrchestrationService
{
public static void Create(IApiOrchestrator orchestrator, IApplicationBuilder app)
{
orchestrator.AddApi("weatherservice", "https://localhost:5003/")
.AddRoute("forecast", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/forecast", ResponseType = typeof(IEnumerable<WeatherForecast>) })
.AddRoute("add", GatewayVerb.POST, new RouteInfo { Path = "weatherforecast/summaries/add", RequestType = typeof(WeatherSummaryRequest), ResponseType = typeof(string[]) });
}
}
The next step is to configure and register this service.
builder.Services.AddApiGateway(options =>
{
options.UseResponseCaching = false;
options.ResponseCacheSettings = new ApiGatewayResponseCacheSettings
{
Duration = 60,
Location = ResponseCacheLocation.Any,
VaryByQueryKeys = ["apiKey", "routeKey"]
};
});
app.UseApiGateway(orchestrator => OrchestrationService.Create(orchestrator, app));
It's mega simple of base functionality. Sure, you need authorization, handlers, and validators.
Authorization
There are two ways to authorize your API: HTTP verb or global.
builder.Services.AddScoped<IGatewayAuthorization, AuthorizationService>();
builder.Services.AddScoped<IGetOrHeadGatewayAuthorization, GetAuthorizationService>();
public class AuthorizationService : IGatewayAuthorization
{
public async Task AuthorizeAsync(AuthorizationFilterContext context, string apiKey, string routeKey, string verb)
{
await Task.CompletedTask;
}
}
public class GetAuthorizationService : IGetOrHeadGatewayAuthorization
{
public async Task AuthorizeAsync(AuthorizationFilterContext context, string apiKey, string routeKey)
{
await Task.CompletedTask;
}
}
Action filters
The same goes for action filters, but each HTTP verb has a separate interface.
builder.Services.AddScoped<IGatewayActionFilter, ActionFilterService>();
builder.Services.AddScoped<IPostGatewayActionFilter, PostActionFilterService>();
public class ActionFilterService : IGatewayActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, string apiKey, string routeKey, string verb)
{
await Task.CompletedTask;
}
}
public class PostActionFilterService : IPostGatewayActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, string apiKey, string routeKey)
{
await Task.CompletedTask;
}
}
Exception filters
A similar approach can also register the exception filters.
builder.Services.AddScoped<IGatewayExceptionFilter, ExceptionFilterService>();
builder.Services.AddScoped<IPostGatewayExceptionFilter, PostExceptionFilterService>();
public class ExceptionFilterService : IGatewayExceptionFilter
{
public async Task OnExceptionAsync(ExceptionContext context, string apiKey, string routeKey, string verb)
{
await Task.CompletedTask;
}
}
public class PostExceptionFilterService : IPostGatewayExceptionFilter
{
public async Task OnExceptionAsync(ExceptionContext context, string apiKey, string routeKey)
{
await Task.CompletedTask;
}
}
Result filters
For handling results, use this.
builder.Services.AddScoped<IGatewayResultFilter, ResultFilterService>();
builder.Services.AddScoped<IPostGatewayResultFilter, PostResultFilterService>();
public class ResultFilterService : IGatewayResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, string apiKey, string routeKey, string verb)
{
await Task.CompletedTask;
}
}
public class PostResultFilterService : IPostGatewayResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, string apiKey, string routeKey)
{
await Task.CompletedTask;
}
}
Hub filters
If you use Signalr
, you'll can use this:
builder.Services.AddScoped<IGatewayHubFilter, GatewayHubFilterService>();
public class GatewayHubFilterService : IGatewayHubFilter
{
public ValueTask<object> InvokeMethodAsync(HubInvocationContext invocationContext)
{
return new ValueTask<object>();
}
public Task OnConnectedAsync(HubLifetimeContext context)
{
return Task.CompletedTask;
}
public Task OnDisconnectedAsync(HubLifetimeContext context, Exception exception)
{
return Task.CompletedTask;
}
}
Middleware
For middleware, use this:
builder.Services.AddTransient<IGatewayMiddleware, GatewayMiddlewareService>();
public class GatewayMiddlewareService : IGatewayMiddleware
{
public async Task Invoke(HttpContext context, string apiKey, string routeKey)
{
await Task.CompletedTask;
}
}
Swagger
Under the hood of the NuGet package is an implemented controller for most HTTP verbs, and the Swagger gets all these operators. However, you do not need all HTTP verbs: GET, POST, PUT, PATCH, HEAD, DELETE. What if you need only GET and POST? To resolve this task, you can create a custom Swagger filter. The API Gateway also has the query parameter you don't need. This filter can filter specified operators and parameters.
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My Api Gateway", Version = "v1" });
c.DocumentFilter<SwaggerDocumentFilter>();
});
public class SwaggerDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var verbs = new[] { OperationType.Get, OperationType.Post };
foreach (var path in swaggerDoc.Paths)
{
if (path.Key.StartsWith("/api/Gateway/{apiKey}") || path.Key.StartsWith("/api/Gateway/orchestration"))
{
var operationsToRemove = path.Value.Operations
.Where(o => !verbs.Contains(o.Key))
.Select(o => o.Key)
.ToList();
foreach (var operation in operationsToRemove)
{
path.Value.Operations.Remove(operation);
}
foreach (var operation in path.Value.Operations)
{
var queryParam = operation.Value.Parameters.FirstOrDefault(p => p.In == ParameterLocation.Query);
if (queryParam != null)
{
operation.Value.Parameters.Remove(queryParam);
}
}
}
else
{
swaggerDoc.Paths.Remove(path.Key);
}
}
}
}
Without this filter, you'll see all endpoints, but your microservice doesn't use Signalr and has only GET and POST methods.
You also don't need a query parameter that was implemented by default.
With this filter, you can show only those methods that you need.
And you can hide unused query parameters:
Pros and cons
This package definitely can be useful in software development when you don't need high flexibility.
Pros
- simple
- free
Cons
- low flexibility: I want to set only needed operators
- not working with type string request
- default parameters for all operators
- you should adjust your swagger
Source code: link
About the project: link
Happy coding!
Posted on November 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.