|
1 | 1 | using System; |
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.Linq; |
| 4 | +using System.Threading; |
| 5 | +using System.Threading.Tasks; |
| 6 | +using Microsoft.Extensions.Logging; |
4 | 7 | using NServiceBus; |
5 | 8 | using NServiceBus.Raw; |
6 | 9 | using NServiceBus.Transport; |
7 | 10 |
|
8 | | -class EndpointRegistry : IEndpointRegistry |
| 11 | +class EndpointRegistry(EndpointProxyFactory endpointProxyFactory, ILogger<StartableBridge> logger) : IEndpointRegistry |
9 | 12 | { |
10 | | - public void RegisterDispatcher( |
11 | | - BridgeEndpoint endpoint, |
12 | | - string targetTransportName, |
13 | | - IStartableRawEndpoint rawEndpoint) |
| 13 | + //NOTE: This method cannot have a return type of IAsyncEnumerable as all the endpoints need to be configured on the bridge before the bridge can start processing messages. |
| 14 | + public async Task<IEnumerable<ProxyRegistration>> Initialize(IReadOnlyCollection<BridgeTransport> transportConfigurations, CancellationToken cancellationToken = default) |
14 | 15 | { |
15 | | - registrations.Add(new ProxyRegistration |
16 | | - { |
17 | | - Endpoint = endpoint, |
18 | | - TranportName = targetTransportName, |
19 | | - RawEndpoint = rawEndpoint |
20 | | - }); |
21 | | - } |
22 | | - |
23 | | - public void ApplyMappings(IReadOnlyCollection<BridgeTransport> transportConfigurations) |
24 | | - { |
25 | | - foreach (var registration in registrations) |
26 | | - { |
27 | | - var endpoint = registration.Endpoint; |
28 | | - |
29 | | - // target transport is the transport where this endpoint is actually running |
30 | | - var targetTransport = transportConfigurations.Single(t => t.Endpoints.Any(e => e.Name == endpoint.Name)); |
| 16 | + // Assume that it is the number of endpoints that is more likely to scale up in size than the number of transports (typically only 2). |
| 17 | + // Therefore in cases where it might matter, it is more efficient to iterate over the transports multiple times. |
| 18 | + transportConfigurationMappings = transportConfigurations.ToDictionary(t => t.Name, t => t); |
31 | 19 |
|
32 | | - // just pick the first proxy that is running on the target transport since |
33 | | - // we just need to be able to send messages to that transport |
34 | | - var proxyEndpoint = registrations |
35 | | - .First(r => r.TranportName == targetTransport.Name) |
36 | | - .RawEndpoint; |
| 20 | + IList<ProxyRegistration> proxyRegistrations = []; |
37 | 21 |
|
38 | | - //This value represents in fact the endpoint's name. It is wrapped in the QueueAddress class only because |
39 | | - //the ToTransportAddress API expects it. |
40 | | - var endpointName = new QueueAddress(endpoint.Name); |
41 | | - |
42 | | - var transportAddress = endpoint.QueueAddress ?? proxyEndpoint.ToTransportAddress(endpointName); |
43 | | - |
44 | | - endpointAddressMappings[registration.Endpoint.Name] = transportAddress; |
45 | | - |
46 | | - targetEndpointAddressMappings[transportAddress] = registration.RawEndpoint.ToTransportAddress(endpointName); |
47 | | - |
48 | | - targetEndpointDispatchers[registration.Endpoint.Name] = new TargetEndpointDispatcher( |
49 | | - targetTransport.Name, |
50 | | - proxyEndpoint, |
51 | | - transportAddress); |
| 22 | + foreach (var targetTransport in transportConfigurations) |
| 23 | + { |
| 24 | + // Create the dispatcher for this transport |
| 25 | + var dispatchEndpoint = await EndpointProxyFactory.CreateDispatcher(targetTransport, cancellationToken).ConfigureAwait(false); |
| 26 | + |
| 27 | + proxyRegistrations.Add(new ProxyRegistration |
| 28 | + { |
| 29 | + Endpoint = null, |
| 30 | + TranportName = targetTransport.Name, |
| 31 | + RawEndpoint = dispatchEndpoint |
| 32 | + }); |
| 33 | + |
| 34 | + // create required proxy endpoints on all transports |
| 35 | + foreach (var endpointToSimulate in targetTransport.Endpoints) |
| 36 | + { |
| 37 | + // Endpoint will need to be proxied on the other transports |
| 38 | + foreach (var proxyTransport in transportConfigurationMappings.Where(kvp => kvp.Key != targetTransport.Name).Select(kvp => kvp.Value)) |
| 39 | + { |
| 40 | + var startableEndpointProxy = await endpointProxyFactory.CreateProxy(endpointToSimulate, proxyTransport, cancellationToken) |
| 41 | + .ConfigureAwait(false); |
| 42 | + |
| 43 | + logger.LogInformation("Proxy for endpoint {endpoint} created on {transport}", endpointToSimulate.Name, proxyTransport.Name); |
| 44 | + |
| 45 | + var queueAddress = new QueueAddress(endpointToSimulate.Name); |
| 46 | + var targetTransportAddress = dispatchEndpoint.ToTransportAddress(queueAddress); |
| 47 | + var sourceTransportAddress = startableEndpointProxy.ToTransportAddress(queueAddress); |
| 48 | + |
| 49 | + endpointAddressMappings[endpointToSimulate.Name] = endpointToSimulate.QueueAddress ?? targetTransportAddress; |
| 50 | + targetEndpointAddressMappings[targetTransportAddress] = sourceTransportAddress; |
| 51 | + if (targetTransportAddress != sourceTransportAddress) |
| 52 | + { |
| 53 | + // Also add the reverse mapping so that any messages that were in-flight before a bridge configuration change |
| 54 | + // can still have their address translated correctly. This also allows for the case where duplicate logical endpoints |
| 55 | + // are running as a competing consumer with the bridge in canary/parallel deployment scenarios. |
| 56 | + targetEndpointAddressMappings[sourceTransportAddress] = targetTransportAddress; |
| 57 | + } |
| 58 | + targetEndpointDispatchers[endpointToSimulate.Name] = new TargetEndpointDispatcher(targetTransport.Name, dispatchEndpoint, targetTransportAddress); |
| 59 | + |
| 60 | + proxyRegistrations.Add(new ProxyRegistration |
| 61 | + { |
| 62 | + Endpoint = endpointToSimulate, |
| 63 | + TranportName = proxyTransport.Name, |
| 64 | + RawEndpoint = startableEndpointProxy |
| 65 | + }); |
| 66 | + } |
| 67 | + } |
52 | 68 | } |
| 69 | + |
| 70 | + return proxyRegistrations; |
53 | 71 | } |
54 | 72 |
|
55 | 73 | public TargetEndpointDispatcher GetTargetEndpointDispatcher(string sourceEndpointName) |
@@ -96,12 +114,11 @@ static string GetClosestMatchForExceptionMessage(string sourceEndpointName, IEnu |
96 | 114 | return nearestMatch ?? "(No mappings registered)"; |
97 | 115 | } |
98 | 116 |
|
99 | | - public IEnumerable<ProxyRegistration> Registrations => registrations; |
| 117 | + Dictionary<string, BridgeTransport> transportConfigurationMappings = []; |
100 | 118 |
|
101 | 119 | readonly Dictionary<string, TargetEndpointDispatcher> targetEndpointDispatchers = []; |
102 | 120 | readonly Dictionary<string, string> targetEndpointAddressMappings = []; |
103 | 121 | readonly Dictionary<string, string> endpointAddressMappings = []; |
104 | | - readonly List<ProxyRegistration> registrations = []; |
105 | 122 |
|
106 | 123 | public class ProxyRegistration |
107 | 124 | { |
|
0 commit comments