From 3e57193283d846d7d68a7d35ac51a889a3e29107 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Wed, 15 Oct 2025 11:57:56 +0200 Subject: [PATCH 01/25] Hack my way through things for now --- .../AcceptanceTestingPersistence.cs | 7 +-- .../Persistence/EnabledPersistence.cs | 2 +- .../Learning/LearningPersistence.cs | 2 +- .../Persistence/PersistenceComponent.cs | 8 +-- .../Persistence/PersistenceDefinition.cs | 58 ++++++++++--------- .../Persistence/PersistenceStorageMerger.cs | 2 +- .../Persistence/StorageType.cs | 47 ++++++++++----- 7 files changed, 73 insertions(+), 53 deletions(-) diff --git a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs index b4db3855487..49a60405277 100644 --- a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs +++ b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs @@ -1,6 +1,5 @@ namespace NServiceBus; -using Features; using AcceptanceTesting; using Persistence; @@ -8,8 +7,8 @@ public class AcceptanceTestingPersistence : PersistenceDefinition { internal AcceptanceTestingPersistence() { - Supports(s => s.EnableFeatureByDefault()); - Supports(s => s.EnableFeatureByDefault()); - Supports(s => s.EnableFeatureByDefault()); + Supports(); + Supports(); + Supports(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs index ee464832c93..a6427cff615 100644 --- a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs +++ b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs @@ -5,6 +5,6 @@ class EnabledPersistence { - public List SelectedStorages { get; set; } + public List SelectedStorages { get; set; } public Type DefinitionType; } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index 1c00bf777ad..a089a56f5de 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -8,5 +8,5 @@ /// public class LearningPersistence : PersistenceDefinition { - internal LearningPersistence() => Supports(s => s.EnableFeatureByDefault()); + internal LearningPersistence() => Supports(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index 84f75197549..2d208983a22 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -21,7 +21,7 @@ public static void ConfigurePersistence(this SettingsHolder settings) settings.ValidateSagaAndOutboxUseSamePersistence(enabledPersistences); - var resultingSupportedStorages = new List(); + var resultingSupportedStorages = new List(); var diagnostics = new Dictionary(); foreach (var definition in enabledPersistences) @@ -36,7 +36,7 @@ public static void ConfigurePersistence(this SettingsHolder settings) persistenceDefinition.ApplyActionForStorage(storageType, settings); resultingSupportedStorages.Add(storageType); - diagnostics.Add(storageType.Name, new + diagnostics.Add(storageType.ToString(), new { Type = definition.DefinitionType.FullName, Version = FileVersionRetriever.GetFileVersion(definition.DefinitionType) @@ -51,8 +51,8 @@ public static void ConfigurePersistence(this SettingsHolder settings) static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, List enabledPersistences) { - var sagaPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(typeof(StorageType.Sagas))); - var outboxPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(typeof(StorageType.Outbox))); + var sagaPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Sagas.Instance)); + var outboxPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Outbox.Instance)); var bothFeaturesEnabled = settings.IsFeatureEnabled(typeof(Features.Sagas)) && settings.IsFeatureEnabled(typeof(Features.Outbox)); if (sagaPersisterType != null diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index 8975b17fb31..834022087e7 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using Features; using Settings; /// @@ -13,14 +13,27 @@ public abstract class PersistenceDefinition /// /// Used by the storage definitions to declare what they support. /// - protected void Supports(Action action) where T : StorageType + protected void Supports() + where TStorage : StorageType + where TFeature : Feature { - ArgumentNullException.ThrowIfNull(action); - if (storageToActionMap.ContainsKey(typeof(T))) + var storageType = StorageType.Get(); + if (storageToFeatureMap.TryGetValue(storageType, out Type supportedStorageType)) { - throw new Exception($"Action for {typeof(T)} already defined."); + throw new Exception($"Storage {typeof(TStorage)} is already supported by {supportedStorageType}"); } - storageToActionMap[typeof(T)] = action; + + storageToFeatureMap[storageType] = typeof(TFeature); + } + + /// + /// Used by the storage definitions to declare what they support. + /// +#pragma warning disable CA1822 + protected void Supports(Action action) where T : StorageType +#pragma warning restore CA1822 + { + // TODO obsolete } /// @@ -35,28 +48,24 @@ protected void Defaults(Action action) /// /// True if supplied storage is supported. /// - public bool HasSupportFor() where T : StorageType - { - return HasSupportFor(typeof(T)); - } + public bool HasSupportFor() where T : StorageType => storageToFeatureMap.ContainsKey(StorageType.Get()); /// /// True if supplied storage is supported. /// +#pragma warning disable CA1822 public bool HasSupportFor(Type storageType) +#pragma warning restore CA1822 { + // TODO ArgumentNullException.ThrowIfNull(storageType); - return storageToActionMap.ContainsKey(storageType); + return false; } - internal void ApplyActionForStorage(Type storageType, SettingsHolder settings) + internal void ApplyActionForStorage(StorageType storageType, SettingsHolder settings) { - if (!storageType.IsSubclassOf(typeof(StorageType))) - { - throw new ArgumentException($"Storage type '{storageType.FullName}' is not a sub-class of StorageType", nameof(storageType)); - } - var actionForStorage = storageToActionMap[storageType]; - actionForStorage(settings); + var featureSupportingStorage = storageToFeatureMap[storageType]; + _ = settings.EnableFeatureByDefault(featureSupportingStorage); } internal void ApplyDefaults(SettingsHolder settings) @@ -67,16 +76,9 @@ internal void ApplyDefaults(SettingsHolder settings) } } - internal List GetSupportedStorages(List selectedStorages) - { - if (selectedStorages.Count > 0) - { - return selectedStorages; - } - - return storageToActionMap.Keys.ToList(); - } + internal IReadOnlyCollection GetSupportedStorages(IReadOnlyCollection selectedStorages) => + selectedStorages.Count > 0 ? selectedStorages : [.. storageToFeatureMap.Keys]; readonly List> defaults = []; - readonly Dictionary> storageToActionMap = []; + readonly Dictionary storageToFeatureMap = []; } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs b/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs index 5f797c8f5c6..b5d39036ce3 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs @@ -10,7 +10,7 @@ public static List MergePersistences(this SettingsHolder set { definitions.Reverse(); - var availableStorages = StorageType.GetAvailableStorageTypes(); + var availableStorages = new List(StorageType.GetAvailableStorageTypes()); var mergedEnabledPersistences = new List(); foreach (var definition in definitions) diff --git a/src/NServiceBus.Core/Persistence/StorageType.cs b/src/NServiceBus.Core/Persistence/StorageType.cs index de9cec00d9c..93f60eb222a 100644 --- a/src/NServiceBus.Core/Persistence/StorageType.cs +++ b/src/NServiceBus.Core/Persistence/StorageType.cs @@ -2,28 +2,41 @@ using System; using System.Collections.Generic; -using System.Linq; /// /// The storage types used for NServiceBus needs. /// -public abstract partial class StorageType +public abstract class StorageType { - StorageType(string storage) - { - this.storage = storage; - } + StorageType(string storage) => this.storage = storage; + + /// + public override string ToString() => storage; /// - public override string ToString() + public override bool Equals(object obj) { - return storage; + if (obj is StorageType other) + { + return storage == other.storage; + } + + return false; } - internal static List GetAvailableStorageTypes() + /// + public override int GetHashCode() => storage != null ? storage.GetHashCode() : 0; + + internal static IReadOnlyCollection GetAvailableStorageTypes() => + [Subscriptions.Instance, Sagas.Instance, Outbox.Instance]; + + internal static StorageType Get() where TStorageType : notnull, StorageType => typeof(TStorageType) switch { - return typeof(StorageType).GetNestedTypes().Where(t => t.BaseType == typeof(StorageType)).ToList(); - } + { } t when t == typeof(Subscriptions) => Subscriptions.Instance, + { } t when t == typeof(Sagas) => Sagas.Instance, + { } t when t == typeof(Outbox) => Outbox.Instance, + _ => throw new InvalidOperationException($"The storage type '{typeof(TStorageType)}' is not supported.") + }; readonly string storage; @@ -32,9 +45,11 @@ internal static List GetAvailableStorageTypes() /// public sealed class Subscriptions : StorageType { - internal Subscriptions() : base("Subscriptions") + Subscriptions() : base("Subscriptions") { } + + internal static readonly StorageType Instance = new Subscriptions(); } /// @@ -42,9 +57,11 @@ internal Subscriptions() : base("Subscriptions") /// public sealed class Sagas : StorageType { - internal Sagas() : base("Sagas") + Sagas() : base("Sagas") { } + + internal static readonly StorageType Instance = new Sagas(); } /// @@ -52,8 +69,10 @@ internal Sagas() : base("Sagas") /// public sealed class Outbox : StorageType { - internal Outbox() : base("Outbox") + Outbox() : base("Outbox") { } + + internal static readonly StorageType Instance = new Outbox(); } } \ No newline at end of file From 66926bbb229a81831fb9eb1b95d69f9c7de05d74 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Wed, 15 Oct 2025 18:17:06 +0200 Subject: [PATCH 02/25] Cleanup some tests --- .../Pub_from_sendonly.cs | 78 +++++++------------ .../Cross_q_tx_msg_moved_to_error_q.cs | 1 - 2 files changed, 29 insertions(+), 50 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs index 06c9c498575..6f270c21a6a 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs @@ -6,12 +6,12 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NServiceBus; -using NServiceBus.AcceptanceTesting; -using NServiceBus.AcceptanceTests.EndpointTemplates; -using NServiceBus.Extensibility; -using NServiceBus.Features; +using AcceptanceTesting; +using EndpointTemplates; +using Extensibility; +using Features; using NServiceBus.Persistence; -using NServiceBus.Unicast.Subscriptions; +using Unicast.Subscriptions; using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; @@ -39,39 +39,27 @@ public class Context : ScenarioContext public class SendOnlyPublisher : EndpointConfigurationBuilder { - public SendOnlyPublisher() - { + public SendOnlyPublisher() => EndpointSetup(b => { b.SendOnly(); - b.UsePersistence(typeof(HardCodedPersistence)); + b.UsePersistence(); b.DisableFeature(); }, metadata => metadata.RegisterSelfAsPublisherFor(this)); - } } public class Subscriber : EndpointConfigurationBuilder { - public Subscriber() - { - EndpointSetup(c => c.DisableFeature()); - } + public Subscriber() => EndpointSetup(c => c.DisableFeature()); - public class MyHandler : IHandleMessages + public class MyHandler(Context testContext) : IHandleMessages { - public MyHandler(Context context) - { - testContext = context; - } - public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) { testContext.SubscriberGotTheEvent = true; return Task.CompletedTask; } - - Context testContext; } } @@ -80,47 +68,39 @@ public class MyEvent : IEvent { } - public class HardCodedPersistence : PersistenceDefinition + public class HardCodedPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - internal HardCodedPersistence() - { + HardCodedPersistence() => Supports(s => s.EnableFeatureByDefault()); - } + + public static HardCodedPersistence Create() => new(); } public class HardCodedPersistenceFeature : Feature { - protected override void Setup(FeatureConfigurationContext context) - { + protected override void Setup(FeatureConfigurationContext context) => context.Services.AddSingleton(typeof(ISubscriptionStorage), typeof(HardcodedSubscriptionManager)); - } } public class HardcodedSubscriptionManager : ISubscriptionStorage { - public HardcodedSubscriptionManager() - { - addressTask = Task.FromResult(new[] - { - new Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber(Conventions.EndpointNamingConvention(typeof(Subscriber)), null) - }.AsEnumerable()); - } + public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, + MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) => + Task.CompletedTask; - public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } + public Task Unsubscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, + MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) => + Task.CompletedTask; - public Task Unsubscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context, CancellationToken cancellationToken = default) - { - return addressTask; - } + public Task> + GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context, + CancellationToken cancellationToken = default) => addressTask; - Task> addressTask; + readonly Task> addressTask = + Task.FromResult(new[] + { + new Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber( + Conventions.EndpointNamingConvention(typeof(Subscriber)), null) + }.AsEnumerable()); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Cross_q_tx_msg_moved_to_error_q.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Cross_q_tx_msg_moved_to_error_q.cs index 30af44ca69e..b37c4fce3d2 100644 --- a/src/NServiceBus.AcceptanceTests/Recoverability/Cross_q_tx_msg_moved_to_error_q.cs +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Cross_q_tx_msg_moved_to_error_q.cs @@ -1,7 +1,6 @@ namespace NServiceBus.AcceptanceTests.Recoverability; using System; -using System.Linq; using System.Threading.Tasks; using AcceptanceTesting; using AcceptanceTesting.Customization; From 06ce79598840a0ed155a6abb80e2c3968b5b9a8d Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Wed, 15 Oct 2025 18:18:45 +0200 Subject: [PATCH 03/25] Spike definition factory --- .../AcceptanceTestingPersistence.cs | 8 ++++++-- .../Persistence/Learning/LearningPersistence.cs | 5 +++-- .../Persistence/PersistenceConfig.cs | 7 +++++-- .../Persistence/PersistenceDefinition.cs | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs index 49a60405277..296003b71ed 100644 --- a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs +++ b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs @@ -2,13 +2,17 @@ using AcceptanceTesting; using Persistence; +using Settings; -public class AcceptanceTestingPersistence : PersistenceDefinition +public class AcceptanceTestingPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - internal AcceptanceTestingPersistence() + AcceptanceTestingPersistence(SettingsHolder settingsHolder) { + settingsHolder.Set(); Supports(); Supports(); Supports(); } + + public static AcceptanceTestingPersistence Create(SettingsHolder settingsHolder) => new(settingsHolder); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index a089a56f5de..f0e24c99626 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -6,7 +6,8 @@ /// /// Used to enable Learning persistence. /// -public class LearningPersistence : PersistenceDefinition +public class LearningPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - internal LearningPersistence() => Supports(); + LearningPersistence() => Supports(); + public static LearningPersistence Create() => new(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceConfig.cs b/src/NServiceBus.Core/Persistence/PersistenceConfig.cs index 7deb054bc6d..f563d65267b 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceConfig.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceConfig.cs @@ -15,7 +15,8 @@ public static class PersistenceConfig /// /// The persistence definition eg , NHibernate etc. /// The instance to apply the settings to. - public static PersistenceExtensions UsePersistence(this EndpointConfiguration config) where T : PersistenceDefinition + public static PersistenceExtensions UsePersistence(this EndpointConfiguration config) + where T : PersistenceDefinition, IPersistenceDefinitionFactory { ArgumentNullException.ThrowIfNull(config); return new PersistenceExtensions(config.Settings); @@ -27,7 +28,8 @@ public static PersistenceExtensions UsePersistence(this EndpointConfigurat /// The persistence definition eg , NHibernate etc. /// The storage type. /// The instance to apply the settings to. - public static PersistenceExtensions UsePersistence(this EndpointConfiguration config) where T : PersistenceDefinition + public static PersistenceExtensions UsePersistence(this EndpointConfiguration config) + where T : PersistenceDefinition, IPersistenceDefinitionFactory where S : StorageType { ArgumentNullException.ThrowIfNull(config); @@ -39,6 +41,7 @@ public static PersistenceExtensions UsePersistence(this EndpointConf /// /// The instance to apply the settings to. /// The persistence definition eg , NHibernate etc. + // TODO obsolete? public static PersistenceExtensions UsePersistence(this EndpointConfiguration config, Type definitionType) { ArgumentNullException.ThrowIfNull(config); diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index 834022087e7..b7dceb993ea 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -81,4 +81,18 @@ internal IReadOnlyCollection GetSupportedStorages(IReadOnlyCollecti readonly List> defaults = []; readonly Dictionary storageToFeatureMap = []; +} + +/// +/// +/// +/// +public interface IPersistenceDefinitionFactory + where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory +{ + /// + /// + /// + /// + static abstract TDefinition Create(SettingsHolder settings = null); } \ No newline at end of file From 4198d29d767f70c027f31a9445ace5d468207964 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 09:40:24 +0200 Subject: [PATCH 04/25] Obsolete some no longer needed APIs --- .../AcceptanceTestingPersistence.cs | 5 +- .../IPersistenceDefinitionFactory.cs | 19 ++ .../Learning/LearningPersistence.cs | 4 +- .../Persistence/PersistenceConfig.cs | 15 +- .../Persistence/PersistenceDefinition.cs | 14 -- .../Persistence/PersistenceExtensions.cs | 68 +++---- src/NServiceBus.Core/obsoletes-v10.cs | 173 ++++++++++++++---- 7 files changed, 186 insertions(+), 112 deletions(-) create mode 100644 src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs diff --git a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs index 296003b71ed..5024e26aec7 100644 --- a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs +++ b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs @@ -6,13 +6,12 @@ public class AcceptanceTestingPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - AcceptanceTestingPersistence(SettingsHolder settingsHolder) + AcceptanceTestingPersistence() { - settingsHolder.Set(); Supports(); Supports(); Supports(); } - public static AcceptanceTestingPersistence Create(SettingsHolder settingsHolder) => new(settingsHolder); + public static AcceptanceTestingPersistence Create(SettingsHolder settingsHolder) => new(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs b/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs new file mode 100644 index 00000000000..062a41c5702 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs @@ -0,0 +1,19 @@ +#nullable enable + +namespace NServiceBus.Persistence; + +using Settings; + +/// +/// +/// +/// +public interface IPersistenceDefinitionFactory + where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory +{ + /// + /// Creates the persistence definition. + /// + /// The persistence definition. + static abstract TDefinition Create(SettingsHolder settings); +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index f0e24c99626..8fc805f4086 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -2,6 +2,7 @@ using Features; using Persistence; +using Settings; /// /// Used to enable Learning persistence. @@ -9,5 +10,6 @@ public class LearningPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { LearningPersistence() => Supports(); - public static LearningPersistence Create() => new(); + + public static LearningPersistence Create(SettingsHolder settings) => new(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceConfig.cs b/src/NServiceBus.Core/Persistence/PersistenceConfig.cs index f563d65267b..c53e8907626 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceConfig.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceConfig.cs @@ -8,7 +8,7 @@ namespace NServiceBus; /// /// Enables users to select persistence by calling .UsePersistence(). /// -public static class PersistenceConfig +public static partial class PersistenceConfig { /// /// Configures the given persistence to be used. @@ -35,17 +35,4 @@ public static PersistenceExtensions UsePersistence(this EndpointConf ArgumentNullException.ThrowIfNull(config); return new PersistenceExtensions(config.Settings); } - - /// - /// Configures the given persistence to be used. - /// - /// The instance to apply the settings to. - /// The persistence definition eg , NHibernate etc. - // TODO obsolete? - public static PersistenceExtensions UsePersistence(this EndpointConfiguration config, Type definitionType) - { - ArgumentNullException.ThrowIfNull(config); - ArgumentNullException.ThrowIfNull(definitionType); - return new PersistenceExtensions(definitionType, config.Settings, null); - } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index b7dceb993ea..834022087e7 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -81,18 +81,4 @@ internal IReadOnlyCollection GetSupportedStorages(IReadOnlyCollecti readonly List> defaults = []; readonly Dictionary storageToFeatureMap = []; -} - -/// -/// -/// -/// -public interface IPersistenceDefinitionFactory - where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory -{ - /// - /// - /// - /// - static abstract TDefinition Create(SettingsHolder settings = null); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs index 2aeefe034b4..501d3ccfbd9 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs @@ -1,4 +1,6 @@ -namespace NServiceBus; +#nullable enable + +namespace NServiceBus; using System; using System.Collections.Generic; @@ -19,7 +21,7 @@ public class PersistenceExtensions : PersistenceExtensions /// /// Initializes a new instance of . /// - public PersistenceExtensions(SettingsHolder settings) : base(settings, typeof(S)) + public PersistenceExtensions(SettingsHolder settings) : base(settings, StorageType.Get()) { } } @@ -29,54 +31,40 @@ public PersistenceExtensions(SettingsHolder settings) : base(settings, typeof(S) /// methods. /// /// The persister definition eg , etc. -public class PersistenceExtensions : PersistenceExtensions where T : PersistenceDefinition +public partial class PersistenceExtensions : ExposeSettings + where T : PersistenceDefinition { /// /// Default constructor. /// - public PersistenceExtensions(SettingsHolder settings) : base(typeof(T), settings, null) + public PersistenceExtensions(SettingsHolder settings) : this(settings, default(StorageType)) { } /// /// Constructor for a specific . /// - protected PersistenceExtensions(SettingsHolder settings, Type storageType) : base(typeof(T), settings, storageType) - { - } -} - -/// -/// This class provides implementers of persisters with an extension mechanism for custom settings via extension -/// methods. -/// -public class PersistenceExtensions : ExposeSettings -{ - /// - /// Initializes a new instance of . - /// - public PersistenceExtensions(Type definitionType, SettingsHolder settings, Type storageType) - : base(settings) + protected PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null) : base(settings) { - List definitions = settings.GetOrSetEnabledPersistences(); - - var enabledPersistence = new EnabledPersistence - { - DefinitionType = definitionType, - SelectedStorages = [] - }; - - if (storageType != null) - { - var definition = definitionType.Construct(); - if (!definition.HasSupportFor(storageType)) - { - throw new Exception($"{definitionType.Name} does not support storage type {storageType.Name}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); - } - - enabledPersistence.SelectedStorages.Add(storageType); - } - - definitions.Add(enabledPersistence); + // List definitions = settings.GetOrSetEnabledPersistences(); + // + // var enabledPersistence = new EnabledPersistence + // { + // DefinitionType = definitionType, + // SelectedStorages = [] + // }; + // + // if (storageType != null) + // { + // var definition = definitionType.Construct(); + // if (!definition.HasSupportFor(storageType)) + // { + // throw new Exception($"{definitionType.Name} does not support storage type {storageType.Name}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); + // } + // + // enabledPersistence.SelectedStorages.Add(storageType); + // } + // + // definitions.Add(enabledPersistence); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/obsoletes-v10.cs b/src/NServiceBus.Core/obsoletes-v10.cs index 13799a4d2c4..fef58a91ba0 100644 --- a/src/NServiceBus.Core/obsoletes-v10.cs +++ b/src/NServiceBus.Core/obsoletes-v10.cs @@ -7,22 +7,29 @@ namespace NServiceBus using System.Reflection; using System.Text.Json.Serialization; using System.Xml.Serialization; + using Configuration.AdvancedExtensibility; using NServiceBus.DataBus; using Particular.Obsoletes; + using Settings; [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public static class ConfigureFileShareDataBus { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] - public static DataBusExtensions BasePath(this DataBusExtensions config, string basePath) => throw new NotImplementedException(); + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] + public static DataBusExtensions BasePath(this DataBusExtensions config, + string basePath) => throw new NotImplementedException(); } public partial class Conventions @@ -31,7 +38,9 @@ public partial class Conventions Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public bool IsDataBusProperty(PropertyInfo property) => throw new NotImplementedException(); } @@ -41,29 +50,38 @@ public partial class ConventionsBuilder Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] - public ConventionsBuilder DefiningDataBusPropertiesAs(Func definesDataBusProperty) => throw new NotImplementedException(); + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] + public ConventionsBuilder DefiningDataBusPropertiesAs(Func definesDataBusProperty) => + throw new NotImplementedException(); } [ObsoleteMetadata( - Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", - RemoveInVersion = "11", - TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", + RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public class DataBusProperty : IDataBusProperty where T : class { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public DataBusProperty() => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public DataBusProperty(T value) => throw new NotImplementedException(); [JsonIgnore] @@ -72,7 +90,9 @@ public class DataBusProperty : IDataBusProperty where T : class Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public T Value => throw new NotImplementedException(); [JsonIgnore] @@ -80,50 +100,64 @@ public class DataBusProperty : IDataBusProperty where T : class Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public Type Type => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public string Key { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public bool HasValue { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public void SetValue(object valueToSet) => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public object GetValue() => throw new NotImplementedException(); } [ObsoleteMetadata( - Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", - RemoveInVersion = "11", - TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", + RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public class FileShareDataBus : DataBusDefinition { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] protected internal override Type ProvidedByFeature() => throw new NotImplementedException(); } @@ -131,42 +165,54 @@ public class FileShareDataBus : DataBusDefinition Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public interface IDataBusProperty { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] string Key { get; set; } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] bool HasValue { get; set; } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] object GetValue(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] void SetValue(object value); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] Type Type { get; } } @@ -174,28 +220,36 @@ public interface IDataBusProperty Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public class SystemJsonDataBusSerializer : IDataBusSerializer { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public void Serialize(object dataBusProperty, Stream stream) => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public object Deserialize(Type propertyType, Stream stream) => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public string ContentType => throw new NotImplementedException(); } @@ -203,15 +257,20 @@ public class SystemJsonDataBusSerializer : IDataBusSerializer Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] public static class UseDataBusExtensions { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] - public static DataBusExtensions UseDataBus(this EndpointConfiguration config) + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] + public static DataBusExtensions UseDataBus( + this EndpointConfiguration config) where TDataBusDefinition : DataBusDefinition, new() where TDataBusSerializer : IDataBusSerializer, new() => throw new NotImplementedException(); @@ -219,16 +278,50 @@ public static DataBusExtensions UseDataBus UseDataBus(this EndpointConfiguration config, IDataBusSerializer dataBusSerializer) + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] + public static DataBusExtensions UseDataBus( + this EndpointConfiguration config, IDataBusSerializer dataBusSerializer) where TDataBusDefinition : DataBusDefinition, new() => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] - public static DataBusExtensions UseDataBus(this EndpointConfiguration config, Func dataBusFactory, IDataBusSerializer dataBusSerializer) => throw new NotImplementedException(); + [Obsolete( + "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", + true)] + public static DataBusExtensions UseDataBus(this EndpointConfiguration config, + Func dataBusFactory, IDataBusSerializer dataBusSerializer) => + throw new NotImplementedException(); + } + + public static partial class PersistenceConfig + { + [ObsoleteMetadata(ReplacementTypeOrMember = "UsePersistence", RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete("Use 'UsePersistence' instead. Will be removed in version 11.0.0.", true)] + public static PersistenceExtensions UsePersistence(this EndpointConfiguration config, Type definitionType) => + throw new NotImplementedException(); + } + + [ObsoleteMetadata(ReplacementTypeOrMember = "PersistenceExtensions", RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete("Use 'PersistenceExtensions' instead. Will be removed in version 11.0.0.", true)] + public class PersistenceExtensions : ExposeSettings + { + public PersistenceExtensions(Type definitionType, SettingsHolder settings, Type storageType) + : base(settings) => + throw new NotImplementedException(); + } + + public partial class PersistenceExtensions + { + [ObsoleteMetadata(ReplacementTypeOrMember = "PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null)", RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete("Use 'PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null)' instead. Will be removed in version 11.0.0.", true)] + protected PersistenceExtensions(SettingsHolder settings, Type storageType) : base(settings) => throw new NotImplementedException(); } } From dbd0a92fd312eb52e300ee0b0214914683eebfa7 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 13:40:08 +0200 Subject: [PATCH 05/25] Align acceptance tests --- ...enabled_without_persister_supporting_it.cs | 12 +++--- ...ot_provide_synchronized_storage_session.cs | 43 ++++++------------- ...hen_configuring_subscription_authorizer.cs | 28 ++++-------- .../Pub_from_sendonly.cs | 3 +- 4 files changed, 28 insertions(+), 58 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs index a8dadf92f58..998e0771917 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs @@ -5,6 +5,7 @@ namespace NServiceBus.AcceptanceTests.Outbox; using EndpointTemplates; using NServiceBus.Persistence; using NUnit.Framework; +using Settings; public class When_outbox_enabled_without_persister_supporting_it : NServiceBusAcceptanceTest { @@ -19,24 +20,21 @@ public void Should_fail_to_start() Assert.That(exception.Message, Does.Contain("The selected persistence doesn't have support for outbox storage")); } - public class Context : ScenarioContext - { - } + public class Context : ScenarioContext; public class Endpoint : EndpointConfigurationBuilder { - public Endpoint() - { + public Endpoint() => EndpointSetup(c => { c.EnableOutbox(); c.ConfigureTransport().TransportTransactionMode = TransportTransactionMode.ReceiveOnly; c.UsePersistence(); }); - } } - class FakeNoOutboxSupportPersistence : PersistenceDefinition + class FakeNoOutboxSupportPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { + public static FakeNoOutboxSupportPersistence Create(SettingsHolder settings) => new(); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs index 66dd3934ec8..7414110cd22 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using NServiceBus.Persistence; using NUnit.Framework; +using Settings; using Unicast.Subscriptions; using Unicast.Subscriptions.MessageDrivenSubscriptions; @@ -18,69 +19,51 @@ public class When_a_persistence_does_not_provide_synchronized_storage_session : // is not altered by Fody to throw an ObjectDisposedException if it was disposed [Test] [Repeat(2)] - public async Task ReceiveFeature_should_work_without_ISynchronizedStorage() - { + public async Task ReceiveFeature_should_work_without_ISynchronizedStorage() => await Scenario.Define() .WithEndpoint(e => e.When(b => b.SendLocal(new MyMessage()))) .Done(c => c.MessageReceived) .Run(); - } - class FakeNoSynchronizedStorageSupportPersistence : PersistenceDefinition + class FakeNoSynchronizedStorageSupportPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakeNoSynchronizedStorageSupportPersistence() + FakeNoSynchronizedStorageSupportPersistence() { Supports(s => { }); Supports(s => { }); } + + public static FakeNoSynchronizedStorageSupportPersistence Create(SettingsHolder settings) => new(); } class NoOpISubscriptionStorage : ISubscriptionStorage { - public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context, CancellationToken cancellationToken = default) - { - return Task.FromResult>(null); - } + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context, CancellationToken cancellationToken = default) => Task.FromResult>(null); - public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } + public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) => Task.CompletedTask; - public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } + public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) => Task.CompletedTask; } class NoSyncEndpoint : EndpointConfigurationBuilder { - public NoSyncEndpoint() - { + public NoSyncEndpoint() => EndpointSetup(c => { // The subscription storage is needed because at this stage we have no way of DisablingPublishing on the non-generic version of ConfigureTransport c.RegisterComponents(container => container.AddSingleton()); c.UsePersistence(); }); - } } - public class MyMessageHandler : IHandleMessages + public class MyMessageHandler(Context testContext) : IHandleMessages { - public MyMessageHandler(Context context) - { - testContext = context; - } - public Task Handle(MyMessage message, IMessageHandlerContext context) { testContext.MessageReceived = true; return Task.CompletedTask; } - - Context testContext; } public class Context : ScenarioContext @@ -89,7 +72,5 @@ public class Context : ScenarioContext public bool MessageReceived { get; set; } } - public class MyMessage : ICommand - { - } + public class MyMessage : ICommand; } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs b/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs index 1911a88c959..a033a2cbdca 100644 --- a/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs +++ b/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Persistence; +using Settings; using Unicast.Subscriptions; using Unicast.Subscriptions.MessageDrivenSubscriptions; @@ -100,10 +101,7 @@ public PublisherWithAuthorizer() } class StorageAccessor : FeatureStartupTask { - public StorageAccessor(ISubscriptionStorage subscriptionStorage, Context testContext) - { - testContext.SubscriptionStorage = (FakePersistence.FakeSubscriptionStorage)subscriptionStorage; - } + public StorageAccessor(ISubscriptionStorage subscriptionStorage, Context testContext) => testContext.SubscriptionStorage = (FakePersistence.FakeSubscriptionStorage)subscriptionStorage; protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken = default) => Task.CompletedTask; @@ -111,27 +109,17 @@ public StorageAccessor(ISubscriptionStorage subscriptionStorage, Context testCon } } - public class AllowedEvent : IEvent - { - } + public class AllowedEvent : IEvent; - public class ForbiddenEvent : IEvent - { - } + public class ForbiddenEvent : IEvent; - class FakePersistence : PersistenceDefinition + class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakePersistence() - { - Supports(s => s.EnableFeatureByDefault(typeof(SubscriptionStorageFeature))); - } + FakePersistence() => Supports(); class SubscriptionStorageFeature : Feature { - protected override void Setup(FeatureConfigurationContext context) - { - context.Services.AddSingleton(new FakeSubscriptionStorage()); - } + protected override void Setup(FeatureConfigurationContext context) => context.Services.AddSingleton(new FakeSubscriptionStorage()); } public class FakeSubscriptionStorage : ISubscriptionStorage @@ -148,5 +136,7 @@ public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscribe public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context, CancellationToken cancellationToken = default) => throw new NotImplementedException(); } + + public static FakePersistence Create(SettingsHolder settings) => new(); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs index 6f270c21a6a..d7877c5cdc5 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs @@ -14,6 +14,7 @@ using Unicast.Subscriptions; using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; using NUnit.Framework; +using Settings; using Conventions = AcceptanceTesting.Customization.Conventions; public class Pub_from_sendonly : NServiceBusAcceptanceTest @@ -73,7 +74,7 @@ public class HardCodedPersistence : PersistenceDefinition, IPersistenceDefinitio HardCodedPersistence() => Supports(s => s.EnableFeatureByDefault()); - public static HardCodedPersistence Create() => new(); + public static HardCodedPersistence Create(SettingsHolder settings) => new(); } public class HardCodedPersistenceFeature : Feature From 466e5811b083416e064f477ec4d5bab9aea99556 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 14:26:53 +0200 Subject: [PATCH 06/25] PersistenceRegistry --- .../Persistence/PersistenceExtensionsTests.cs | 29 +++--- .../Persistence/PersistenceStartupTests.cs | 20 +++- .../PersistenceStorageMergerTests.cs | 92 ++++++++++++------ .../Persistence/EnabledPersistence.cs | 5 +- .../Persistence/PersistenceComponent.cs | 37 +++----- .../Persistence/PersistenceDefinition.cs | 2 + .../Persistence/PersistenceExtensions.cs | 31 ++---- .../Persistence/PersistenceRegistry.cs | 95 +++++++++++++++++++ 8 files changed, 218 insertions(+), 93 deletions(-) create mode 100644 src/NServiceBus.Core/Persistence/PersistenceRegistry.cs diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs index d64fd57c44e..b2b8036d7e4 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs @@ -1,6 +1,7 @@ namespace NServiceBus.Core.Tests.Persistence; using System; +using NServiceBus.Features; using NServiceBus.Persistence; using Settings; using NUnit.Framework; @@ -11,17 +12,19 @@ public class When_configuring_storage_type_not_supported_by_persistence [Test] public void Should_throw_exception() { - var ex = Assert.Throws(() => new PersistenceExtensions(typeof(PartialPersistence), new SettingsHolder(), typeof(StorageType.Sagas))); + var ex = Assert.Throws(() => new PersistenceExtensions(new SettingsHolder())); Assert.That(ex.Message, Does.StartWith("PartialPersistence does not support storage type Sagas.")); } - public class PartialPersistence : PersistenceDefinition + public class PartialPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public PartialPersistence() + PartialPersistence() => Supports(); + + public static PartialPersistence Create(SettingsHolder settings) => new(); + + class FakeSubscriptionStorage : Feature { - Supports(s => - { - }); + protected internal override void Setup(FeatureConfigurationContext context) => throw new NotImplementedException(); } } } @@ -32,16 +35,18 @@ public class When_configuring_storage_type_supported_by_persistence [Test] public void Should_not_throw_exception() { - Assert.DoesNotThrow(() => new PersistenceExtensions(typeof(PartialPersistence), new SettingsHolder(), typeof(StorageType.Subscriptions))); + Assert.DoesNotThrow(() => new PersistenceExtensions(new SettingsHolder())); } - public class PartialPersistence : PersistenceDefinition + public class PartialPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public PartialPersistence() + public PartialPersistence() => Supports(); + + public static PartialPersistence Create(SettingsHolder settings) => new(); + + class FakeSubscriptionStorage : Feature { - Supports(s => - { - }); + protected internal override void Setup(FeatureConfigurationContext context) => throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs index 8d63611aa3e..8a6c71ffcae 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs @@ -60,13 +60,25 @@ public void Should_not_prevent_using_different_persistence_for_sagas_and_outbox_ Assert.DoesNotThrow(() => config.Settings.ConfigurePersistence(), "Should not throw for a single single feature enabled out of the two."); } - class FakeSagaPersistence : PersistenceDefinition + class FakeSagaPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakeSagaPersistence() => Supports(_ => { }); + FakeSagaPersistence() => Supports(); + public static FakeSagaPersistence Create(SettingsHolder settings) => new(); + + class FakeStorage : Feature + { + protected internal override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); + } } - class FakeOutboxPersistence : PersistenceDefinition + class FakeOutboxPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakeOutboxPersistence() => Supports(_ => { }); + FakeOutboxPersistence() => Supports(); + public static FakeOutboxPersistence Create(SettingsHolder settings) => new(); + + class FakeStorage : Feature + { + protected internal override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); + } } } diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs index 84e4f9fbc78..66caa3c5f1c 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs @@ -1,9 +1,10 @@ namespace NServiceBus.Core.Tests.Persistence; -using System.Collections.Generic; using System.Linq; +using NServiceBus.Features; using NServiceBus.Persistence; using NUnit.Framework; +using Settings; [TestFixture] public class When_no_storage_persistence_overrides_are_enabled @@ -13,20 +14,31 @@ public void Should_use_all_storages_supported_by_persistence() { var config = new EndpointConfiguration("MyEndpoint"); config.UsePersistence(); - var persistences = config.Settings.Get>(PersistenceComponent.PersistenceDefinitionsSettingsKey); - var resultedEnabledPersistences = config.Settings.MergePersistences(persistences); + var registry = config.Settings.Get(); + var enabledPersistences = registry.Merge(); - Assert.That(resultedEnabledPersistences[0].SelectedStorages, Is.EquivalentTo(StorageType.GetAvailableStorageTypes())); + using (Assert.EnterMultipleScope()) + { + Assert.That(enabledPersistences, Has.Count.EqualTo(1)); + Assert.That(enabledPersistences.ElementAt(0).SelectedStorages, Is.EquivalentTo(StorageType.GetAvailableStorageTypes())); + } } - class FakePersistence : PersistenceDefinition + class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakePersistence() + FakePersistence() + { + Supports(); + Supports(); + Supports(); + } + + public static FakePersistence Create(SettingsHolder settings) => new(); + + class FakeStorage : Feature { - Supports(_ => { }); - Supports(_ => { }); - Supports(_ => { }); + protected internal override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); } } } @@ -41,33 +53,48 @@ public void Should_replace_default_storages_by_overrides() config.UsePersistence(); config.UsePersistence(); config.UsePersistence(); - var persistences = config.Settings.Get>(PersistenceComponent.PersistenceDefinitionsSettingsKey); - var resultedEnabledPersistences = config.Settings.MergePersistences(persistences); + var registry = config.Settings.Get(); + var enabledPersistences = registry.Merge(); using (Assert.EnterMultipleScope()) { - Assert.That(resultedEnabledPersistences[0].SelectedStorages, Is.EquivalentTo([typeof(StorageType.Subscriptions)])); - Assert.That(resultedEnabledPersistences[1].SelectedStorages, Is.EquivalentTo([typeof(StorageType.Sagas)])); + Assert.That(enabledPersistences, Has.Count.EqualTo(2)); + Assert.That(enabledPersistences.ElementAt(0).SelectedStorages, Is.EquivalentTo([StorageType.Subscriptions.Instance])); + Assert.That(enabledPersistences.ElementAt(1).SelectedStorages, Is.EquivalentTo([StorageType.Sagas.Instance])); } } - class FakePersistence2 : PersistenceDefinition + class FakePersistence2 : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakePersistence2() + FakePersistence2() { - Supports(_ => { }); - Supports(_ => { }); + Supports(); + Supports(); + } + + public static FakePersistence2 Create(SettingsHolder settings) => new(); + + class FakeStorage : Feature + { + protected internal override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); } } - class FakePersistence : PersistenceDefinition + class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakePersistence() + FakePersistence() + { + Supports(); + Supports(); + Supports(); + } + + public static FakePersistence Create(SettingsHolder settings) => new(); + + class FakeStorage : Feature { - Supports(_ => { }); - Supports(_ => { }); - Supports(_ => { }); + protected internal override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); } } } @@ -80,19 +107,26 @@ public void Should_not_use_other_supported_storages() { var config = new EndpointConfiguration("MyEndpoint"); config.UsePersistence(); - var persistences = config.Settings.Get>(PersistenceComponent.PersistenceDefinitionsSettingsKey); - var resultedEnabledPersistences = config.Settings.MergePersistences(persistences); + var registry = config.Settings.Get(); + var enabledPersistences = registry.Merge(); - Assert.That(resultedEnabledPersistences.Any(p => p.SelectedStorages.Contains(typeof(StorageType.Subscriptions))), Is.False); + Assert.That(enabledPersistences.Any(p => p.SelectedStorages.Contains(StorageType.Subscriptions.Instance)), Is.False); } - class FakePersistence : PersistenceDefinition + class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public FakePersistence() + FakePersistence() + { + Supports(); + Supports(); + } + + public static FakePersistence Create(SettingsHolder settings) => new(); + + class FakeStorage : Feature { - Supports(_ => { }); - Supports(_ => { }); + protected internal override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); } } } diff --git a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs index a6427cff615..1dae58858b4 100644 --- a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs +++ b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs @@ -2,9 +2,12 @@ using System; using System.Collections.Generic; +using Persistence; class EnabledPersistence { public List SelectedStorages { get; set; } public Type DefinitionType; -} \ No newline at end of file +} + +record MergedPersistence(IReadOnlyCollection SelectedStorages, PersistenceDefinition Definition); \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index 2d208983a22..f84126feb44 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -5,41 +5,40 @@ using System.Linq; using Features; using Logging; -using Persistence; using Settings; static class PersistenceComponent { public static void ConfigurePersistence(this SettingsHolder settings) { - if (!settings.TryGet(PersistenceDefinitionsSettingsKey, out List definitions)) + if (!settings.TryGet(out var persistenceRegistry)) { return; } - var enabledPersistences = settings.MergePersistences(definitions); + var enabledPersistences = persistenceRegistry.Merge(); settings.ValidateSagaAndOutboxUseSamePersistence(enabledPersistences); var resultingSupportedStorages = new List(); var diagnostics = new Dictionary(); - foreach (var definition in enabledPersistences) + foreach (var enabledPersistence in enabledPersistences) { - var persistenceDefinition = definition.DefinitionType.Construct(); - + var persistenceDefinition = enabledPersistence.Definition; persistenceDefinition.ApplyDefaults(settings); - foreach (var storageType in definition.SelectedStorages) + var definitionType = persistenceDefinition.GetType(); + foreach (var storageType in enabledPersistence.SelectedStorages) { - Logger.DebugFormat("Activating persistence '{0}' to provide storage for '{1}' storage.", definition.DefinitionType.Name, storageType); + Logger.DebugFormat("Activating persistence '{0}' to provide storage for '{1}' storage.", definitionType.Name, storageType); persistenceDefinition.ApplyActionForStorage(storageType, settings); resultingSupportedStorages.Add(storageType); diagnostics.Add(storageType.ToString(), new { - Type = definition.DefinitionType.FullName, - Version = FileVersionRetriever.GetFileVersion(definition.DefinitionType) + Type = definitionType.FullName, + Version = FileVersionRetriever.GetFileVersion(definitionType) }); } } @@ -49,7 +48,7 @@ public static void ConfigurePersistence(this SettingsHolder settings) settings.AddStartupDiagnosticsSection("Persistence", diagnostics); } - static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, List enabledPersistences) + static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, IReadOnlyCollection enabledPersistences) { var sagaPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Sagas.Instance)); var outboxPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Outbox.Instance)); @@ -57,25 +56,13 @@ static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings if (sagaPersisterType != null && outboxPersisterType != null - && sagaPersisterType.DefinitionType != outboxPersisterType.DefinitionType + && sagaPersisterType.Definition != outboxPersisterType.Definition && bothFeaturesEnabled) { - throw new Exception($"Sagas and the Outbox need to use the same type of persistence. Saga persistence is configured to use {sagaPersisterType.DefinitionType.Name}. Outbox persistence is configured to use {outboxPersisterType.DefinitionType.Name}."); + throw new Exception($"Sagas and the Outbox need to use the same type of persistence. Saga persistence is configured to use {sagaPersisterType.Definition.GetType().Name}. Outbox persistence is configured to use {outboxPersisterType.GetType().Name}."); } } - internal static List GetOrSetEnabledPersistences(this SettingsHolder settings) - { - if (settings.TryGet(PersistenceDefinitionsSettingsKey, out List definitions)) - { - return definitions; - } - - definitions = []; - settings.Set(PersistenceDefinitionsSettingsKey, definitions); - return definitions; - } - internal static bool HasSupportFor(this IReadOnlySettings settings) where T : StorageType { settings.TryGet(ResultingSupportedStoragesSettingsKey, out List supportedStorages); diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index 834022087e7..51e642473ed 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -50,6 +50,8 @@ protected void Defaults(Action action) /// public bool HasSupportFor() where T : StorageType => storageToFeatureMap.ContainsKey(StorageType.Get()); + internal bool HasSupportFor(StorageType storageType) => storageToFeatureMap.ContainsKey(storageType); + /// /// True if supplied storage is supported. /// diff --git a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs index 501d3ccfbd9..18d3e86ed10 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs @@ -15,7 +15,7 @@ namespace NServiceBus; /// The persister definition eg , etc. /// The storage type. public class PersistenceExtensions : PersistenceExtensions - where T : PersistenceDefinition + where T : PersistenceDefinition, IPersistenceDefinitionFactory where S : StorageType { /// @@ -32,7 +32,7 @@ public PersistenceExtensions(SettingsHolder settings) : base(settings, StorageTy /// /// The persister definition eg , etc. public partial class PersistenceExtensions : ExposeSettings - where T : PersistenceDefinition + where T : PersistenceDefinition, IPersistenceDefinitionFactory { /// /// Default constructor. @@ -46,25 +46,12 @@ public partial class PersistenceExtensions : ExposeSettings /// protected PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null) : base(settings) { - // List definitions = settings.GetOrSetEnabledPersistences(); - // - // var enabledPersistence = new EnabledPersistence - // { - // DefinitionType = definitionType, - // SelectedStorages = [] - // }; - // - // if (storageType != null) - // { - // var definition = definitionType.Construct(); - // if (!definition.HasSupportFor(storageType)) - // { - // throw new Exception($"{definitionType.Name} does not support storage type {storageType.Name}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); - // } - // - // enabledPersistence.SelectedStorages.Add(storageType); - // } - // - // definitions.Add(enabledPersistence); + var registry = settings + .GetOrCreate() + .Enable(settings); + if (storageType is not null) + { + registry.WithStorage(storageType); + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs new file mode 100644 index 00000000000..5ddfa74f5a5 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -0,0 +1,95 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Linq; +using Persistence; +using Settings; + +sealed class PersistenceRegistry +{ + public EnableBuilder Enable(SettingsHolder settings) + where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory + { + if (definitions.TryGetValue(typeof(TDefinition), out var persistenceAndStorageTypes)) + { + return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this, settings); + } + + var definition = TDefinition.Create(settings); + definitions.Add(typeof(TDefinition), (definition, [])); + return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this, settings); + } + + public IReadOnlyCollection Merge() + { + IEnumerable EnabledStorages)>> reversedDefinitions = definitions.Reverse(); + + var availableStorages = new List(StorageType.GetAvailableStorageTypes()); + var mergedEnabledPersistences = new List(); + + foreach (var (_, (persistenceDefinition, enabledStorages)) in reversedDefinitions) + { + var supportedStorages = persistenceDefinition.GetSupportedStorages(enabledStorages); + + var selectedStorages = new List(); + + foreach (var storageType in supportedStorages) + { + if (!availableStorages.Contains(storageType)) + { + continue; + } + + selectedStorages.Add(storageType); + availableStorages.Remove(storageType); + } + + if (selectedStorages.Count != 0) + { + mergedEnabledPersistences.Add(new MergedPersistence(selectedStorages, persistenceDefinition)); + } + } + + return mergedEnabledPersistences; + } + + void EnableStorageFor(SettingsHolder settings, StorageType storage) + where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory + { + var builder = Enable(settings); + + if (!builder.Persistence.HasSupportFor(storage)) + { + throw new Exception($"{typeof(TDefinition).Name} does not support storage type {storage.GetType().Name}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); + } + + var (_, enabledStorageTypes) = definitions[typeof(TDefinition)]; + if (!enabledStorageTypes.Contains(storage)) + { + enabledStorageTypes.Add(storage); + } + } + + void EnableStorageFor(SettingsHolder settings) + where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory + where TStorage : StorageType => + EnableStorageFor(settings, StorageType.Get()); + + public class EnableBuilder( + TDefinition definition, + PersistenceRegistry registry, + SettingsHolder settings) + where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory + { + public TDefinition Persistence { get; } = definition; + + public void WithStorage() + where TStorage : StorageType => + registry.EnableStorageFor(settings); + + public void WithStorage(StorageType storageType) => registry.EnableStorageFor(settings, storageType); + } + + readonly Dictionary EnabledStorageTypes)> definitions = []; +} \ No newline at end of file From c2be6d8929c872e515a87d57e086cfc691e27cc9 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 14:29:26 +0200 Subject: [PATCH 07/25] Fix test --- .../Persistence/PersistenceStorageMergerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs index 66caa3c5f1c..435f1f83ace 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs @@ -60,8 +60,8 @@ public void Should_replace_default_storages_by_overrides() using (Assert.EnterMultipleScope()) { Assert.That(enabledPersistences, Has.Count.EqualTo(2)); - Assert.That(enabledPersistences.ElementAt(0).SelectedStorages, Is.EquivalentTo([StorageType.Subscriptions.Instance])); - Assert.That(enabledPersistences.ElementAt(1).SelectedStorages, Is.EquivalentTo([StorageType.Sagas.Instance])); + Assert.That(enabledPersistences.ElementAt(0).SelectedStorages, Is.EquivalentTo([StorageType.Sagas.Instance, StorageType.Subscriptions.Instance])); + Assert.That(enabledPersistences.ElementAt(1).SelectedStorages, Is.EquivalentTo([StorageType.Outbox.Instance])); } } From 532c6b3b264398ab26bf82e2c9db71167b1eb225 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 14:32:50 +0200 Subject: [PATCH 08/25] Fix more support usage --- ...does_not_provide_synchronized_storage_session.cs | 10 ++++++++-- .../MessageDrivenSubscriptions/Pub_from_sendonly.cs | 13 +++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs index 7414110cd22..12e0a3252ca 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs @@ -6,6 +6,7 @@ using AcceptanceTesting; using EndpointTemplates; using Extensibility; +using Features; using Microsoft.Extensions.DependencyInjection; using NServiceBus.Persistence; using NUnit.Framework; @@ -29,11 +30,16 @@ class FakeNoSynchronizedStorageSupportPersistence : PersistenceDefinition, IPers { FakeNoSynchronizedStorageSupportPersistence() { - Supports(s => { }); - Supports(s => { }); + Supports(); + Supports(); } public static FakeNoSynchronizedStorageSupportPersistence Create(SettingsHolder settings) => new(); + + sealed class FakeStorage : Feature + { + protected override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); + } } class NoOpISubscriptionStorage : ISubscriptionStorage diff --git a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs index d7877c5cdc5..b4cfe914738 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs @@ -65,25 +65,22 @@ public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context } - public class MyEvent : IEvent - { - } + public class MyEvent : IEvent; public class HardCodedPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - HardCodedPersistence() => - Supports(s => s.EnableFeatureByDefault()); + HardCodedPersistence() => Supports(); public static HardCodedPersistence Create(SettingsHolder settings) => new(); } - public class HardCodedPersistenceFeature : Feature + class HardCodedPersistenceFeature : Feature { protected override void Setup(FeatureConfigurationContext context) => - context.Services.AddSingleton(typeof(ISubscriptionStorage), typeof(HardcodedSubscriptionManager)); + context.Services.AddSingleton(); } - public class HardcodedSubscriptionManager : ISubscriptionStorage + class HardcodedSubscriptionManager : ISubscriptionStorage { public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) => From 696ad22cad38ff3f61aa79e238c69eacbca83f0d Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 15:00:20 +0200 Subject: [PATCH 09/25] More obsoletes and cleanup --- .../Persistence/EnabledPersistence.cs | 9 +--- .../IPersistenceDefinitionFactory.cs | 4 +- .../Learning/LearningPersistence.cs | 3 ++ .../Persistence/PersistenceComponent.cs | 12 ++--- .../Persistence/PersistenceDefinition.cs | 12 +---- .../Persistence/PersistenceExtensions.cs | 29 ++++-------- .../Persistence/PersistenceRegistry.cs | 10 +++-- .../Persistence/PersistenceStorageMerger.cs | 45 ------------------- .../Persistence/StorageType.cs | 4 +- src/NServiceBus.Core/obsoletes-v10.cs | 14 ++++++ 10 files changed, 42 insertions(+), 100 deletions(-) delete mode 100644 src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs diff --git a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs index 1dae58858b4..e900a4fd1c9 100644 --- a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs +++ b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs @@ -1,13 +1,6 @@ namespace NServiceBus; -using System; using System.Collections.Generic; using Persistence; -class EnabledPersistence -{ - public List SelectedStorages { get; set; } - public Type DefinitionType; -} - -record MergedPersistence(IReadOnlyCollection SelectedStorages, PersistenceDefinition Definition); \ No newline at end of file +record EnabledPersistence(IReadOnlyCollection SelectedStorages, PersistenceDefinition Definition); \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs b/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs index 062a41c5702..7715e114ff7 100644 --- a/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs +++ b/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs @@ -5,9 +5,9 @@ namespace NServiceBus.Persistence; using Settings; /// -/// +/// Defines a factory for creating persistence definitions used in . /// -/// +/// The persistence definition type. public interface IPersistenceDefinitionFactory where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index 8fc805f4086..945c11fbb31 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -11,5 +11,8 @@ public class LearningPersistence : PersistenceDefinition, IPersistenceDefinition { LearningPersistence() => Supports(); + /// + /// Creates the learning persistence definition. + /// public static LearningPersistence Create(SettingsHolder settings) => new(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index f84126feb44..56c5dc6e4ac 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -43,12 +43,12 @@ public static void ConfigurePersistence(this SettingsHolder settings) } } - settings.Set(ResultingSupportedStoragesSettingsKey, resultingSupportedStorages); + settings.Set>(resultingSupportedStorages); settings.AddStartupDiagnosticsSection("Persistence", diagnostics); } - static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, IReadOnlyCollection enabledPersistences) + static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, IReadOnlyCollection enabledPersistences) { var sagaPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Sagas.Instance)); var outboxPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Outbox.Instance)); @@ -65,14 +65,10 @@ static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings internal static bool HasSupportFor(this IReadOnlySettings settings) where T : StorageType { - settings.TryGet(ResultingSupportedStoragesSettingsKey, out List supportedStorages); + _ = settings.TryGet(out IReadOnlyCollection supportedStorages); - return supportedStorages?.Contains(typeof(T)) ?? false; + return supportedStorages?.Contains(StorageType.Get()) ?? false; } - internal const string PersistenceDefinitionsSettingsKey = "PersistenceDefinitions"; - - const string ResultingSupportedStoragesSettingsKey = "ResultingSupportedStorages"; - static readonly ILog Logger = LogManager.GetLogger(typeof(PersistenceComponent)); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index 51e642473ed..3092b4edc93 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -8,7 +8,7 @@ /// /// Base class for persistence definitions. /// -public abstract class PersistenceDefinition +public abstract partial class PersistenceDefinition { /// /// Used by the storage definitions to declare what they support. @@ -26,16 +26,6 @@ protected void Supports() storageToFeatureMap[storageType] = typeof(TFeature); } - /// - /// Used by the storage definitions to declare what they support. - /// -#pragma warning disable CA1822 - protected void Supports(Action action) where T : StorageType -#pragma warning restore CA1822 - { - // TODO obsolete - } - /// /// Used by the storage definitions to declare what they support. /// diff --git a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs index 18d3e86ed10..4ffb0c3a84e 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs @@ -2,8 +2,6 @@ namespace NServiceBus; -using System; -using System.Collections.Generic; using Configuration.AdvancedExtensibility; using Persistence; using Settings; @@ -14,17 +12,12 @@ namespace NServiceBus; /// /// The persister definition eg , etc. /// The storage type. -public class PersistenceExtensions : PersistenceExtensions +/// +/// Initializes a new instance of . +/// +public class PersistenceExtensions(SettingsHolder settings) : PersistenceExtensions(settings, StorageType.Get()) where T : PersistenceDefinition, IPersistenceDefinitionFactory - where S : StorageType -{ - /// - /// Initializes a new instance of . - /// - public PersistenceExtensions(SettingsHolder settings) : base(settings, StorageType.Get()) - { - } -} + where S : StorageType; /// /// This class provides implementers of persisters with an extension mechanism for custom settings via extension @@ -41,17 +34,13 @@ public partial class PersistenceExtensions : ExposeSettings { } - /// - /// Constructor for a specific . - /// - protected PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null) : base(settings) + internal PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null) : base(settings) { - var registry = settings - .GetOrCreate() - .Enable(settings); + var registry = settings.GetOrCreate(); + var enablePersistence = registry.Enable(settings); if (storageType is not null) { - registry.WithStorage(storageType); + enablePersistence.WithStorage(storageType); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index 5ddfa74f5a5..5bec21baf2e 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -1,3 +1,5 @@ +#nullable enable + namespace NServiceBus; using System; @@ -21,12 +23,12 @@ public EnableBuilder Enable(SettingsHolder settings) return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this, settings); } - public IReadOnlyCollection Merge() + public IReadOnlyCollection Merge() { IEnumerable EnabledStorages)>> reversedDefinitions = definitions.Reverse(); var availableStorages = new List(StorageType.GetAvailableStorageTypes()); - var mergedEnabledPersistences = new List(); + var enabledPersistences = new List(); foreach (var (_, (persistenceDefinition, enabledStorages)) in reversedDefinitions) { @@ -47,11 +49,11 @@ public IReadOnlyCollection Merge() if (selectedStorages.Count != 0) { - mergedEnabledPersistences.Add(new MergedPersistence(selectedStorages, persistenceDefinition)); + enabledPersistences.Add(new EnabledPersistence(selectedStorages, persistenceDefinition)); } } - return mergedEnabledPersistences; + return enabledPersistences; } void EnableStorageFor(SettingsHolder settings, StorageType storage) diff --git a/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs b/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs deleted file mode 100644 index b5d39036ce3..00000000000 --- a/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NServiceBus; - -using System.Collections.Generic; -using Persistence; -using Settings; - -static class PersistenceStorageMerger -{ - public static List MergePersistences(this SettingsHolder settings, List definitions) - { - definitions.Reverse(); - - var availableStorages = new List(StorageType.GetAvailableStorageTypes()); - var mergedEnabledPersistences = new List(); - - foreach (var definition in definitions) - { - var persistenceDefinition = definition.DefinitionType.Construct(); - var supportedStorages = persistenceDefinition.GetSupportedStorages(definition.SelectedStorages); - - var currentDefinition = new EnabledPersistence - { - DefinitionType = definition.DefinitionType, - SelectedStorages = [] - }; - - foreach (var storageType in supportedStorages) - { - if (availableStorages.Contains(storageType)) - { - currentDefinition.SelectedStorages.Add(storageType); - availableStorages.Remove(storageType); - persistenceDefinition.ApplyActionForStorage(storageType, settings); - } - } - - if (currentDefinition.SelectedStorages.Count != 0) - { - mergedEnabledPersistences.Add(currentDefinition); - } - } - - return mergedEnabledPersistences; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/StorageType.cs b/src/NServiceBus.Core/Persistence/StorageType.cs index 93f60eb222a..7caaf0055ea 100644 --- a/src/NServiceBus.Core/Persistence/StorageType.cs +++ b/src/NServiceBus.Core/Persistence/StorageType.cs @@ -30,12 +30,12 @@ public override bool Equals(object obj) internal static IReadOnlyCollection GetAvailableStorageTypes() => [Subscriptions.Instance, Sagas.Instance, Outbox.Instance]; - internal static StorageType Get() where TStorageType : notnull, StorageType => typeof(TStorageType) switch + internal static StorageType Get() where TStorage : notnull, StorageType => typeof(TStorage) switch { { } t when t == typeof(Subscriptions) => Subscriptions.Instance, { } t when t == typeof(Sagas) => Sagas.Instance, { } t when t == typeof(Outbox) => Outbox.Instance, - _ => throw new InvalidOperationException($"The storage type '{typeof(TStorageType)}' is not supported.") + _ => throw new InvalidOperationException($"The storage type '{typeof(TStorage)}' is not supported.") }; readonly string storage; diff --git a/src/NServiceBus.Core/obsoletes-v10.cs b/src/NServiceBus.Core/obsoletes-v10.cs index fef58a91ba0..c5ac6838d08 100644 --- a/src/NServiceBus.Core/obsoletes-v10.cs +++ b/src/NServiceBus.Core/obsoletes-v10.cs @@ -468,4 +468,18 @@ public class DataBus } } +namespace NServiceBus.Persistence +{ + using System; + using Particular.Obsoletes; + using Settings; + + public partial class PersistenceDefinition + { + [ObsoleteMetadata(ReplacementTypeOrMember = "Supports()", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] + [Obsolete("Use 'Supports()' instead. Will be removed in version 11.0.0.", true)] + protected void Supports(Action action) where T : StorageType => throw new NotImplementedException(); + } +} + #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member \ No newline at end of file From 740eb31c1bb6ac03b9397a77d1583236d48f102e Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 17:51:11 +0200 Subject: [PATCH 10/25] Removing the settings holder for now because it allows us to only apply defaults for actually used persistences at a deterministic time --- .../AcceptanceTestingPersistence.cs | 2 +- ...enabled_without_persister_supporting_it.cs | 2 +- ...ot_provide_synchronized_storage_session.cs | 2 +- ...hen_configuring_subscription_authorizer.cs | 2 +- .../Pub_from_sendonly.cs | 2 +- .../Persistence/PersistenceExtensionsTests.cs | 4 ++-- ...gerTests.cs => PersistenceMergingTests.cs} | 9 ++++--- .../Persistence/PersistenceStartupTests.cs | 4 ++-- .../IPersistenceDefinitionFactory.cs | 4 +--- .../Learning/LearningPersistence.cs | 3 +-- .../Persistence/PersistenceDefinition.cs | 14 +---------- .../Persistence/PersistenceExtensions.cs | 2 +- .../Persistence/PersistenceRegistry.cs | 24 +++++++++---------- src/NServiceBus.Core/obsoletes-v10.cs | 4 ++++ 14 files changed, 32 insertions(+), 46 deletions(-) rename src/NServiceBus.Core.Tests/Persistence/{PersistenceStorageMergerTests.cs => PersistenceMergingTests.cs} (92%) diff --git a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs index 5024e26aec7..ad35410c79f 100644 --- a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs +++ b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs @@ -13,5 +13,5 @@ public class AcceptanceTestingPersistence : PersistenceDefinition, IPersistenceD Supports(); } - public static AcceptanceTestingPersistence Create(SettingsHolder settingsHolder) => new(); + public static AcceptanceTestingPersistence Create() => new(); } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs index 998e0771917..65119d97fab 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs @@ -35,6 +35,6 @@ public Endpoint() => class FakeNoOutboxSupportPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { - public static FakeNoOutboxSupportPersistence Create(SettingsHolder settings) => new(); + public static FakeNoOutboxSupportPersistence Create() => new(); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs index 12e0a3252ca..2ad287a5e9a 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs @@ -34,7 +34,7 @@ class FakeNoSynchronizedStorageSupportPersistence : PersistenceDefinition, IPers Supports(); } - public static FakeNoSynchronizedStorageSupportPersistence Create(SettingsHolder settings) => new(); + public static FakeNoSynchronizedStorageSupportPersistence Create() => new(); sealed class FakeStorage : Feature { diff --git a/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs b/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs index a033a2cbdca..b7e2a95ab36 100644 --- a/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs +++ b/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs @@ -137,6 +137,6 @@ public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscribe public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context, CancellationToken cancellationToken = default) => throw new NotImplementedException(); } - public static FakePersistence Create(SettingsHolder settings) => new(); + public static FakePersistence Create() => new(); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs index b4cfe914738..e1ab956d9af 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Routing/MessageDrivenSubscriptions/Pub_from_sendonly.cs @@ -71,7 +71,7 @@ public class HardCodedPersistence : PersistenceDefinition, IPersistenceDefinitio { HardCodedPersistence() => Supports(); - public static HardCodedPersistence Create(SettingsHolder settings) => new(); + public static HardCodedPersistence Create() => new(); } class HardCodedPersistenceFeature : Feature diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs index b2b8036d7e4..06a68d03d0c 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs @@ -20,7 +20,7 @@ public class PartialPersistence : PersistenceDefinition, IPersistenceDefinitionF { PartialPersistence() => Supports(); - public static PartialPersistence Create(SettingsHolder settings) => new(); + public static PartialPersistence Create() => new(); class FakeSubscriptionStorage : Feature { @@ -42,7 +42,7 @@ public class PartialPersistence : PersistenceDefinition, IPersistenceDefinitionF { public PartialPersistence() => Supports(); - public static PartialPersistence Create(SettingsHolder settings) => new(); + public static PartialPersistence Create() => new(); class FakeSubscriptionStorage : Feature { diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceMergingTests.cs similarity index 92% rename from src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs rename to src/NServiceBus.Core.Tests/Persistence/PersistenceMergingTests.cs index 435f1f83ace..2d4354c9b30 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceMergingTests.cs @@ -4,7 +4,6 @@ using NServiceBus.Features; using NServiceBus.Persistence; using NUnit.Framework; -using Settings; [TestFixture] public class When_no_storage_persistence_overrides_are_enabled @@ -34,7 +33,7 @@ class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory(); } - public static FakePersistence Create(SettingsHolder settings) => new(); + public static FakePersistence Create() => new(); class FakeStorage : Feature { @@ -73,7 +72,7 @@ class FakePersistence2 : PersistenceDefinition, IPersistenceDefinitionFactory(); } - public static FakePersistence2 Create(SettingsHolder settings) => new(); + public static FakePersistence2 Create() => new(); class FakeStorage : Feature { @@ -90,7 +89,7 @@ class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory(); } - public static FakePersistence Create(SettingsHolder settings) => new(); + public static FakePersistence Create() => new(); class FakeStorage : Feature { @@ -122,7 +121,7 @@ class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory(); } - public static FakePersistence Create(SettingsHolder settings) => new(); + public static FakePersistence Create() => new(); class FakeStorage : Feature { diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs index 8a6c71ffcae..dc6e39f5e7f 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs @@ -63,7 +63,7 @@ public void Should_not_prevent_using_different_persistence_for_sagas_and_outbox_ class FakeSagaPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { FakeSagaPersistence() => Supports(); - public static FakeSagaPersistence Create(SettingsHolder settings) => new(); + public static FakeSagaPersistence Create() => new(); class FakeStorage : Feature { @@ -74,7 +74,7 @@ class FakeStorage : Feature class FakeOutboxPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { FakeOutboxPersistence() => Supports(); - public static FakeOutboxPersistence Create(SettingsHolder settings) => new(); + public static FakeOutboxPersistence Create() => new(); class FakeStorage : Feature { diff --git a/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs b/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs index 7715e114ff7..e9f02f31fea 100644 --- a/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs +++ b/src/NServiceBus.Core/Persistence/IPersistenceDefinitionFactory.cs @@ -2,8 +2,6 @@ namespace NServiceBus.Persistence; -using Settings; - /// /// Defines a factory for creating persistence definitions used in . /// @@ -15,5 +13,5 @@ public interface IPersistenceDefinitionFactory /// Creates the persistence definition. /// /// The persistence definition. - static abstract TDefinition Create(SettingsHolder settings); + static abstract TDefinition Create(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index 945c11fbb31..00e425acd4b 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -2,7 +2,6 @@ using Features; using Persistence; -using Settings; /// /// Used to enable Learning persistence. @@ -14,5 +13,5 @@ public class LearningPersistence : PersistenceDefinition, IPersistenceDefinition /// /// Creates the learning persistence definition. /// - public static LearningPersistence Create(SettingsHolder settings) => new(); + public static LearningPersistence Create() => new(); } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index 3092b4edc93..f92093eb114 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -38,22 +38,10 @@ protected void Defaults(Action action) /// /// True if supplied storage is supported. /// - public bool HasSupportFor() where T : StorageType => storageToFeatureMap.ContainsKey(StorageType.Get()); + public bool HasSupportFor() where T : StorageType => HasSupportFor(StorageType.Get()); internal bool HasSupportFor(StorageType storageType) => storageToFeatureMap.ContainsKey(storageType); - /// - /// True if supplied storage is supported. - /// -#pragma warning disable CA1822 - public bool HasSupportFor(Type storageType) -#pragma warning restore CA1822 - { - // TODO - ArgumentNullException.ThrowIfNull(storageType); - return false; - } - internal void ApplyActionForStorage(StorageType storageType, SettingsHolder settings) { var featureSupportingStorage = storageToFeatureMap[storageType]; diff --git a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs index 4ffb0c3a84e..a06389b4b26 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs @@ -37,7 +37,7 @@ public partial class PersistenceExtensions : ExposeSettings internal PersistenceExtensions(SettingsHolder settings, StorageType? storageType = null) : base(settings) { var registry = settings.GetOrCreate(); - var enablePersistence = registry.Enable(settings); + var enablePersistence = registry.Enable(); if (storageType is not null) { enablePersistence.WithStorage(storageType); diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index 5bec21baf2e..49e07966883 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -6,21 +6,20 @@ namespace NServiceBus; using System.Collections.Generic; using System.Linq; using Persistence; -using Settings; sealed class PersistenceRegistry { - public EnableBuilder Enable(SettingsHolder settings) + public EnableBuilder Enable() where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { if (definitions.TryGetValue(typeof(TDefinition), out var persistenceAndStorageTypes)) { - return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this, settings); + return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this); } - var definition = TDefinition.Create(settings); + var definition = TDefinition.Create(); definitions.Add(typeof(TDefinition), (definition, [])); - return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this, settings); + return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this); } public IReadOnlyCollection Merge() @@ -56,10 +55,10 @@ public IReadOnlyCollection Merge() return enabledPersistences; } - void EnableStorageFor(SettingsHolder settings, StorageType storage) + void EnableStorageFor(StorageType storage) where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { - var builder = Enable(settings); + var builder = Enable(); if (!builder.Persistence.HasSupportFor(storage)) { @@ -73,24 +72,23 @@ void EnableStorageFor(SettingsHolder settings, StorageType storage) } } - void EnableStorageFor(SettingsHolder settings) + void EnableStorageFor() where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory where TStorage : StorageType => - EnableStorageFor(settings, StorageType.Get()); + EnableStorageFor(StorageType.Get()); public class EnableBuilder( TDefinition definition, - PersistenceRegistry registry, - SettingsHolder settings) + PersistenceRegistry registry) where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { public TDefinition Persistence { get; } = definition; public void WithStorage() where TStorage : StorageType => - registry.EnableStorageFor(settings); + registry.EnableStorageFor(); - public void WithStorage(StorageType storageType) => registry.EnableStorageFor(settings, storageType); + public void WithStorage(StorageType storageType) => registry.EnableStorageFor(storageType); } readonly Dictionary EnabledStorageTypes)> definitions = []; diff --git a/src/NServiceBus.Core/obsoletes-v10.cs b/src/NServiceBus.Core/obsoletes-v10.cs index c5ac6838d08..dc129eeea3d 100644 --- a/src/NServiceBus.Core/obsoletes-v10.cs +++ b/src/NServiceBus.Core/obsoletes-v10.cs @@ -479,6 +479,10 @@ public partial class PersistenceDefinition [ObsoleteMetadata(ReplacementTypeOrMember = "Supports()", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] [Obsolete("Use 'Supports()' instead. Will be removed in version 11.0.0.", true)] protected void Supports(Action action) where T : StorageType => throw new NotImplementedException(); + + [ObsoleteMetadata(ReplacementTypeOrMember = "HasSupportFor()", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] + [Obsolete("Use 'HasSupportFor()' instead. Will be removed in version 11.0.0.", true)] + public bool HasSupportFor(Type storageType) => throw new NotImplementedException(); } } From 853863f0e278615b218dad62b8915dc2b558a2fb Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 17:53:38 +0200 Subject: [PATCH 11/25] Approve API changes --- ...IApprovals.ApproveNServiceBus.approved.txt | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt index a569d08c7ba..5d018c18101 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt @@ -544,7 +544,10 @@ namespace NServiceBus { public static void EnableInstallers(this NServiceBus.EndpointConfiguration config, string? username = null) { } } - public class LearningPersistence : NServiceBus.Persistence.PersistenceDefinition { } + public class LearningPersistence : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory + { + public static NServiceBus.LearningPersistence Create() { } + } public static class LearningSagaPersisterConfigurationExtensions { public static void SagaStorageDirectory(this NServiceBus.PersistenceExtensions persistenceExtensions, string path) { } @@ -668,25 +671,29 @@ namespace NServiceBus } public static class PersistenceConfig { + [System.Obsolete("Use \'UsePersistence\' instead. Will be removed in version 11.0.0.", true)] public static NServiceBus.PersistenceExtensions UsePersistence(this NServiceBus.EndpointConfiguration config, System.Type definitionType) { } public static NServiceBus.PersistenceExtensions UsePersistence(this NServiceBus.EndpointConfiguration config) - where T : NServiceBus.Persistence.PersistenceDefinition { } + where T : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory { } public static NServiceBus.PersistenceExtensions UsePersistence(this NServiceBus.EndpointConfiguration config) - where T : NServiceBus.Persistence.PersistenceDefinition + where T : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory where S : NServiceBus.StorageType { } } + [System.Obsolete("Use \'PersistenceExtensions\' instead. Will be removed in version 11.0.0.", true)] public class PersistenceExtensions : NServiceBus.Configuration.AdvancedExtensibility.ExposeSettings { public PersistenceExtensions(System.Type definitionType, NServiceBus.Settings.SettingsHolder settings, System.Type storageType) { } } - public class PersistenceExtensions : NServiceBus.PersistenceExtensions - where T : NServiceBus.Persistence.PersistenceDefinition + public class PersistenceExtensions : NServiceBus.Configuration.AdvancedExtensibility.ExposeSettings + where T : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory { public PersistenceExtensions(NServiceBus.Settings.SettingsHolder settings) { } + [System.Obsolete("Use \'PersistenceExtensions(SettingsHolder settings, StorageType? storageType = nu" + + "ll)\' instead. Will be removed in version 11.0.0.", true)] protected PersistenceExtensions(NServiceBus.Settings.SettingsHolder settings, System.Type storageType) { } } public class PersistenceExtensions : NServiceBus.PersistenceExtensions - where T : NServiceBus.Persistence.PersistenceDefinition + where T : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory where S : NServiceBus.StorageType { public PersistenceExtensions(NServiceBus.Settings.SettingsHolder settings) { } @@ -913,6 +920,8 @@ namespace NServiceBus } public abstract class StorageType { + public override bool Equals(object obj) { } + public override int GetHashCode() { } public override string ToString() { } public sealed class Outbox : NServiceBus.StorageType { } public sealed class Sagas : NServiceBus.StorageType { } @@ -1557,16 +1566,26 @@ namespace NServiceBus.Persistence System.Threading.Tasks.ValueTask TryOpen(NServiceBus.Outbox.IOutboxTransaction transaction, NServiceBus.Extensibility.ContextBag context, System.Threading.CancellationToken cancellationToken = default); System.Threading.Tasks.ValueTask TryOpen(NServiceBus.Transport.TransportTransaction transportTransaction, NServiceBus.Extensibility.ContextBag context, System.Threading.CancellationToken cancellationToken = default); } + public interface IPersistenceDefinitionFactory + where out TDefinition : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory + { + TDefinition Create(); + } public interface ISynchronizedStorageSession { } public abstract class PersistenceDefinition { protected PersistenceDefinition() { } protected void Defaults(System.Action action) { } + [System.Obsolete("Use \'HasSupportFor()\' instead. Will be removed in version 11.0.0.", true)] public bool HasSupportFor(System.Type storageType) { } public bool HasSupportFor() where T : NServiceBus.StorageType { } + [System.Obsolete("Use \'Supports()\' instead. Will be removed in version 11.0.0.", true)] protected void Supports(System.Action action) where T : NServiceBus.StorageType { } + protected void Supports() + where TStorage : NServiceBus.StorageType + where TFeature : NServiceBus.Features.Feature { } } } namespace NServiceBus.Pipeline From 4e712b9b3e37ed164a13a33acc6f763021375bf3 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 18:03:16 +0200 Subject: [PATCH 12/25] Nullable --- ...ot_provide_synchronized_storage_session.cs | 5 +++-- ...IApprovals.ApproveNServiceBus.approved.txt | 2 +- ...otations.ApproveNullableTypes.approved.txt | 6 ------ .../Persistence/EnabledPersistence.cs | 4 +++- .../Learning/LearningPersistence.cs | 4 +++- .../Learning/LearningSynchronizedStorage.cs | 2 ++ .../LearningSynchronizedStorageSession.cs | 6 ++++-- ...ingSagaPersisterConfigurationExtensions.cs | 4 +++- .../Learning/SagaPersister/SagaStorageFile.cs | 20 +++++++++++++------ .../Persistence/PersistenceComponent.cs | 4 +++- .../Persistence/PersistenceDefinition.cs | 6 ++++-- .../Persistence/StorageType.cs | 8 +++++--- 12 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs index 2ad287a5e9a..fc57931e4a7 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_synchronized_storage_session.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection; using NServiceBus.Persistence; using NUnit.Framework; -using Settings; using Unicast.Subscriptions; using Unicast.Subscriptions.MessageDrivenSubscriptions; @@ -38,7 +37,9 @@ class FakeNoSynchronizedStorageSupportPersistence : PersistenceDefinition, IPers sealed class FakeStorage : Feature { - protected override void Setup(FeatureConfigurationContext context) => throw new System.NotImplementedException(); + protected override void Setup(FeatureConfigurationContext context) + { + } } } diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt index 5d018c18101..4b82db8ef89 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt @@ -920,7 +920,7 @@ namespace NServiceBus } public abstract class StorageType { - public override bool Equals(object obj) { } + public override bool Equals(object? obj) { } public override int GetHashCode() { } public override string ToString() { } public sealed class Outbox : NServiceBus.StorageType { } diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/NullableAnnotations.ApproveNullableTypes.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/NullableAnnotations.ApproveNullableTypes.approved.txt index d9b35fb0ffe..9220358c2fd 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/NullableAnnotations.ApproveNullableTypes.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/NullableAnnotations.ApproveNullableTypes.approved.txt @@ -52,8 +52,6 @@ NServiceBus.ImmediateRetry NServiceBus.IPipelineContext NServiceBus.IStartableEndpointWithExternallyManagedContainer NServiceBus.IToSagaExpression`1 -NServiceBus.LearningPersistence -NServiceBus.LearningSagaPersisterConfigurationExtensions NServiceBus.LearningTransport NServiceBus.LearningTransportConfigurationExtensions NServiceBus.LoadMessageHandlersExtensions @@ -80,10 +78,6 @@ NServiceBus.OutboxConfigExtensions NServiceBus.PendingTransportOperations NServiceBus.Persistence.CompletableSynchronizedStorageSessionExtensions NServiceBus.Persistence.ICompletableSynchronizedStorageSession -NServiceBus.Persistence.PersistenceDefinition -NServiceBus.PersistenceExtensions -NServiceBus.PersistenceExtensions`1 -NServiceBus.PersistenceExtensions`2 NServiceBus.Pipeline.ForkConnector`2 NServiceBus.Pipeline.IAuditActionContext NServiceBus.Pipeline.IAuditContext diff --git a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs index e900a4fd1c9..0d12fb179c4 100644 --- a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs +++ b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs @@ -1,4 +1,6 @@ -namespace NServiceBus; +#nullable enable + +namespace NServiceBus; using System.Collections.Generic; using Persistence; diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index 00e425acd4b..bba6eb8e012 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -1,4 +1,6 @@ -namespace NServiceBus; +#nullable enable + +namespace NServiceBus; using Features; using Persistence; diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorage.cs b/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorage.cs index 053de1d97a5..01b788ed738 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorage.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorage.cs @@ -1,3 +1,5 @@ +#nullable enable + namespace NServiceBus; using Features; diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs b/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs index 483ea8c212e..d719c23ec6d 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs @@ -1,3 +1,5 @@ +#nullable enable + namespace NServiceBus; using System; @@ -61,7 +63,7 @@ public async Task CompleteAsync(CancellationToken cancellationToken = default) deferredActions.Clear(); } - public async Task Read(Guid sagaId, SagaManifestCollection sagaManifests, CancellationToken cancellationToken = default) where TSagaData : class, IContainSagaData + public async Task Read(Guid sagaId, SagaManifestCollection sagaManifests, CancellationToken cancellationToken = default) where TSagaData : class, IContainSagaData { var sagaStorageFile = await Open(sagaId, typeof(TSagaData), sagaManifests, cancellationToken) .ConfigureAwait(false); @@ -81,7 +83,7 @@ public async Task Read(Guid sagaId, SagaManifestCollection public void Complete(IContainSagaData sagaData, SagaManifestCollection sagaManifests) => deferredActions.Add(new CompleteAction(sagaData, sagaFiles, sagaManifests)); - async Task Open(Guid sagaId, Type entityType, SagaManifestCollection sagaManifests, CancellationToken cancellationToken) + async Task Open(Guid sagaId, Type entityType, SagaManifestCollection sagaManifests, CancellationToken cancellationToken) { var sagaManifest = sagaManifests.GetForEntityType(entityType); diff --git a/src/NServiceBus.Core/Persistence/Learning/SagaPersister/LearningSagaPersisterConfigurationExtensions.cs b/src/NServiceBus.Core/Persistence/Learning/SagaPersister/LearningSagaPersisterConfigurationExtensions.cs index beb1542a419..dd0c9579d31 100644 --- a/src/NServiceBus.Core/Persistence/Learning/SagaPersister/LearningSagaPersisterConfigurationExtensions.cs +++ b/src/NServiceBus.Core/Persistence/Learning/SagaPersister/LearningSagaPersisterConfigurationExtensions.cs @@ -1,4 +1,6 @@ -namespace NServiceBus; +#nullable enable + +namespace NServiceBus; using System; using Features; diff --git a/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs b/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs index 86724c170e3..253df6a8213 100644 --- a/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs +++ b/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs @@ -1,3 +1,5 @@ +#nullable enable + namespace NServiceBus; using System; @@ -47,7 +49,7 @@ public async ValueTask DisposeAsync() fileStream = null; } - public static Task Open(Guid sagaId, SagaManifest manifest, CancellationToken cancellationToken = default) + public static Task Open(Guid sagaId, SagaManifest manifest, CancellationToken cancellationToken = default) { var filePath = manifest.GetFilePath(sagaId); @@ -56,7 +58,7 @@ public static Task Open(Guid sagaId, SagaManifest manifest, Can return noSagaFoundResult; } - return OpenWithRetryOnConcurrency(filePath, FileMode.Open, cancellationToken); + return OpenWithRetryOnConcurrency(filePath, FileMode.Open, cancellationToken)!; } public static Task Create(Guid sagaId, SagaManifest manifest, CancellationToken cancellationToken = default) @@ -96,6 +98,8 @@ await Task.Delay(100, cancellationToken) public async Task Write(IContainSagaData sagaData, CancellationToken cancellationToken = default) { + ObjectDisposedException.ThrowIf(fileStream is null, this); + fileStream.Position = 0; await JsonSerializer.SerializeAsync(fileStream, sagaData, sagaData.GetType(), Options, cancellationToken) .ConfigureAwait(false); @@ -107,14 +111,18 @@ await JsonSerializer.SerializeAsync(fileStream, sagaData, sagaData.GetType(), Op public void MarkAsCompleted() => isCompleted = true; - public ValueTask Read(CancellationToken cancellationToken = default) where TSagaData : class, IContainSagaData - => JsonSerializer.DeserializeAsync(fileStream, Options, cancellationToken); + public ValueTask Read(CancellationToken cancellationToken = default) where TSagaData : class, IContainSagaData + { + ObjectDisposedException.ThrowIf(fileStream is null, this); + + return JsonSerializer.DeserializeAsync(fileStream, Options, cancellationToken); + } - FileStream fileStream; + FileStream? fileStream; bool isCompleted; const int DefaultBufferSize = 4096; - static readonly Task noSagaFoundResult = Task.FromResult(null); + static readonly Task noSagaFoundResult = Task.FromResult(null); static readonly JsonSerializerOptions Options = new() { diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index 56c5dc6e4ac..b43d1a04bc4 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -1,4 +1,6 @@ -namespace NServiceBus; +#nullable enable + +namespace NServiceBus; using System; using System.Collections.Generic; diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index f92093eb114..a45e6b73b8f 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -1,4 +1,6 @@ -namespace NServiceBus.Persistence; +#nullable enable + +namespace NServiceBus.Persistence; using System; using System.Collections.Generic; @@ -18,7 +20,7 @@ protected void Supports() where TFeature : Feature { var storageType = StorageType.Get(); - if (storageToFeatureMap.TryGetValue(storageType, out Type supportedStorageType)) + if (storageToFeatureMap.TryGetValue(storageType, out var supportedStorageType)) { throw new Exception($"Storage {typeof(TStorage)} is already supported by {supportedStorageType}"); } diff --git a/src/NServiceBus.Core/Persistence/StorageType.cs b/src/NServiceBus.Core/Persistence/StorageType.cs index 7caaf0055ea..ebf3b906b6f 100644 --- a/src/NServiceBus.Core/Persistence/StorageType.cs +++ b/src/NServiceBus.Core/Persistence/StorageType.cs @@ -1,4 +1,6 @@ -namespace NServiceBus; +#nullable enable + +namespace NServiceBus; using System; using System.Collections.Generic; @@ -14,7 +16,7 @@ public abstract class StorageType public override string ToString() => storage; /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is StorageType other) { @@ -25,7 +27,7 @@ public override bool Equals(object obj) } /// - public override int GetHashCode() => storage != null ? storage.GetHashCode() : 0; + public override int GetHashCode() => storage.GetHashCode(); internal static IReadOnlyCollection GetAvailableStorageTypes() => [Subscriptions.Instance, Sagas.Instance, Outbox.Instance]; From 1f494fe11ea590c268c7ba3e65cdbb2ed7702d51 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 19:05:27 +0200 Subject: [PATCH 13/25] Simplify registry --- .../Persistence/PersistenceExtensionsTests.cs | 2 +- .../Persistence/PersistenceRegistry.cs | 61 ++++++++----------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs index 06a68d03d0c..6028005a2e0 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs @@ -13,7 +13,7 @@ public class When_configuring_storage_type_not_supported_by_persistence public void Should_throw_exception() { var ex = Assert.Throws(() => new PersistenceExtensions(new SettingsHolder())); - Assert.That(ex.Message, Does.StartWith("PartialPersistence does not support storage type Sagas.")); + Assert.That(ex.Message, Does.StartWith("PartialPersistence does not support storage type 'Sagas'.")); } public class PartialPersistence : PersistenceDefinition, IPersistenceDefinitionFactory diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index 49e07966883..153e42191fa 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -12,26 +12,26 @@ sealed class PersistenceRegistry public EnableBuilder Enable() where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { - if (definitions.TryGetValue(typeof(TDefinition), out var persistenceAndStorageTypes)) + if (definitions.TryGetValue(typeof(TDefinition), out var builder)) { - return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this); + return (EnableBuilder)builder; } - var definition = TDefinition.Create(); - definitions.Add(typeof(TDefinition), (definition, [])); - return new EnableBuilder((TDefinition)persistenceAndStorageTypes.Definition, this); + var strongBuilder = new EnableBuilder(); + definitions.Add(typeof(TDefinition), strongBuilder); + return strongBuilder; } public IReadOnlyCollection Merge() { - IEnumerable EnabledStorages)>> reversedDefinitions = definitions.Reverse(); + IEnumerable> builtPersistences = definitions.Reverse(); var availableStorages = new List(StorageType.GetAvailableStorageTypes()); var enabledPersistences = new List(); - foreach (var (_, (persistenceDefinition, enabledStorages)) in reversedDefinitions) + foreach (var (_, builtPersistence) in builtPersistences) { - var supportedStorages = persistenceDefinition.GetSupportedStorages(enabledStorages); + var supportedStorages = builtPersistence.Persistence.GetSupportedStorages(builtPersistence.EnabledStorageTypes); var selectedStorages = new List(); @@ -48,48 +48,41 @@ public IReadOnlyCollection Merge() if (selectedStorages.Count != 0) { - enabledPersistences.Add(new EnabledPersistence(selectedStorages, persistenceDefinition)); + enabledPersistences.Add(new EnabledPersistence(selectedStorages, builtPersistence.Persistence)); } } return enabledPersistences; } - void EnableStorageFor(StorageType storage) + public class EnableBuilder : IEnabledBuilder where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { - var builder = Enable(); + public PersistenceDefinition Persistence { get; } = TDefinition.Create(); - if (!builder.Persistence.HasSupportFor(storage)) - { - throw new Exception($"{typeof(TDefinition).Name} does not support storage type {storage.GetType().Name}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); - } + public IReadOnlyCollection EnabledStorageTypes => enabledStorageTypes; + + public void WithStorage() where TStorage : StorageType => WithStorage(StorageType.Get()); - var (_, enabledStorageTypes) = definitions[typeof(TDefinition)]; - if (!enabledStorageTypes.Contains(storage)) + public void WithStorage(StorageType storageType) { - enabledStorageTypes.Add(storage); + if (!Persistence.HasSupportFor(storageType)) + { + throw new Exception($"{typeof(TDefinition).Name} does not support storage type '{storageType}'. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); + } + + enabledStorageTypes.Add(storageType); } - } - void EnableStorageFor() - where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory - where TStorage : StorageType => - EnableStorageFor(StorageType.Get()); + readonly HashSet enabledStorageTypes = []; + } - public class EnableBuilder( - TDefinition definition, - PersistenceRegistry registry) - where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory + interface IEnabledBuilder { - public TDefinition Persistence { get; } = definition; - - public void WithStorage() - where TStorage : StorageType => - registry.EnableStorageFor(); + PersistenceDefinition Persistence { get; } - public void WithStorage(StorageType storageType) => registry.EnableStorageFor(storageType); + IReadOnlyCollection EnabledStorageTypes { get; } } - readonly Dictionary EnabledStorageTypes)> definitions = []; + readonly Dictionary definitions = []; } \ No newline at end of file From c2d64d7a686d9cc142dad974f653f1786365f05d Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 19:10:33 +0200 Subject: [PATCH 14/25] Remove constraint --- src/NServiceBus.Core/Persistence/StorageType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/Persistence/StorageType.cs b/src/NServiceBus.Core/Persistence/StorageType.cs index ebf3b906b6f..346db27034b 100644 --- a/src/NServiceBus.Core/Persistence/StorageType.cs +++ b/src/NServiceBus.Core/Persistence/StorageType.cs @@ -32,7 +32,7 @@ public override bool Equals(object? obj) internal static IReadOnlyCollection GetAvailableStorageTypes() => [Subscriptions.Instance, Sagas.Instance, Outbox.Instance]; - internal static StorageType Get() where TStorage : notnull, StorageType => typeof(TStorage) switch + internal static StorageType Get() where TStorage : StorageType => typeof(TStorage) switch { { } t when t == typeof(Subscriptions) => Subscriptions.Instance, { } t when t == typeof(Sagas) => Sagas.Instance, From b9ffe7d790eaf99818558859fdc6d623c89248f7 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 19:26:08 +0200 Subject: [PATCH 15/25] Static --- src/NServiceBus.Core/Utils/FileVersionRetriever.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/Utils/FileVersionRetriever.cs b/src/NServiceBus.Core/Utils/FileVersionRetriever.cs index 9add7cacb97..581bba0cf34 100644 --- a/src/NServiceBus.Core/Utils/FileVersionRetriever.cs +++ b/src/NServiceBus.Core/Utils/FileVersionRetriever.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Reflection; -class FileVersionRetriever +static class FileVersionRetriever { public static string GetFileVersion(Type type) => GetFileVersion(type.Assembly); From b4291ad34864e34b9295e13035b82c0dcd45d5f1 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 19:27:31 +0200 Subject: [PATCH 16/25] Address review feedback --- .../Persistence/PersistenceComponent.cs | 19 +++++++++---------- .../Persistence/PersistenceDefinition.cs | 3 +++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index b43d1a04bc4..c03b577210b 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -30,17 +30,16 @@ public static void ConfigurePersistence(this SettingsHolder settings) var persistenceDefinition = enabledPersistence.Definition; persistenceDefinition.ApplyDefaults(settings); - var definitionType = persistenceDefinition.GetType(); foreach (var storageType in enabledPersistence.SelectedStorages) { - Logger.DebugFormat("Activating persistence '{0}' to provide storage for '{1}' storage.", definitionType.Name, storageType); + Logger.DebugFormat("Activating persistence '{0}' to provide storage for '{1}' storage.", persistenceDefinition.Name, storageType); persistenceDefinition.ApplyActionForStorage(storageType, settings); resultingSupportedStorages.Add(storageType); diagnostics.Add(storageType.ToString(), new { - Type = definitionType.FullName, - Version = FileVersionRetriever.GetFileVersion(definitionType) + Type = persistenceDefinition.FullName, + Version = FileVersionRetriever.GetFileVersion(persistenceDefinition.GetType()) }); } } @@ -52,16 +51,16 @@ public static void ConfigurePersistence(this SettingsHolder settings) static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, IReadOnlyCollection enabledPersistences) { - var sagaPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Sagas.Instance)); - var outboxPersisterType = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Outbox.Instance)); + var sagaPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Sagas.Instance))?.Definition; + var outboxPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Outbox.Instance))?.Definition; var bothFeaturesEnabled = settings.IsFeatureEnabled(typeof(Features.Sagas)) && settings.IsFeatureEnabled(typeof(Features.Outbox)); - if (sagaPersisterType != null - && outboxPersisterType != null - && sagaPersisterType.Definition != outboxPersisterType.Definition + if (sagaPersisterDefinition != null + && outboxPersisterDefinition != null + && sagaPersisterDefinition != outboxPersisterDefinition && bothFeaturesEnabled) { - throw new Exception($"Sagas and the Outbox need to use the same type of persistence. Saga persistence is configured to use {sagaPersisterType.Definition.GetType().Name}. Outbox persistence is configured to use {outboxPersisterType.GetType().Name}."); + throw new Exception($"Sagas and the Outbox need to use the same type of persistence. Saga persistence is configured to use '{sagaPersisterDefinition.Name}'. Outbox persistence is configured to use '{outboxPersisterDefinition.Name}'."); } } diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index a45e6b73b8f..3bbf2eb881d 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -42,6 +42,9 @@ protected void Defaults(Action action) /// public bool HasSupportFor() where T : StorageType => HasSupportFor(StorageType.Get()); + internal string Name => GetType().Name; + internal string FullName => GetType().FullName ?? Name; + internal bool HasSupportFor(StorageType storageType) => storageToFeatureMap.ContainsKey(storageType); internal void ApplyActionForStorage(StorageType storageType, SettingsHolder settings) From aefbeb3288b3ac81a10b498b873d531e8186ffb3 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Thu, 16 Oct 2025 19:44:36 +0200 Subject: [PATCH 17/25] Revert "Obsolete some no longer needed APIs" messup This reverts commit 4198d29d767f70c027f31a9445ace5d468207964. --- src/NServiceBus.Core/obsoletes-v10.cs | 144 +++++++------------------- 1 file changed, 40 insertions(+), 104 deletions(-) diff --git a/src/NServiceBus.Core/obsoletes-v10.cs b/src/NServiceBus.Core/obsoletes-v10.cs index dc129eeea3d..9ca7c17ee8c 100644 --- a/src/NServiceBus.Core/obsoletes-v10.cs +++ b/src/NServiceBus.Core/obsoletes-v10.cs @@ -16,20 +16,15 @@ namespace NServiceBus Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public static class ConfigureFileShareDataBus { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] - public static DataBusExtensions BasePath(this DataBusExtensions config, - string basePath) => throw new NotImplementedException(); + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + public static DataBusExtensions BasePath(this DataBusExtensions config, string basePath) => throw new NotImplementedException(); } public partial class Conventions @@ -38,9 +33,7 @@ public partial class Conventions Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public bool IsDataBusProperty(PropertyInfo property) => throw new NotImplementedException(); } @@ -50,38 +43,29 @@ public partial class ConventionsBuilder Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] - public ConventionsBuilder DefiningDataBusPropertiesAs(Func definesDataBusProperty) => - throw new NotImplementedException(); + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + public ConventionsBuilder DefiningDataBusPropertiesAs(Func definesDataBusProperty) => throw new NotImplementedException(); } [ObsoleteMetadata( - Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", - RemoveInVersion = "11", - TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", + RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public class DataBusProperty : IDataBusProperty where T : class { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public DataBusProperty() => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public DataBusProperty(T value) => throw new NotImplementedException(); [JsonIgnore] @@ -90,9 +74,7 @@ public class DataBusProperty : IDataBusProperty where T : class Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public T Value => throw new NotImplementedException(); [JsonIgnore] @@ -100,64 +82,50 @@ public class DataBusProperty : IDataBusProperty where T : class Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public Type Type => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public string Key { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public bool HasValue { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public void SetValue(object valueToSet) => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public object GetValue() => throw new NotImplementedException(); } [ObsoleteMetadata( - Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", - RemoveInVersion = "11", - TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", + RemoveInVersion = "11", + TreatAsErrorFromVersion = "10")] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public class FileShareDataBus : DataBusDefinition { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] protected internal override Type ProvidedByFeature() => throw new NotImplementedException(); } @@ -165,54 +133,42 @@ public class FileShareDataBus : DataBusDefinition Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public interface IDataBusProperty { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] string Key { get; set; } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] bool HasValue { get; set; } [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] object GetValue(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] void SetValue(object value); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] Type Type { get; } } @@ -220,36 +176,28 @@ public interface IDataBusProperty Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public class SystemJsonDataBusSerializer : IDataBusSerializer { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public void Serialize(object dataBusProperty, Stream stream) => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public object Deserialize(Type propertyType, Stream stream) => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public string ContentType => throw new NotImplementedException(); } @@ -257,20 +205,15 @@ public class SystemJsonDataBusSerializer : IDataBusSerializer Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] public static class UseDataBusExtensions { [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] - public static DataBusExtensions UseDataBus( - this EndpointConfiguration config) + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + public static DataBusExtensions UseDataBus(this EndpointConfiguration config) where TDataBusDefinition : DataBusDefinition, new() where TDataBusSerializer : IDataBusSerializer, new() => throw new NotImplementedException(); @@ -278,23 +221,16 @@ public static DataBusExtensions UseDataBus UseDataBus( - this EndpointConfiguration config, IDataBusSerializer dataBusSerializer) + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + public static DataBusExtensions UseDataBus(this EndpointConfiguration config, IDataBusSerializer dataBusSerializer) where TDataBusDefinition : DataBusDefinition, new() => throw new NotImplementedException(); [ObsoleteMetadata( Message = "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'", RemoveInVersion = "11", TreatAsErrorFromVersion = "10")] - [Obsolete( - "The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", - true)] - public static DataBusExtensions UseDataBus(this EndpointConfiguration config, - Func dataBusFactory, IDataBusSerializer dataBusSerializer) => - throw new NotImplementedException(); + [Obsolete("The DataBus feature has been released as a dedicated package, 'NServiceBus.ClaimCheck'. Will be removed in version 11.0.0.", true)] + public static DataBusExtensions UseDataBus(this EndpointConfiguration config, Func dataBusFactory, IDataBusSerializer dataBusSerializer) => throw new NotImplementedException(); } public static partial class PersistenceConfig From 21824400c1a699c280e1ac2e189f004582c19839 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 10:52:51 +0200 Subject: [PATCH 18/25] Simplify syntax --- .../Persistence/PersistenceComponent.cs | 6 ++--- .../StorageTypeCollectionExtensions.cs | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/NServiceBus.Core/Persistence/StorageTypeCollectionExtensions.cs diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index c03b577210b..675cc9f577d 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -51,8 +51,8 @@ public static void ConfigurePersistence(this SettingsHolder settings) static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings, IReadOnlyCollection enabledPersistences) { - var sagaPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Sagas.Instance))?.Definition; - var outboxPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains(StorageType.Outbox.Instance))?.Definition; + var sagaPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains())?.Definition; + var outboxPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains())?.Definition; var bothFeaturesEnabled = settings.IsFeatureEnabled(typeof(Features.Sagas)) && settings.IsFeatureEnabled(typeof(Features.Outbox)); if (sagaPersisterDefinition != null @@ -68,7 +68,7 @@ internal static bool HasSupportFor(this IReadOnlySettings settings) where T : { _ = settings.TryGet(out IReadOnlyCollection supportedStorages); - return supportedStorages?.Contains(StorageType.Get()) ?? false; + return supportedStorages.Contains(); } static readonly ILog Logger = LogManager.GetLogger(typeof(PersistenceComponent)); diff --git a/src/NServiceBus.Core/Persistence/StorageTypeCollectionExtensions.cs b/src/NServiceBus.Core/Persistence/StorageTypeCollectionExtensions.cs new file mode 100644 index 00000000000..65b9e11ebee --- /dev/null +++ b/src/NServiceBus.Core/Persistence/StorageTypeCollectionExtensions.cs @@ -0,0 +1,23 @@ +#nullable enable + +namespace NServiceBus; + +using System.Collections.Generic; +using System.Linq; + +static class StorageTypeCollectionExtensions +{ + extension(IReadOnlyCollection? storageTypes) + { + public bool Contains() where TStorage : StorageType + { + if (storageTypes is null) + { + return false; + } + + var storageType = StorageType.Get(); + return storageTypes.Contains(storageType); + } + } +} \ No newline at end of file From 3128a822ba9488b71f0ed2d24938f9c47efbb6a5 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 11:04:51 +0200 Subject: [PATCH 19/25] Use generics instead --- src/NServiceBus.Core/Features/SettingsExtensions.cs | 12 +++++++++++- .../Persistence/PersistenceComponent.cs | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core/Features/SettingsExtensions.cs b/src/NServiceBus.Core/Features/SettingsExtensions.cs index 47825adeb31..ad75aa99786 100644 --- a/src/NServiceBus.Core/Features/SettingsExtensions.cs +++ b/src/NServiceBus.Core/Features/SettingsExtensions.cs @@ -8,7 +8,7 @@ namespace NServiceBus.Features; /// /// Feature related extensions to the settings. /// -public static partial class SettingsExtensions +public static class SettingsExtensions { /// /// Marks the given feature as enabled by default. @@ -51,23 +51,33 @@ public static bool IsFeatureEnabled(this IReadOnlySettings settings, Type featur return settings.GetOrDefault(featureType.FullName) == FeatureState.Enabled; } + internal static bool IsFeatureEnabled(this IReadOnlySettings settings) where T : Feature + { + ArgumentNullException.ThrowIfNull(settings); + return settings.IsFeatureEnabled(typeof(T)); + } + internal static void EnableFeature(this SettingsHolder settings, Type featureType) { + ArgumentNullException.ThrowIfNull(settings); settings.Set(featureType.FullName, FeatureState.Enabled); } internal static void DisableFeature(this SettingsHolder settings, Type featureType) { + ArgumentNullException.ThrowIfNull(settings); settings.Set(featureType.FullName, FeatureState.Disabled); } internal static void MarkFeatureAsActive(this SettingsHolder settings, Type featureType) { + ArgumentNullException.ThrowIfNull(settings); settings.Set(featureType.FullName, FeatureState.Active); } internal static void MarkFeatureAsDeactivated(this SettingsHolder settings, Type featureType) { + ArgumentNullException.ThrowIfNull(settings); settings.Set(featureType.FullName, FeatureState.Deactivated); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs index 675cc9f577d..3cccfc0f359 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceComponent.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceComponent.cs @@ -53,7 +53,7 @@ static void ValidateSagaAndOutboxUseSamePersistence(this SettingsHolder settings { var sagaPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains())?.Definition; var outboxPersisterDefinition = enabledPersistences.FirstOrDefault(p => p.SelectedStorages.Contains())?.Definition; - var bothFeaturesEnabled = settings.IsFeatureEnabled(typeof(Features.Sagas)) && settings.IsFeatureEnabled(typeof(Features.Outbox)); + var bothFeaturesEnabled = settings.IsFeatureEnabled() && settings.IsFeatureEnabled(); if (sagaPersisterDefinition != null && outboxPersisterDefinition != null From 316dc02ec497af8c00efb01ddb45082e6b88a493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Fri, 17 Oct 2025 11:09:38 +0200 Subject: [PATCH 20/25] Tweaks --- .../When_outbox_enabled_without_persister_supporting_it.cs | 1 - .../PublishSubscribe/When_configuring_subscription_authorizer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs index 65119d97fab..e9b57f39c35 100644 --- a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs +++ b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_outbox_enabled_without_persister_supporting_it.cs @@ -5,7 +5,6 @@ namespace NServiceBus.AcceptanceTests.Outbox; using EndpointTemplates; using NServiceBus.Persistence; using NUnit.Framework; -using Settings; public class When_outbox_enabled_without_persister_supporting_it : NServiceBusAcceptanceTest { diff --git a/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs b/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs index b7e2a95ab36..14c9e241719 100644 --- a/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs +++ b/src/NServiceBus.AcceptanceTests/Core/PublishSubscribe/When_configuring_subscription_authorizer.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Persistence; -using Settings; using Unicast.Subscriptions; using Unicast.Subscriptions.MessageDrivenSubscriptions; From 29875924411dd22e9137fe2e28de12ea98b36779 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 11:49:30 +0200 Subject: [PATCH 21/25] Explicit implementation because this is not something we have to publicly expose --- .../AcceptanceTestingPersistence.cs | 3 +-- .../APIApprovals.ApproveNServiceBus.approved.txt | 5 +---- .../Persistence/Learning/LearningPersistence.cs | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs index ad35410c79f..631b063b1ca 100644 --- a/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs +++ b/src/NServiceBus.AcceptanceTesting/AcceptanceTestingPersistence/AcceptanceTestingPersistence.cs @@ -2,7 +2,6 @@ using AcceptanceTesting; using Persistence; -using Settings; public class AcceptanceTestingPersistence : PersistenceDefinition, IPersistenceDefinitionFactory { @@ -13,5 +12,5 @@ public class AcceptanceTestingPersistence : PersistenceDefinition, IPersistenceD Supports(); } - public static AcceptanceTestingPersistence Create() => new(); + static AcceptanceTestingPersistence IPersistenceDefinitionFactory.Create() => new(); } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt index 4b82db8ef89..5a88a4da859 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt @@ -544,10 +544,7 @@ namespace NServiceBus { public static void EnableInstallers(this NServiceBus.EndpointConfiguration config, string? username = null) { } } - public class LearningPersistence : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory - { - public static NServiceBus.LearningPersistence Create() { } - } + public class LearningPersistence : NServiceBus.Persistence.PersistenceDefinition, NServiceBus.Persistence.IPersistenceDefinitionFactory { } public static class LearningSagaPersisterConfigurationExtensions { public static void SagaStorageDirectory(this NServiceBus.PersistenceExtensions persistenceExtensions, string path) { } diff --git a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs index bba6eb8e012..c9f4802ccf1 100644 --- a/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Learning/LearningPersistence.cs @@ -15,5 +15,5 @@ public class LearningPersistence : PersistenceDefinition, IPersistenceDefinition /// /// Creates the learning persistence definition. /// - public static LearningPersistence Create() => new(); + static LearningPersistence IPersistenceDefinitionFactory.Create() => new(); } \ No newline at end of file From 700fe3f651031ee68e0b9c40ca242e77610d9925 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 13:01:06 +0200 Subject: [PATCH 22/25] Better data structure to be closer to the previous behavior --- .../Persistence/PersistenceRegistry.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index 153e42191fa..ac7d76e15c3 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -12,24 +12,27 @@ sealed class PersistenceRegistry public EnableBuilder Enable() where TDefinition : PersistenceDefinition, IPersistenceDefinitionFactory { - if (definitions.TryGetValue(typeof(TDefinition), out var builder)) + if (tracker.TryGetValue(typeof(TDefinition), out var position)) { - return (EnableBuilder)builder; + return (EnableBuilder)definitions[position]; } var strongBuilder = new EnableBuilder(); - definitions.Add(typeof(TDefinition), strongBuilder); + tracker.Add(typeof(TDefinition), definitions.Count); + definitions.Add(strongBuilder); return strongBuilder; } public IReadOnlyCollection Merge() { - IEnumerable> builtPersistences = definitions.Reverse(); + // the order of the definitions is reversed when merging because the last UsePersistence calls have higher priority + // using linq to reverse to avoid mutating the original list + IEnumerable builtPersistences = definitions.AsEnumerable().Reverse(); var availableStorages = new List(StorageType.GetAvailableStorageTypes()); var enabledPersistences = new List(); - foreach (var (_, builtPersistence) in builtPersistences) + foreach (var builtPersistence in builtPersistences) { var supportedStorages = builtPersistence.Persistence.GetSupportedStorages(builtPersistence.EnabledStorageTypes); @@ -84,5 +87,9 @@ interface IEnabledBuilder IReadOnlyCollection EnabledStorageTypes { get; } } - readonly Dictionary definitions = []; + readonly Dictionary tracker = []; + // using a list to preserve the order of registrations since a dictionary does not guarantee it + // the order is important because the last UsePersistence calls have higher priority during merging + // that's why the list is reversed when merging. + readonly List definitions = []; } \ No newline at end of file From 944da112d716452eb2eca2aa5b84d73654bf726a Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 13:04:38 +0200 Subject: [PATCH 23/25] Small tweak --- src/NServiceBus.Core/Persistence/PersistenceRegistry.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index ac7d76e15c3..a537e0da1e8 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -4,7 +4,6 @@ namespace NServiceBus; using System; using System.Collections.Generic; -using System.Linq; using Persistence; sealed class PersistenceRegistry @@ -26,8 +25,9 @@ public EnableBuilder Enable() public IReadOnlyCollection Merge() { // the order of the definitions is reversed when merging because the last UsePersistence calls have higher priority - // using linq to reverse to avoid mutating the original list - IEnumerable builtPersistences = definitions.AsEnumerable().Reverse(); + // taking a copy to avoid modifying the original list + var builtPersistences = new List(definitions); + builtPersistences.Reverse(); var availableStorages = new List(StorageType.GetAvailableStorageTypes()); var enabledPersistences = new List(); From f09ecb8c603b82fbbf503d6b65cf9e845df4b0c7 Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 13:06:57 +0200 Subject: [PATCH 24/25] Another hint --- src/NServiceBus.Core/Persistence/PersistenceRegistry.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index a537e0da1e8..8eebd7bb6de 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -17,6 +17,7 @@ public EnableBuilder Enable() } var strongBuilder = new EnableBuilder(); + // using the count here works because we never remove enabled persistences tracker.Add(typeof(TDefinition), definitions.Count); definitions.Add(strongBuilder); return strongBuilder; From c17fbc868b9f9a1c583f01686099f9cb7d19bd6d Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Fri, 17 Oct 2025 15:27:13 +0200 Subject: [PATCH 25/25] Simplify deconstruction --- .../Persistence/PersistenceRegistry.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs index 8eebd7bb6de..7083f19ce66 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceRegistry.cs @@ -33,9 +33,9 @@ public IReadOnlyCollection Merge() var availableStorages = new List(StorageType.GetAvailableStorageTypes()); var enabledPersistences = new List(); - foreach (var builtPersistence in builtPersistences) + foreach (var (persistence, enabledStorageTypes) in builtPersistences) { - var supportedStorages = builtPersistence.Persistence.GetSupportedStorages(builtPersistence.EnabledStorageTypes); + var supportedStorages = persistence.GetSupportedStorages(enabledStorageTypes); var selectedStorages = new List(); @@ -52,7 +52,7 @@ public IReadOnlyCollection Merge() if (selectedStorages.Count != 0) { - enabledPersistences.Add(new EnabledPersistence(selectedStorages, builtPersistence.Persistence)); + enabledPersistences.Add(new EnabledPersistence(selectedStorages, persistence)); } } @@ -86,6 +86,12 @@ interface IEnabledBuilder PersistenceDefinition Persistence { get; } IReadOnlyCollection EnabledStorageTypes { get; } + + void Deconstruct(out PersistenceDefinition persistence, out IReadOnlyCollection enabledStorageTypes) + { + persistence = Persistence; + enabledStorageTypes = EnabledStorageTypes; + } } readonly Dictionary tracker = [];