Browse Source

Support auto-subscribe routable message

The default subscription mode of routable message is Manual.
This behavior has two issues: it breaks the principle of least
surprise and it makes turning existing message routable a dangerous
operation.

Changing this behavior now is not possible, but it would be nice to
configure new routable messages with the Auto subscription mode.

This change allows to configure the default subscription mode of
routable messages.
Olivier Coanet 2 years ago
parent
commit
e77ed06ed0

+ 20 - 0
src/Abc.Zebus.Contracts/Routing/Routable.cs

@@ -2,8 +2,28 @@
 
 namespace Abc.Zebus.Routing
 {
+    /// <summary>
+    /// Indicates that the target message is routable.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// A routable message must have routable members. The routable members must be explicitly specified using <see cref="RoutingPositionAttribute"/>.
+    /// </para>
+    /// <para>
+    /// The default subscription mode of routable message is <c>Manual</c>, i.e.: subscriptions must be manually created with <c>IBus.Subscribe</c>.
+    /// If you update an existing message to make it routable, consider specifying <c>AutoSubscribe = true</c> to keep previous subscription mode.
+    /// </para>
+    /// </remarks>
     [AttributeUsage(AttributeTargets.Class)]
     public sealed class Routable : Attribute
     {
+        /// <summary>
+        /// Indicates whether the default subscription mode for the target message is Auto (<c>AutoSubscribe == true</c>) or Manual.
+        /// </summary>
+        /// <remarks>
+        /// <see cref="AutoSubscribe"/> configures the default subscription mode for the target message. The subscription mode
+        /// can still be overriden per message handler using the <c>SubscriptionMode</c> attribute.
+        /// </remarks>
+        public bool AutoSubscribe { get; set; }
     }
 }

+ 60 - 0
src/Abc.Zebus.Tests/Scan/SyncMessageHandlerInvokerLoaderTests.cs

@@ -46,6 +46,34 @@ namespace Abc.Zebus.Tests.Scan
             invoker.GetStartupSubscriptions().ShouldBeEmpty();
         }
 
+        [Test]
+        public void should_subscribe_to_auto_subscribe_routable_message_handler_on_startup()
+        {
+            var invokerLoader = new SyncMessageHandlerInvokerLoader(new Container());
+            var invoker = invokerLoader.LoadMessageHandlerInvokers(TypeSource.FromType<FakeRoutableMessageWithAutoSubscribeHandler>()).ExpectedSingle();
+
+            invoker.GetStartupSubscriptions().ShouldBeEquivalentTo(Subscription.Any<FakeRoutableMessageWithAutoSubscribe>());
+        }
+
+        [Test]
+        public void should_subscribe_to_auto_subscribe_routable_message_handler_with_auto_subscription_mode_on_startup()
+        {
+            var invokerLoader = new SyncMessageHandlerInvokerLoader(new Container());
+            var invoker = invokerLoader.LoadMessageHandlerInvokers(TypeSource.FromType<FakeRoutableMessageWithAutoSubscribeHandler_Auto>()).ExpectedSingle();
+
+            invoker.GetStartupSubscriptions().ShouldBeEquivalentTo(Subscription.Any<FakeRoutableMessageWithAutoSubscribe>());
+        }
+
+
+        [Test]
+        public void should_not_subscribe_to_auto_subscribe_routable_message_handler_with_manual_subscription_mode_on_startup()
+        {
+            var invokerLoader = new SyncMessageHandlerInvokerLoader(new Container());
+            var invoker = invokerLoader.LoadMessageHandlerInvokers(TypeSource.FromType<FakeRoutableMessageWithAutoSubscribeHandler_Manual>()).ExpectedSingle();
+
+            invoker.GetStartupSubscriptions().ShouldBeEmpty();
+        }
+
         [Test]
         public void should_switch_to_manual_subscription_mode_when_specified()
         {
@@ -104,6 +132,14 @@ namespace Abc.Zebus.Tests.Scan
             public string Key;
         }
 
+
+        [Routable(AutoSubscribe = true)]
+        public class FakeRoutableMessageWithAutoSubscribe : IMessage
+        {
+            [RoutingPosition(1)]
+            public string Key;
+        }
+
         public class FakeHandler : IMessageHandler<FakeMessage>, IMessageHandler<FakeMessage2>
         {
             public void Handle(FakeMessage message)
@@ -156,6 +192,30 @@ namespace Abc.Zebus.Tests.Scan
             }
         }
 
+        public class FakeRoutableMessageWithAutoSubscribeHandler : IMessageHandler<FakeRoutableMessageWithAutoSubscribe>
+        {
+            public void Handle(FakeRoutableMessageWithAutoSubscribe message)
+            {
+            }
+        }
+
+        [SubscriptionMode(SubscriptionMode.Auto)]
+        public class FakeRoutableMessageWithAutoSubscribeHandler_Auto : IMessageHandler<FakeRoutableMessageWithAutoSubscribe>
+        {
+            public void Handle(FakeRoutableMessageWithAutoSubscribe message)
+            {
+            }
+        }
+
+        [SubscriptionMode(SubscriptionMode.Manual)]
+        public class FakeRoutableMessageWithAutoSubscribeHandler_Manual : IMessageHandler<FakeRoutableMessageWithAutoSubscribe>
+        {
+            public void Handle(FakeRoutableMessageWithAutoSubscribe message)
+            {
+            }
+        }
+
+
         [SubscriptionMode(typeof(StartupSubscriber))]
         public class FakeRoutableHandlerWithStartupSubscriber : IMessageHandler<FakeRoutableMessage>
         {

+ 4 - 1
src/Abc.Zebus/Dispatch/MessageHandlerInvokerSubscriber.cs

@@ -66,7 +66,10 @@ namespace Abc.Zebus.Dispatch
 
         private static SubscriptionMode DefaultSubscriptionMode(Type messageType)
         {
-            return Attribute.IsDefined(messageType, typeof(Routable)) ? SubscriptionMode.Manual : SubscriptionMode.Auto;
+            if (Attribute.GetCustomAttribute(messageType, typeof(Routable), true) is Routable { AutoSubscribe: false })
+                return SubscriptionMode.Manual;
+
+            return SubscriptionMode.Auto;
         }
 
         private IEnumerable<Subscription> GetSubscriptionsFromMode(SubscriptionMode subscriptionMode, MessageTypeId messageTypeId)