API Gateway using .NET Core, Ocelot and Consul

Paulius Juozelskis
Apr 23 · 5 min read
version: '3.4'services:
orders:
image: orders:latest
build:
context: .
dockerfile: src/services/Orders/Dockerfile
container_name: orders
deliveries:
image: deliveries:latest
build:
context: .
dockerfile: src/services/Deliveries/Dockerfile
container_name: deliveries
api.gateway:
image: apigateway:latest
build:
context: .
dockerfile: src/gateways/API.Gateway/Dockerfile
container_name: apigateway
consul:
image: consul:latest
command: consul agent -dev -log-level=warn -ui -client=0.0.0.0
hostname: consul
container_name: consul
version: '3.4'services:
orders:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ServiceConfig__serviceDiscoveryAddress=http://consul:8500
- ServiceConfig__serviceAddress=http://orders:80
- ServiceConfig__serviceName=orders
- ServiceConfig__serviceId=orders-v1
ports:
- "80"
deliveries:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ServiceConfig__serviceDiscoveryAddress=http://consul:8500
- ServiceConfig__serviceAddress=http://deliveries:80
- ServiceConfig__serviceName=deliveries
- ServiceConfig__serviceId=deliveries-v1
ports:
- "80"
api.gateway:
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "80:80"
consul:
ports:
- "8500"
Service discovery using a self-registration pattern
public static class ServiceConfigExtensions
{
public static ServiceConfig GetServiceConfig(this IConfiguration configuration)
{
if (configuration == null) {
throw new ArgumentNullException(nameof(configuration));
}

var serviceConfig = new ServiceConfig {
ServiceDiscoveryAddress = configuration.GetValue<Uri>("ServiceConfig:serviceDiscoveryAddress"),
ServiceAddress = configuration.GetValue<Uri>("ServiceConfig:serviceAddress"),
ServiceName = configuration.GetValue<string>("ServiceConfig:serviceName"),
ServiceId = configuration.GetValue<string>("ServiceConfig:serviceId")
};

return serviceConfig;
}
}
public class ServiceDiscoveryHostedService : IHostedService 
{
private readonly IConsulClient _client;
private readonly ServiceConfig _config;
private string _registrationId;
public ServiceDiscoveryHostedService(IConsulClient client, ServiceConfig config)
{
_client = client;
_config = config;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_registrationId = $"{_config.ServiceName}-{_config.ServiceId}";
var registration = new AgentServiceRegistration {
ID = _registrationId,
Name = _config.ServiceName,
Address = _config.ServiceAddress.Host,
Port = _config.ServiceAddress.Port
};
await _client.Agent.ServiceDeregister(registration.ID, cancellationToken);
await _client.Agent.ServiceRegister(registration, cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await _client.Agent.ServiceDeregister(_registrationId, cancellationToken);
}
}
public static void RegisterConsulServices(this IServiceCollection   services, ServiceConfig serviceConfig) {
if (serviceConfig == null)
{
throw new ArgumentNullException(nameof(serviceConfig));
}

var consulClient = CreateConsulClient(serviceConfig);
services.AddSingleton(serviceConfig);
services.AddSingleton<IHostedService, ServiceDiscoveryHostedService>();
services.AddSingleton<IConsulClient, ConsulClient>(p => consulClient);
}
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"ServiceName": "orders",
"UpstreamPathTemplate": "/api/ordering/{everything}",
"UpstreamHttpMethod": [ "Get" ]
},
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"ServiceName": "deliveries",
"UpstreamPathTemplate": "/api/logistics/{everything}",
"UpstreamHttpMethod": [ "Get" ]
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Host": "consul",
"Port": 8500,
"Type": "Consul"
}
}
}
public static void Main(string[] args)
{
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((context, config) =>
{
config
.SetBasePath(context.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("ocelot.json")
.AddEnvironmentVariables();
})
.ConfigureServices(s => {
s.AddOcelot().AddConsul();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
})
.UseIISIntegration()
.Configure(app =>
{
app.UseOcelot().Wait();
})
.Build()
.Run();
}

Paulius Juozelskis

Written by

.NET developer interested in Big Data, Machine Learning and Serverless computing

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade