Skip to content

Commit 5d88bb2

Browse files
authored
Add IDiscoveryClient.InstancesFetched event (#1672)
* Add IDiscoveryClient.InstancesFetched event * Consul: don't use UseNetworkInterfaces to determine ports (reverts change from #1666) * Cleanup queries, fix case insensivity
1 parent d95d9bf commit 5d88bb2

16 files changed

Lines changed: 291 additions & 24 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace Steeltoe.Common.Discovery;
6+
7+
/// <summary>
8+
/// Provides data for the <see cref="IDiscoveryClient.InstancesFetched" /> event.
9+
/// </summary>
10+
public sealed class DiscoveryInstancesFetchedEventArgs : EventArgs
11+
{
12+
/// <summary>
13+
/// Gets the updated list of service instances, grouped by service ID.
14+
/// </summary>
15+
public IReadOnlyDictionary<string, IReadOnlyList<IServiceInstance>> InstancesByServiceId { get; }
16+
17+
public DiscoveryInstancesFetchedEventArgs(IReadOnlyDictionary<string, IReadOnlyList<IServiceInstance>> instancesByServiceId)
18+
{
19+
ArgumentNullException.ThrowIfNull(instancesByServiceId);
20+
21+
InstancesByServiceId = instancesByServiceId;
22+
}
23+
}

src/Common/src/Common/Discovery/IDiscoveryClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public interface IDiscoveryClient
1414
/// </summary>
1515
string Description { get; }
1616

17+
/// <summary>
18+
/// Occurs when service instances have been fetched from the discovery server.
19+
/// </summary>
20+
public event EventHandler<DiscoveryInstancesFetchedEventArgs> InstancesFetched;
21+
1722
/// <summary>
1823
/// Gets information used to register the local service instance (this app) to the discovery server.
1924
/// </summary>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
#nullable enable
2+
Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs
3+
Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs.DiscoveryInstancesFetchedEventArgs(System.Collections.Generic.IReadOnlyDictionary<string!, System.Collections.Generic.IReadOnlyList<Steeltoe.Common.Discovery.IServiceInstance!>!>! instancesByServiceId) -> void
4+
Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs.InstancesByServiceId.get -> System.Collections.Generic.IReadOnlyDictionary<string!, System.Collections.Generic.IReadOnlyList<Steeltoe.Common.Discovery.IServiceInstance!>!>!
5+
Steeltoe.Common.Discovery.IDiscoveryClient.InstancesFetched -> System.EventHandler<Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs!>!

src/Configuration/test/ConfigServer.Discovery.Test/ConfigServerDiscoveryServiceTest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ private sealed class TestDiscoveryClient : IDiscoveryClient
114114
{
115115
public string Description => throw new NotImplementedException();
116116

117+
#pragma warning disable CS0067 // The event is never used
118+
public event EventHandler<DiscoveryInstancesFetchedEventArgs>? InstancesFetched;
119+
#pragma warning restore CS0067 // The event is never used
120+
117121
public Task<ISet<string>> GetServiceIdsAsync(CancellationToken cancellationToken)
118122
{
119123
throw new NotImplementedException();

src/Discovery/src/Configuration/ConfigurationDiscoveryClient.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.ObjectModel;
56
using Microsoft.Extensions.Options;
67
using Steeltoe.Common.Discovery;
78

@@ -10,17 +11,69 @@ namespace Steeltoe.Discovery.Configuration;
1011
/// <summary>
1112
/// A discovery client that reads service instances from app configuration.
1213
/// </summary>
13-
public sealed class ConfigurationDiscoveryClient : IDiscoveryClient
14+
public sealed class ConfigurationDiscoveryClient : IDiscoveryClient, IDisposable
1415
{
1516
private readonly IOptionsMonitor<ConfigurationDiscoveryOptions> _optionsMonitor;
17+
private readonly IDisposable? _changeTokenRegistration;
1618

1719
public string Description => "A discovery client that returns service instances from app configuration.";
1820

21+
/// <summary>
22+
/// Occurs when the configuration of service instances has been reloaded.
23+
/// </summary>
24+
public event EventHandler<DiscoveryInstancesFetchedEventArgs>? InstancesFetched;
25+
1926
public ConfigurationDiscoveryClient(IOptionsMonitor<ConfigurationDiscoveryOptions> optionsMonitor)
2027
{
2128
ArgumentNullException.ThrowIfNull(optionsMonitor);
2229

2330
_optionsMonitor = optionsMonitor;
31+
_changeTokenRegistration = optionsMonitor.OnChange(OnOptionsChanged);
32+
}
33+
34+
private void OnOptionsChanged(ConfigurationDiscoveryOptions options)
35+
{
36+
if (InstancesFetched != null)
37+
{
38+
ReadOnlyDictionary<string, IReadOnlyList<IServiceInstance>> instancesByServiceId = ToServiceInstanceMap(options.Services);
39+
var eventArgs = new DiscoveryInstancesFetchedEventArgs(instancesByServiceId);
40+
RaiseFetchEvent(eventArgs);
41+
}
42+
}
43+
44+
private static ReadOnlyDictionary<string, IReadOnlyList<IServiceInstance>> ToServiceInstanceMap(IList<ConfigurationServiceInstance> services)
45+
{
46+
// @formatter:wrap_chained_method_calls chop_always
47+
// @formatter:wrap_before_first_method_call true
48+
49+
return services
50+
.Where(service => service.ServiceId != null)
51+
.GroupBy(service => service.ServiceId!, StringComparer.OrdinalIgnoreCase)
52+
.ToDictionary(grouping => grouping.Key, grouping => (IReadOnlyList<IServiceInstance>)grouping
53+
.Cast<IServiceInstance>()
54+
.ToList()
55+
.AsReadOnly(), StringComparer.OrdinalIgnoreCase)
56+
.AsReadOnly();
57+
58+
// @formatter:wrap_before_first_method_call restore
59+
// @formatter:wrap_chained_method_calls restore
60+
}
61+
62+
private void RaiseFetchEvent(DiscoveryInstancesFetchedEventArgs eventArgs)
63+
{
64+
// Execute on separate thread, so we won't block the configuration system in case the handler logic is expensive.
65+
ThreadPool.QueueUserWorkItem(_ =>
66+
{
67+
try
68+
{
69+
InstancesFetched?.Invoke(this, eventArgs);
70+
}
71+
catch (Exception)
72+
{
73+
// Intentionally left empty. Adding a logger to the constructor is a breaking change.
74+
// Adding an extra constructor confuses the service container.
75+
}
76+
});
2477
}
2578

2679
/// <inheritdoc />
@@ -54,4 +107,10 @@ public Task ShutdownAsync(CancellationToken cancellationToken)
54107
{
55108
return Task.CompletedTask;
56109
}
110+
111+
/// <inheritdoc />
112+
public void Dispose()
113+
{
114+
_changeTokenRegistration?.Dispose();
115+
}
57116
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
Steeltoe.Discovery.Configuration.ConfigurationDiscoveryClient.Dispose() -> void
3+
Steeltoe.Discovery.Configuration.ConfigurationDiscoveryClient.InstancesFetched -> System.EventHandler<Steeltoe.Common.Discovery.DiscoveryInstancesFetchedEventArgs!>?

src/Discovery/src/Consul/Configuration/ConsulDiscoveryOptions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ public sealed class ConsulDiscoveryOptions
176176
/// <summary>
177177
/// Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.
178178
/// <para />
179-
/// This property is ignored when <see cref="Port" /> or <see cref="Scheme" /> is explicitly configured, or when <see cref="UseNetworkInterfaces" /> is
180-
/// <c>true</c>.
179+
/// This property is ignored when <see cref="Port" /> or <see cref="Scheme" /> is explicitly configured.
181180
/// </summary>
182181
public bool UseAspNetCoreUrls { get; set; } = true;
183182
}

src/Discovery/src/Consul/ConfigurationSchema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
},
189189
"UseAspNetCoreUrls": {
190190
"type": "boolean",
191-
"description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured, or when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.UseNetworkInterfaces' is true."
191+
"description": "Gets or sets a value indicating whether to register with the port number ASP.NET Core is listening on. Default value: true.\n\nThis property is ignored when 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Port' or 'Steeltoe.Discovery.Consul.Configuration.ConsulDiscoveryOptions.Scheme' is explicitly configured."
192192
},
193193
"UseNetworkInterfaces": {
194194
"type": "boolean",

src/Discovery/src/Consul/ConsulDiscoveryClient.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ public sealed class ConsulDiscoveryClient : IDiscoveryClient
3030
/// <inheritdoc />
3131
public string Description => "A discovery client for HashiCorp Consul.";
3232

33+
/// <summary>
34+
/// This event is never raised. The Consul client doesn't implement caching.
35+
/// </summary>
36+
#pragma warning disable CS0067 // The event is never used
37+
public event EventHandler<DiscoveryInstancesFetchedEventArgs>? InstancesFetched;
38+
#pragma warning restore CS0067 // The event is never used
39+
3340
/// <summary>
3441
/// Initializes a new instance of the <see cref="ConsulDiscoveryClient" /> class.
3542
/// </summary>

src/Discovery/src/Consul/PostConfigureConsulDiscoveryOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void PostConfigure(string? name, ConsulDiscoveryOptions options)
6262
options.HostName = options.IPAddress;
6363
}
6464

65-
if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null, UseNetworkInterfaces: false })
65+
if (options is { UseAspNetCoreUrls: true, Port: 0, Scheme: null })
6666
{
6767
ICollection<string> addresses = _configuration.GetListenAddresses();
6868
SetSchemeWithPortFromListenAddresses(options, addresses);

0 commit comments

Comments
 (0)