設定モデルの再設計
サービスディスカバリーの設定構造を再構築します。以下は再設計された構成オブジェクトです:
namespace ServiceDiscovery.Configuration
{
public class DiscoveryConfig
{
public ClusterEndpoint Cluster { get; set; }
public ServiceRegistration Service { get; set; }
}
}namespace ServiceDiscovery.Configuration
{
public class ServiceRegistration
{
public string ServiceId { get; set; }
public string Host { get; set; }
public string ServiceName { get; set; }
public string[] MetadataTags { get; set; }
public int ServicePort { get; set; }
public HealthCheckConfig HealthProbe { get; set; }
}
public class HealthCheckConfig
{
public string ProbeEndpoint { get; set; }
public int IntervalSeconds { get; set; }
}
}namespace ServiceDiscovery.Configuration
{
public class ClusterEndpoint
{
public string ApiUrl { get; set; }
public string Region { get; set; }
}
}サービス登録の拡張メソッド実装
DIコンテナへの登録処理をカプセル化します:
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ServiceDiscovery.Configuration;
namespace ServiceDiscovery.Extensions
{
public static class ServiceDiscoveryExtensions
{
public static IServiceCollection AddServiceDiscovery(
this IServiceCollection services,
IConfiguration config)
{
services.Configure<DiscoveryConfig>(
config.GetSection("DiscoveryConfig"));
services.AddScoped<ConsulClient>(sp =>
{
var configSnapshot = sp.GetRequiredService<
IOptionsSnapshot<DiscoveryConfig>>();
var endpoint = configSnapshot.Value.Cluster;
return new ConsulClient(cfg =>
{
cfg.Address = new Uri(endpoint.ApiUrl);
cfg.Datacenter = endpoint.Region;
});
});
services.AddScoped<IServiceRegistry, ServiceRegistry>();
return services;
}
public static async Task<IApplicationBuilder>
RegisterWithDiscovery(this IApplicationBuilder app)
{
using var scope = app.ApplicationServices.CreateScope();
var config = scope.ServiceProvider
.GetRequiredService<IOptionsSnapshot<DiscoveryConfig>>()
.Value;
var client = new ConsulClient(cfg =>
{
cfg.Address = new Uri(config.Cluster.ApiUrl);
cfg.Datacenter = config.Cluster.Region;
});
var registration = new AgentServiceRegistration
{
ID = $"{config.Service.ServiceName}-{Guid.NewGuid():N}",
Address = config.Service.Host,
Service = config.Service.ServiceName,
Tags = config.Service.MetadataTags,
Port = config.Service.ServicePort,
Check = new AgentServiceCheck
{
HTTP = config.Service.HealthProbe.ProbeEndpoint,
Interval = TimeSpan.FromSeconds(
config.Service.HealthProbe.IntervalSeconds)
}
};
await client.Agent.ServiceRegister(registration);
return app;
}
}
}サービス操作インターフェースの実装
サービス管理のコアロジックをインターフェースで抽象化:
namespace ServiceDiscovery
{
public interface IServiceRegistry : IDisposable
{
Task<ServiceEndpoint[]> GetAllServicesAsync();
Task DeregisterAllServicesAsync();
Task<int> DeregisterServiceAsync(string serviceId);
Task<int> RefreshClusterStateAsync();
Task<int> RegisterServiceAsync(ServiceRegistration config);
}
}using Consul;
using ServiceDiscovery.Configuration;
namespace ServiceDiscovery
{
public class ServiceRegistry : IServiceRegistry
{
private readonly ConsulClient _discoveryClient;
public ServiceRegistry(ConsulClient discoveryClient)
{
_discoveryClient = discoveryClient;
}
public async Task<ServiceEndpoint[]> GetAllServicesAsync()
{
var result = await _discoveryClient.Agent.Services();
return result.Response.Values.Select(s => new ServiceEndpoint
{
Host = s.Address,
ServiceId = s.ID,
Port = s.Port,
Name = s.Service,
Tags = s.Tags
}).ToArray();
}
public async Task DeregisterAllServicesAsync()
{
var services = (await _discoveryClient.Agent.Services())
.Response.Values;
foreach (var service in services)
{
await _discoveryClient.Agent.ServiceDeregister(service.ID);
}
}
public async Task<int> DeregisterServiceAsync(string serviceId)
{
var result = await _discoveryClient.Agent
.ServiceDeregister(serviceId);
return (int)result.StatusCode;
}
public async Task<int> RefreshClusterStateAsync()
{
var nodeName = await _discoveryClient.Agent.NodeName();
var result = await _discoveryClient.Agent.Reload(nodeName);
return (int)result.StatusCode;
}
public async Task<int> RegisterServiceAsync(ServiceRegistration config)
{
var registration = new AgentServiceRegistration
{
ID = config.ServiceId,
Address = config.Host,
Service = config.ServiceName,
Tags = config.MetadataTags,
Port = config.ServicePort,
Check = new AgentServiceCheck
{
HTTP = config.HealthProbe.ProbeEndpoint,
Interval = TimeSpan.FromSeconds(
config.HealthProbe.IntervalSeconds)
}
};
var result = await _discoveryClient.Agent
.ServiceRegister(registration);
return (int)result.StatusCode;
}
public void Dispose() => _discoveryClient?.Dispose();
}
}アプリケーション設定と登録フロー
appsettings.jsonの構成例:
{
"DiscoveryConfig": {
"Cluster": {
"ApiUrl": "http://consul-server:8500",
"Region": "tokyo-dc"
},
"Service": {
"Host": "api-service",
"ServiceName": "payment-service",
"MetadataTags": ["v2", "secure"],
"ServicePort": 5001,
"HealthProbe": {
"ProbeEndpoint": "http://api-service:5001/health",
"IntervalSeconds": 10
}
}
}
}Startup処理の実装:
var app = WebApplication.CreateBuilder(args);
// サービスディスカバリーの登録
app.Services.AddServiceDiscovery(app.Configuration);
var app = builder.Build();
// ヘルスチェックエンドポイント
app.Map("/health", context =>
{
context.Response.StatusCode = 200;
return context.Response.WriteAsync("Healthy");
});
// サービス登録の実行
await app.RegisterWithDiscovery();
app.MapControllers();
app.Run();