antonyvorontsov / RabbitMQ.Client.Core.DependencyInjection

.Net Core library-wrapper of RabbitMQ.Client for Dependency Injection.
MIT License
111 stars 36 forks source link

Starting client in QueueService constructor #61

Closed andrejt closed 3 years ago

andrejt commented 4 years ago

Isn't that a bit early and uncontrollable? If server is unavailable, the whole app might be blocking, even before logging is set up, so app could just crash without any information. Which may be a wanted effect, but I'd prefer keeping this more under control by moving StartClient() to a later stage, e.g. before StartConsuming(), or maybe even a separate method, exposed through IQueueService?

fbridger commented 3 years ago

We are experiencing this specific issue and getting a InitialConnectionException.

If the connection to RabbitMQ cannot be establish the entire Application fails because the Depedency cannot be injected.

RabbitMQ.Client.Core.DependencyInjection.Exceptions.InitialConnectionException
  HResult=0x80131500
  Message=Could not establish an initial connection in 5 retries
  Source=RabbitMQ.Client.Core.DependencyInjection
  StackTrace:
   at RabbitMQ.Client.Core.DependencyInjection.Services.RabbitMqConnectionFactory.TryToCreateConnection(Func`1 connectionFunction, Int32 numberOfRetries, Int32 timeoutMilliseconds) in C:\Repos\RabbitMQ.Client.Core.DependencyInjection\src\RabbitMQ.Client.Core.DependencyInjection\Services\RabbitMqConnectionFactory.cs:line 139
   at RabbitMQ.Client.Core.DependencyInjection.Services.RabbitMqConnectionFactory.CreateConnectionWithTcpEndpoints(RabbitMqClientOptions options, ConnectionFactory factory) in C:\Repos\RabbitMQ.Client.Core.DependencyInjection\src\RabbitMQ.Client.Core.DependencyInjection\Services\RabbitMqConnectionFactory.cs:line 86
   at RabbitMQ.Client.Core.DependencyInjection.Services.RabbitMqConnectionFactory.CreateRabbitMqConnection(RabbitMqClientOptions options) in C:\Repos\RabbitMQ.Client.Core.DependencyInjection\src\RabbitMQ.Client.Core.DependencyInjection\Services\RabbitMqConnectionFactory.cs:line 45
   at RabbitMQ.Client.Core.DependencyInjection.Services.QueueService.ConfigureConnectionInfrastructure(RabbitMqConnectionOptionsContainer optionsContainer) in C:\Repos\RabbitMQ.Client.Core.DependencyInjection\src\RabbitMQ.Client.Core.DependencyInjection\Services\QueueService.cs:line 338
   at RabbitMQ.Client.Core.DependencyInjection.Services.QueueService..ctor(Guid guid, IRabbitMqConnectionFactory rabbitMqConnectionFactory, IEnumerable`1 connectionOptionsContainers, IMessageHandlingPipelineExecutingService messageHandlingPipelineExecutingService, IEnumerable`1 exchanges, ILogger`1 logger) in C:\Repos\RabbitMQ.Client.Core.DependencyInjection\src\RabbitMQ.Client.Core.DependencyInjection\Services\QueueService.cs:line 61
   at RabbitMQ.Client.Core.DependencyInjection.RabbitMqClientDependencyInjectionExtensions.<>c__DisplayClass17_0.<ResolveSingletonConsumingService>b__0(IServiceProvider provider) in C:\Repos\RabbitMQ.Client.Core.DependencyInjection\src\RabbitMQ.Client.Core.DependencyInjection\RabbitMqClientDependencyInjectionExtensions.cs:line 291
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>d__9.MoveNext()

  This exception was originally thrown at this call stack:
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(System.Exception)
    System.Net.Sockets.Socket.EndConnect(System.IAsyncResult)
    System.Net.Sockets.Socket.ConnectAsync.AnonymousMethod__275_0(System.IAsyncResult)
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
    System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
    RabbitMQ.Client.Impl.TcpClientAdapter.ConnectAsync(string, int)
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    ...
    [Call Stack Truncated]

Inner Exception 1:
BrokerUnreachableException: None of the specified endpoints were reachable

Inner Exception 2:
AggregateException: One or more errors occurred. (Connection failed)

Inner Exception 3:
ConnectFailureException: Connection failed

Inner Exception 4:
ExtendedSocketException: No connection could be made because the target machine actively refused it. 127.0.0.1:5672
fbridger commented 3 years ago

I was able to solve this by injecting a Lazy loaded dependency

StartUp

var rabbitMqSection = configuration.GetSection("rabbitMq");
var exchangeOptions = new RabbitMqExchangeOptions
{
    Type = "fanout",
    Durable = true
};

services.AddRabbitMqProducingClientSingleton(rabbitMqSection)
    .AddProductionExchange("exchange", exchangeOptions);

// Allow to inject Lazy loaded instance for IProducingService
services.AddScoped(x => new Lazy<IProducingService>(() => x.GetRequiredService<IProducingService>()));

Implementation

public class EventPublisher
{
    private readonly Lazy<IProducingService> _producingService;

    public EventPublisher(Lazy<IProducingService> producingService)
    {
        _producingService = producingService;
    }

    public async Task PublishEvent()
    {
        await _producingService.Value.SendAsync(
            @object: new { Message = "something" },
            exchangeName: "exchange",
            routingKey: "my.key");
    }
}
antonyvorontsov commented 3 years ago

@andrejt I am so late to answer that issue, but better later than never, right?

I'll start with a small story how this library even appeared. Some time ago I'd had to deal with rabbitMQ in one of my projects at work and I decided to create a small library that would help me with queue declarations and other stuff. I came up with simple idea to make a single QueueService that would be responsible for all the work: both consumption and production of messages. That was a huge mistake. Simplicity took over common programming principles as single responsibility. Same thing for declarations in constructor - simplicity is the key.

Then I posted this library at GitHub and NuGet without a hope that it will be useful to someone, but after people began to use it I started trying to avoid making breaking changes. And yeah, it is a mistake as well, coz I have to do it anyway.

There is an issue where I share my thoughts about reworking current paradigm of QueueService, and here is a pull request where you can track upcoming changes. I hope a new library version will come up in a week or so.

I think that it answers initial question, so I intend to close this issue. Feel free to re-open it if something is not clear and if the pull-request and connected issue do not satisfy your desires.

antonyvorontsov commented 3 years ago

@fbridger I am happy you solved that problem by yourself!

Please take a look at this issue and the pull request too.

After an update that can change the way you use the library.

And if you have a suggestion (or a couple of them) feel free to leave them in the "Upcoming changes issue"