diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..9db0418 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,9 @@ +version 2 + +build + os ubuntu-22.04 + tools + python 3.12 + +sphinx + configuration docs/conf.py \ No newline at end of file diff --git a/Tapeti/Config/ITapetiConfig.cs b/Tapeti/Config/ITapetiConfig.cs index 4bdad56..27c393e 100644 --- a/Tapeti/Config/ITapetiConfig.cs +++ b/Tapeti/Config/ITapetiConfig.cs @@ -17,7 +17,10 @@ namespace Tapeti.Config /// /// Various Tapeti features which can be turned on or off. /// - ITapetiConfigFeatues Features { get; } + /// + /// Calling this method will freeze the feature set if is used. + /// + ITapetiConfigFeatures GetFeatures(); /// /// Provides access to the different kinds of registered middleware. @@ -34,7 +37,7 @@ namespace Tapeti.Config /// /// Various Tapeti features which can be turned on or off. /// - public interface ITapetiConfigFeatues + public interface ITapetiConfigFeatures { /// /// Determines whether 'publisher confirms' are used. This RabbitMQ features allows Tapeti to diff --git a/Tapeti/Config/ITapetiConfigBuilder.cs b/Tapeti/Config/ITapetiConfigBuilder.cs index 5d517c9..6351293 100644 --- a/Tapeti/Config/ITapetiConfigBuilder.cs +++ b/Tapeti/Config/ITapetiConfigBuilder.cs @@ -53,6 +53,40 @@ namespace Tapeti.Config void RegisterBinding(IBinding binding); + /// + ITapetiConfigBuilder DisablePublisherConfirms(); + + /// + ITapetiConfigBuilder SetPublisherConfirms(bool enabled); + + /// + ITapetiConfigBuilder EnableDeclareDurableQueues(); + + /// + ITapetiConfigBuilder SetDeclareDurableQueues(bool enabled); + + /// + ITapetiConfigBuilder DisableVerifyDurableQueues(); + + /// + ITapetiConfigBuilder SetVerifyDurableQueues(bool enabled); + + + /// + /// Allows the core features to be determine on-demand when first required by the connection instead + /// of before is constructed. + /// + /// Called when the feature set is required. From that moment on the feature set is frozen. + ITapetiConfigBuilder DelayFeatures(Action onBuild); + } + + + + /// + /// Configures Tapeti core features. Every method returns the builder instance for method chaining. + /// + public interface ITapetiConfigFeaturesBuilder + { /// /// Disables 'publisher confirms'. This RabbitMQ features allows Tapeti to be notified if a message /// has no route, and guarantees delivery for request-response style messages and those marked with @@ -62,7 +96,7 @@ namespace Tapeti.Config /// and disables Tapeti.Flow from verifying if a request/response can be routed. This may /// result in never-ending flows. Only disable if you can accept those consequences. /// - ITapetiConfigBuilder DisablePublisherConfirms(); + ITapetiConfigFeaturesBuilder DisablePublisherConfirms(); /// @@ -74,7 +108,7 @@ namespace Tapeti.Config /// and disables Tapeti.Flow from verifying if a request/response can be routed. This may /// result in never-ending flows. Only disable if you can accept those consequences. /// - ITapetiConfigBuilder SetPublisherConfirms(bool enabled); + ITapetiConfigFeaturesBuilder SetPublisherConfirms(bool enabled); /// @@ -84,7 +118,7 @@ namespace Tapeti.Config /// Note that access to the RabbitMQ Management plugin's REST API is required for this /// feature to work, since AMQP does not provide a way to query existing bindings. /// - ITapetiConfigBuilder EnableDeclareDurableQueues(); + ITapetiConfigFeaturesBuilder EnableDeclareDurableQueues(); /// /// Configures the automatic creation of durable queues and updating of their bindings. @@ -93,7 +127,7 @@ namespace Tapeti.Config /// Note that access to the RabbitMQ Management plugin's REST API is required for this /// feature to work, since AMQP does not provide a way to query existing bindings. /// - ITapetiConfigBuilder SetDeclareDurableQueues(bool enabled); + ITapetiConfigFeaturesBuilder SetDeclareDurableQueues(bool enabled); /// @@ -102,7 +136,7 @@ namespace Tapeti.Config /// exchange, which do not correspond to Tapeti's configuration, as these will cause an error /// while verifying. /// - ITapetiConfigBuilder DisableVerifyDurableQueues(); + ITapetiConfigFeaturesBuilder DisableVerifyDurableQueues(); /// @@ -111,7 +145,7 @@ namespace Tapeti.Config /// exchange, which do not correspond to Tapeti's configuration, as these will cause an error /// while verifying. /// - ITapetiConfigBuilder SetVerifyDurableQueues(bool enabled); + ITapetiConfigFeaturesBuilder SetVerifyDurableQueues(bool enabled); } diff --git a/Tapeti/Connection/TapetiClient.cs b/Tapeti/Connection/TapetiClient.cs index 3818238..23d5cbf 100644 --- a/Tapeti/Connection/TapetiClient.cs +++ b/Tapeti/Connection/TapetiClient.cs @@ -135,7 +135,7 @@ namespace Tapeti.Connection DeclareExchange(channel, exchange); // The delivery tag is lost after a reconnect, register under the new tag - if (config.Features.PublisherConfirms) + if (config.GetFeatures().PublisherConfirms) { lastDeliveryTag++; @@ -848,7 +848,7 @@ namespace Tapeti.Connection } - if (config.Features.PublisherConfirms) + if (config.GetFeatures().PublisherConfirms) { lastDeliveryTag = 0; diff --git a/Tapeti/Connection/TapetiSubscriber.cs b/Tapeti/Connection/TapetiSubscriber.cs index df0db5a..b7023eb 100644 --- a/Tapeti/Connection/TapetiSubscriber.cs +++ b/Tapeti/Connection/TapetiSubscriber.cs @@ -125,9 +125,9 @@ namespace Tapeti.Connection CustomBindingTarget bindingTarget; - if (config.Features.DeclareDurableQueues) + if (config.GetFeatures().DeclareDurableQueues) bindingTarget = new DeclareDurableQueuesBindingTarget(clientFactory, routingKeyStrategy, exchangeStrategy, cancellationToken); - else if (config.Features.VerifyDurableQueues) + else if (config.GetFeatures().VerifyDurableQueues) bindingTarget = new PassiveDurableQueuesBindingTarget(clientFactory, routingKeyStrategy, exchangeStrategy, cancellationToken); else bindingTarget = new NoVerifyBindingTarget(clientFactory, routingKeyStrategy, exchangeStrategy, cancellationToken); diff --git a/Tapeti/TapetiConfig.cs b/Tapeti/TapetiConfig.cs index ee48d3a..56188a7 100644 --- a/Tapeti/TapetiConfig.cs +++ b/Tapeti/TapetiConfig.cs @@ -135,7 +135,7 @@ namespace Tapeti /// public ITapetiConfigBuilder DisablePublisherConfirms() { - GetConfig().SetPublisherConfirms(false); + GetConfig().GetFeaturesBuilder().DisablePublisherConfirms(); return this; } @@ -143,7 +143,7 @@ namespace Tapeti /// public ITapetiConfigBuilder SetPublisherConfirms(bool enabled) { - GetConfig().SetPublisherConfirms(enabled); + GetConfig().GetFeaturesBuilder().SetPublisherConfirms(enabled); return this; } @@ -151,7 +151,7 @@ namespace Tapeti /// public ITapetiConfigBuilder EnableDeclareDurableQueues() { - GetConfig().SetDeclareDurableQueues(true); + GetConfig().GetFeaturesBuilder().EnableDeclareDurableQueues(); return this; } @@ -159,7 +159,7 @@ namespace Tapeti /// public ITapetiConfigBuilder SetDeclareDurableQueues(bool enabled) { - GetConfig().SetDeclareDurableQueues(enabled); + GetConfig().GetFeaturesBuilder().SetDeclareDurableQueues(enabled); return this; } @@ -167,7 +167,7 @@ namespace Tapeti /// public ITapetiConfigBuilder DisableVerifyDurableQueues() { - GetConfig().SetVerifyDurableQueues(false); + GetConfig().GetFeaturesBuilder().DisablePublisherConfirms(); return this; } @@ -175,7 +175,15 @@ namespace Tapeti /// public ITapetiConfigBuilder SetVerifyDurableQueues(bool enabled) { - GetConfig().SetVerifyDurableQueues(enabled); + GetConfig().GetFeaturesBuilder().SetVerifyDurableQueues(enabled); + return this; + } + + + /// + public ITapetiConfigBuilder DelayFeatures(Action onBuild) + { + GetConfig().GetFeaturesBuilder().DelayFeatures(onBuild); return this; } @@ -221,12 +229,13 @@ namespace Tapeti /// internal class Config : ITapetiConfig { - private readonly ConfigFeatures features = new(); + private ConfigFeaturesBuilder? featuresBuilder = new(); + private ITapetiConfigFeatures? features; + private readonly ConfigMiddleware middleware = new(); private readonly ConfigBindings bindings = new(); public IDependencyResolver DependencyResolver { get; } - public ITapetiConfigFeatues Features => features; public ITapetiConfigMiddleware Middleware => middleware; public ITapetiConfigBindings Bindings => bindings; @@ -237,6 +246,17 @@ namespace Tapeti } + public ITapetiConfigFeatures GetFeatures() + { + if (features != null) + return features; + + features = featuresBuilder!.Build(); + featuresBuilder = null; + return features; + } + + public void Lock() { bindings.Lock(); @@ -260,24 +280,17 @@ namespace Tapeti } - public void SetPublisherConfirms(bool enabled) + internal ConfigFeaturesBuilder GetFeaturesBuilder() { - features.PublisherConfirms = enabled; - } + if (featuresBuilder == null) + throw new InvalidOperationException("Tapeti features are already frozen"); - public void SetDeclareDurableQueues(bool enabled) - { - features.DeclareDurableQueues = enabled; - } - - public void SetVerifyDurableQueues(bool enabled) - { - features.VerifyDurableQueues = enabled; + return featuresBuilder; } } - internal class ConfigFeatures : ITapetiConfigFeatues + internal class ConfigFeatures : ITapetiConfigFeatures { public bool PublisherConfirms { get; internal set; } = true; public bool DeclareDurableQueues { get; internal set; } @@ -285,6 +298,66 @@ namespace Tapeti } + internal class ConfigFeaturesBuilder : ITapetiConfigFeaturesBuilder + { + private bool publisherConfirms = true; + private bool declareDurableQueues; + private bool verifyDurableQueues = true; + private Action? onBuild; + + + public ITapetiConfigFeaturesBuilder DisablePublisherConfirms() + { + return SetPublisherConfirms(false); + } + + public ITapetiConfigFeaturesBuilder SetPublisherConfirms(bool enabled) + { + publisherConfirms = enabled; + return this; + } + + public ITapetiConfigFeaturesBuilder EnableDeclareDurableQueues() + { + return SetDeclareDurableQueues(true); + } + + public ITapetiConfigFeaturesBuilder SetDeclareDurableQueues(bool enabled) + { + declareDurableQueues = enabled; + return this; + } + + public ITapetiConfigFeaturesBuilder DisableVerifyDurableQueues() + { + return SetVerifyDurableQueues(false); + } + + public ITapetiConfigFeaturesBuilder SetVerifyDurableQueues(bool enabled) + { + verifyDurableQueues = enabled; + return this; + } + + // ReSharper disable once ParameterHidesMember + public void DelayFeatures(Action onBuild) + { + this.onBuild = onBuild; + } + + public ITapetiConfigFeatures Build() + { + onBuild?.Invoke(this); + return new ConfigFeatures + { + DeclareDurableQueues = declareDurableQueues, + PublisherConfirms = publisherConfirms, + VerifyDurableQueues = verifyDurableQueues + }; + } + } + + internal class ConfigMiddleware : ITapetiConfigMiddleware { private readonly List messageMiddleware = new();