♻️ Updated discovery resolver

This commit is contained in:
2025-12-13 19:28:24 +08:00
parent 3a7140f0a6
commit 7ecb64742f
15 changed files with 157 additions and 72 deletions

View File

@@ -3,6 +3,7 @@ using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Registry;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
@@ -27,7 +28,8 @@ public static class Extensions
private const string HealthEndpointPath = "/health";
private const string AlivenessEndpointPath = "/alive";
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder, string serviceName)
where TBuilder : IHostApplicationBuilder
{
// Allow unencrypted grpc
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
@@ -37,6 +39,7 @@ public static class Extensions
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.AddServiceDiscoveryCore();
builder.Services.ConfigureHttpClientDefaults(http =>
{
@@ -54,18 +57,24 @@ public static class Extensions
// });
var etcdClient = new EtcdClient(builder.Configuration.GetConnectionString("Registrar"));
var registrar = new ServiceRegistrar(etcdClient);
builder.Services.AddSingleton<IClock>(SystemClock.Instance);
builder.Services.AddSingleton(etcdClient);
builder.Services.AddSingleton(registrar);
builder.Services.AddSingleton<ServiceRegistrar>();
builder.Services.AddHostedService<ServiceRegistrarHostedService>();
builder.Services.AddSingleton<IServiceEndpointProviderFactory, RegistrarServiceEndpointFactory>();
builder.Services.AddRingService(registrar);
builder.Services.AddAuthService(registrar);
builder.Services.AddAccountService(registrar);
builder.Services.AddSphereService(registrar);
builder.Services.AddDriveService(registrar);
builder.Services.AddDevelopService(registrar);
if (serviceName != "ring")
builder.Services.AddRingService();
if (serviceName != "pass")
builder.Services.AddAuthService();
if (serviceName != "pass")
builder.Services.AddAccountService();
if (serviceName != "sphere")
builder.Services.AddSphereService();
if (serviceName != "drive")
builder.Services.AddDriveService();
if (serviceName != "develop")
builder.Services.AddDevelopService();
builder.AddNatsClient("Queue");
builder.AddRedisClient("Cache", configureOptions: opts => { opts.AbortOnConnectFail = false; });

View File

@@ -0,0 +1,38 @@
using System.Diagnostics.CodeAnalysis;
using dotnet_etcd;
using Microsoft.Extensions.ServiceDiscovery;
namespace DysonNetwork.Shared.Registry;
/// <summary>
/// A factory for creating <see cref="RegistrarServiceEndpointProvider"/> instances.
/// </summary>
public class RegistrarServiceEndpointFactory(EtcdClient etcdClient) : IServiceEndpointProviderFactory
{
/// <summary>
/// Tries to create a provider for the given query.
/// </summary>
/// <remarks>
/// This factory creates a provider for any service name. It supports a convention
/// where the service name can include the service part, e.g., "my-service.http" or "my-service.grpc".
/// If the service part is not specified, it defaults to "http".
/// </remarks>
public bool TryCreateProvider(ServiceEndpointQuery query, [NotNullWhen(true)] out IServiceEndpointProvider? provider)
{
var serviceName = query.ServiceName;
var servicePart = "grpc"; // Default to "grpc"
var lastDot = serviceName.LastIndexOf('.');
if (lastDot > 0 && lastDot < serviceName.Length - 1)
{
var part = serviceName[(lastDot + 1)..];
// You might want to have a list of known parts.
// For now, we assume any suffix after a dot is a service part.
servicePart = part;
serviceName = serviceName[..lastDot];
}
provider = new RegistrarServiceEndpointProvider(serviceName, servicePart, etcdClient);
return true;
}
}

View File

@@ -0,0 +1,41 @@
using System.Net;
using dotnet_etcd;
using Microsoft.Extensions.ServiceDiscovery;
namespace DysonNetwork.Shared.Registry;
/// <summary>
/// A service endpoint provider that resolves endpoints from an etcd registry.
/// </summary>
public class RegistrarServiceEndpointProvider(string serviceName, string servicePart, EtcdClient etcdClient)
: IServiceEndpointProvider
{
public ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
return ValueTask.CompletedTask;
}
/// <summary>
/// Populates the endpoints for the service.
/// </summary>
public async ValueTask PopulateAsync(IServiceEndpointBuilder endpoints, CancellationToken cancellationToken)
{
var prefix = $"/services/{serviceName}/{servicePart}/";
// Fetch service instances from etcd.
var response = await etcdClient.GetRangeAsync(prefix, cancellationToken: cancellationToken);
var instances = response.Kvs.Select(kv => kv.Value.ToStringUtf8());
foreach (var instance in instances)
{
// Instances are in "host:port" format.
var parts = instance.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length != 2 || !int.TryParse(parts[1], out var port)) continue;
var host = parts[0];
// Create a DnsEndPoint. The framework will use the original request's scheme (e.g., http, https).
endpoints.Endpoints.Add(ServiceEndpoint.Create(new DnsEndPoint(host, port)));
}
}
}

View File

@@ -7,11 +7,10 @@ namespace DysonNetwork.Shared.Registry;
public static class ServiceInjectionHelper
{
public static IServiceCollection AddRingService(this IServiceCollection services, ServiceRegistrar registrar)
public static IServiceCollection AddRingService(this IServiceCollection services)
{
var instance = registrar.GetServiceInstanceAsync("ring", "grpc").GetAwaiter().GetResult();
services
.AddGrpcClient<RingService.RingServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<RingService.RingServiceClient>(o => o.Address = new Uri("https://ring"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
@@ -19,17 +18,16 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddAuthService(this IServiceCollection services, ServiceRegistrar registrar)
public static IServiceCollection AddAuthService(this IServiceCollection services)
{
var instance = registrar.GetServiceInstanceAsync("pass", "grpc").GetAwaiter().GetResult();
services
.AddGrpcClient<AuthService.AuthServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<AuthService.AuthServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services
.AddGrpcClient<PermissionService.PermissionServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<PermissionService.PermissionServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
@@ -37,11 +35,10 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddAccountService(this IServiceCollection services, ServiceRegistrar registrar)
public static IServiceCollection AddAccountService(this IServiceCollection services)
{
var instance = registrar.GetServiceInstanceAsync("pass", "grpc").GetAwaiter().GetResult();
services
.AddGrpcClient<AccountService.AccountServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<AccountService.AccountServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
@@ -49,42 +46,42 @@ public static class ServiceInjectionHelper
services
.AddGrpcClient<BotAccountReceiverService.BotAccountReceiverServiceClient>(o =>
o.Address = new Uri(instance)
o.Address = new Uri("https://pass")
)
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services.AddGrpcClient<ActionLogService.ActionLogServiceClient>(o => o.Address = new Uri(instance))
services.AddGrpcClient<ActionLogService.ActionLogServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services.AddGrpcClient<PaymentService.PaymentServiceClient>(o => o.Address = new Uri(instance))
services.AddGrpcClient<PaymentService.PaymentServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services.AddGrpcClient<WalletService.WalletServiceClient>(o => o.Address = new Uri(instance))
services.AddGrpcClient<WalletService.WalletServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services
.AddGrpcClient<RealmService.RealmServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<RealmService.RealmServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services.AddSingleton<RemoteRealmService>();
services
.AddGrpcClient<SocialCreditService.SocialCreditServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<SocialCreditService.SocialCreditServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services
.AddGrpcClient<ExperienceService.ExperienceServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<ExperienceService.ExperienceServiceClient>(o => o.Address = new Uri("https://pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
@@ -92,16 +89,15 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddDriveService(this IServiceCollection services, ServiceRegistrar registrar)
public static IServiceCollection AddDriveService(this IServiceCollection services)
{
var instance = registrar.GetServiceInstanceAsync("drive", "grpc").GetAwaiter().GetResult();
services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri(instance))
services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri("https://drive"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services.AddGrpcClient<FileReferenceService.FileReferenceServiceClient>(o =>
o.Address = new Uri("https://_grpc.drive"))
o.Address = new Uri("https://drive"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
@@ -109,17 +105,16 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddSphereService(this IServiceCollection services, ServiceRegistrar registrar)
public static IServiceCollection AddSphereService(this IServiceCollection services)
{
var instance = registrar.GetServiceInstanceAsync("drive", "grpc").GetAwaiter().GetResult();
services
.AddGrpcClient<PostService.PostServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<PostService.PostServiceClient>(o => o.Address = new Uri("https://sphere"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services
.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri(instance))
.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri("https://sphere"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
@@ -127,15 +122,14 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddDevelopService(this IServiceCollection services, ServiceRegistrar registrar)
public static IServiceCollection AddDevelopService(this IServiceCollection services)
{
var instance = registrar.GetServiceInstanceAsync("develop", "grpc").GetAwaiter().GetResult();
services.AddGrpcClient<CustomAppService.CustomAppServiceClient>(o =>
o.Address = new Uri(instance))
o.Address = new Uri("https://develop"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
return services;
}
}
}