From 7ecb64742ff222e5beaac3afa4701741536f03fc Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 13 Dec 2025 19:28:24 +0800 Subject: [PATCH] :recycle: Updated discovery resolver --- DysonNetwork.Develop/Program.cs | 2 +- DysonNetwork.Drive/Program.cs | 2 +- DysonNetwork.Gateway/Program.cs | 2 +- DysonNetwork.Insight/Program.cs | 2 +- .../Thought/ThoughtProvider.cs | 9 +-- DysonNetwork.Pass/Program.cs | 2 +- .../Properties/launchSettings.json | 38 +++++++------ DysonNetwork.Ring/Program.cs | 2 +- DysonNetwork.Shared/Extensions.cs | 27 ++++++--- .../RegistrarServiceEndpointFactory.cs | 38 +++++++++++++ .../RegistrarServiceEndpointProvider.cs | 41 ++++++++++++++ .../Registry/ServiceInjectionHelper.cs | 56 +++++++++---------- DysonNetwork.Sphere/Program.cs | 2 +- DysonNetwork.Zone/Program.cs | 2 +- DysonNetwork.sln.DotSettings.user | 4 ++ 15 files changed, 157 insertions(+), 72 deletions(-) create mode 100644 DysonNetwork.Shared/Registry/RegistrarServiceEndpointFactory.cs create mode 100644 DysonNetwork.Shared/Registry/RegistrarServiceEndpointProvider.cs diff --git a/DysonNetwork.Develop/Program.cs b/DysonNetwork.Develop/Program.cs index 4d003cb..544b846 100644 --- a/DysonNetwork.Develop/Program.cs +++ b/DysonNetwork.Develop/Program.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("develop"); builder.Services.Configure(opts => { opts.Name = "develop"; }); diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index 7099d0c..21d7123 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("drive"); builder.Services.Configure(opts => { opts.Name = "drive"; }); diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs index ce16a28..4f215d4 100644 --- a/DysonNetwork.Gateway/Program.cs +++ b/DysonNetwork.Gateway/Program.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.HttpOverrides; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("gateway"); builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue, enableGrpc: false); diff --git a/DysonNetwork.Insight/Program.cs b/DysonNetwork.Insight/Program.cs index e420aba..54d27a2 100644 --- a/DysonNetwork.Insight/Program.cs +++ b/DysonNetwork.Insight/Program.cs @@ -9,7 +9,7 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.Configure(opts => { opts.Name = "insight"; }); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("insight"); builder.ConfigureAppKestrel(builder.Configuration); diff --git a/DysonNetwork.Insight/Thought/ThoughtProvider.cs b/DysonNetwork.Insight/Thought/ThoughtProvider.cs index 9151d98..7f0b926 100644 --- a/DysonNetwork.Insight/Thought/ThoughtProvider.cs +++ b/DysonNetwork.Insight/Thought/ThoughtProvider.cs @@ -27,7 +27,6 @@ public class ThoughtProvider private readonly PostService.PostServiceClient _postClient; private readonly AccountService.AccountServiceClient _accountClient; private readonly IConfiguration _configuration; - private readonly ServiceRegistrar _registrar; private readonly Dictionary _kernels = new(); private readonly Dictionary _serviceProviders = new(); @@ -38,11 +37,9 @@ public class ThoughtProvider public ThoughtProvider( IConfiguration configuration, PostService.PostServiceClient postServiceClient, - AccountService.AccountServiceClient accountServiceClient, - ServiceRegistrar registrar + AccountService.AccountServiceClient accountServiceClient ) { - _registrar = registrar; _postClient = postServiceClient; _accountClient = accountServiceClient; _configuration = configuration; @@ -105,8 +102,8 @@ public class ThoughtProvider // Add gRPC clients for Thought Plugins builder.Services.AddServiceDiscoveryCore(); builder.Services.AddServiceDiscovery(); - builder.Services.AddAccountService(_registrar); - builder.Services.AddSphereService(_registrar); + builder.Services.AddAccountService(); + builder.Services.AddSphereService(); builder.Plugins.AddFromObject(new SnAccountKernelPlugin(_accountClient)); builder.Plugins.AddFromObject(new SnPostKernelPlugin(_postClient)); diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index bcc9852..7f4f9b7 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("pass"); builder.Services.Configure(opts => { opts.Name = "pass"; }); diff --git a/DysonNetwork.Pass/Properties/launchSettings.json b/DysonNetwork.Pass/Properties/launchSettings.json index 7d13ff2..b4fb695 100644 --- a/DysonNetwork.Pass/Properties/launchSettings.json +++ b/DysonNetwork.Pass/Properties/launchSettings.json @@ -1,21 +1,23 @@ { - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "HTTP_PORTS": "5010", + "GRPC_PORT": "6010" + } + } } - } } diff --git a/DysonNetwork.Ring/Program.cs b/DysonNetwork.Ring/Program.cs index 83895f1..07f022e 100644 --- a/DysonNetwork.Ring/Program.cs +++ b/DysonNetwork.Ring/Program.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("ring"); builder.Services.Configure(opts => { opts.Name = "ring"; }); diff --git a/DysonNetwork.Shared/Extensions.cs b/DysonNetwork.Shared/Extensions.cs index 19388f7..a8bfe9c 100644 --- a/DysonNetwork.Shared/Extensions.cs +++ b/DysonNetwork.Shared/Extensions.cs @@ -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(this TBuilder builder) where TBuilder : IHostApplicationBuilder + public static TBuilder AddServiceDefaults(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(SystemClock.Instance); builder.Services.AddSingleton(etcdClient); - builder.Services.AddSingleton(registrar); + builder.Services.AddSingleton(); builder.Services.AddHostedService(); + builder.Services.AddSingleton(); - 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; }); diff --git a/DysonNetwork.Shared/Registry/RegistrarServiceEndpointFactory.cs b/DysonNetwork.Shared/Registry/RegistrarServiceEndpointFactory.cs new file mode 100644 index 0000000..6876d2d --- /dev/null +++ b/DysonNetwork.Shared/Registry/RegistrarServiceEndpointFactory.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.CodeAnalysis; +using dotnet_etcd; +using Microsoft.Extensions.ServiceDiscovery; + +namespace DysonNetwork.Shared.Registry; + +/// +/// A factory for creating instances. +/// +public class RegistrarServiceEndpointFactory(EtcdClient etcdClient) : IServiceEndpointProviderFactory +{ + /// + /// Tries to create a provider for the given query. + /// + /// + /// 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". + /// + 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; + } +} diff --git a/DysonNetwork.Shared/Registry/RegistrarServiceEndpointProvider.cs b/DysonNetwork.Shared/Registry/RegistrarServiceEndpointProvider.cs new file mode 100644 index 0000000..5457b64 --- /dev/null +++ b/DysonNetwork.Shared/Registry/RegistrarServiceEndpointProvider.cs @@ -0,0 +1,41 @@ +using System.Net; +using dotnet_etcd; +using Microsoft.Extensions.ServiceDiscovery; + +namespace DysonNetwork.Shared.Registry; + +/// +/// A service endpoint provider that resolves endpoints from an etcd registry. +/// +public class RegistrarServiceEndpointProvider(string serviceName, string servicePart, EtcdClient etcdClient) + : IServiceEndpointProvider +{ + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + + /// + /// Populates the endpoints for the service. + /// + 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))); + } + } +} diff --git a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs index 7f3ed7c..8ccd423 100644 --- a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs @@ -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(o => o.Address = new Uri(instance)) + .AddGrpcClient(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(o => o.Address = new Uri(instance)) + .AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); services - .AddGrpcClient(o => o.Address = new Uri(instance)) + .AddGrpcClient(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(o => o.Address = new Uri(instance)) + .AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); @@ -49,42 +46,42 @@ public static class ServiceInjectionHelper services .AddGrpcClient(o => - o.Address = new Uri(instance) + o.Address = new Uri("https://pass") ) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); - services.AddGrpcClient(o => o.Address = new Uri(instance)) + services.AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); - services.AddGrpcClient(o => o.Address = new Uri(instance)) + services.AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); - services.AddGrpcClient(o => o.Address = new Uri(instance)) + services.AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); services - .AddGrpcClient(o => o.Address = new Uri(instance)) + .AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); services.AddSingleton(); - + services - .AddGrpcClient(o => o.Address = new Uri(instance)) + .AddGrpcClient(o => o.Address = new Uri("https://pass")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); - + services - .AddGrpcClient(o => o.Address = new Uri(instance)) + .AddGrpcClient(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(o => o.Address = new Uri(instance)) + services.AddGrpcClient(o => o.Address = new Uri("https://drive")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); services.AddGrpcClient(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(o => o.Address = new Uri(instance)) + .AddGrpcClient(o => o.Address = new Uri("https://sphere")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); services - .AddGrpcClient(o => o.Address = new Uri(instance)) + .AddGrpcClient(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(o => - o.Address = new Uri(instance)) + o.Address = new Uri("https://develop")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); return services; } -} \ No newline at end of file +} diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 83dbfac..27ad5da 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("sphere"); builder.Services.Configure(opts => { opts.Name = "sphere"; }); diff --git a/DysonNetwork.Zone/Program.cs b/DysonNetwork.Zone/Program.cs index 51061e6..0a3e7d5 100644 --- a/DysonNetwork.Zone/Program.cs +++ b/DysonNetwork.Zone/Program.cs @@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +builder.AddServiceDefaults("zone"); builder.Services.Configure(opts => { opts.Name = "zone"; }); diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 6bad741..ce5e2ab 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -1,6 +1,7 @@  ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -23,6 +24,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -182,6 +185,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded