Question

I'm attempting to modify the Azure-based Video Store sample app so that the front-end Ecommerce site can scale out.

Specifically, I want all instances of the web site to be notified of events like OrderPlaced so that no matter which web server the client web app happens to be connected to via SignalR, it will correctly receive the notification and update the UI.

Below is my current configuration in the Global.asax:

        Feature.Disable<TimeoutManager>();

        Configure.ScaleOut(s => s.UseUniqueBrokerQueuePerMachine());

        startableBus = Configure.With()
            .DefaultBuilder()
            .TraceLogger()
            .UseTransport<AzureServiceBus>()
            .PurgeOnStartup(true)
            .UnicastBus()
            .RunHandlersUnderIncomingPrincipal(false)
            .RijndaelEncryptionService()
            .CreateBus();

        Configure.Instance.ForInstallationOn<Windows>().Install();

        bus = startableBus.Start();

And I've also configured the Azure Service Bus queues using:

class AzureServiceBusConfiguration : IProvideConfiguration<NServiceBus.Config.AzureServiceBusQueueConfig>
{
    public AzureServiceBusQueueConfig GetConfiguration()
    {
        return new AzureServiceBusQueueConfig()
        {
            QueuePerInstance = true     
        };
    }
}

I've set the web role to scale to two instances, and as expected, two queues (ecommerce and ecommerce-1) are created. I do not, however, see additional topic subscriptions being created under the videostore.sales.events topic. Instead, I see:

Missing Subscriptions

I would think that you would see VideoStore.ECommerce-1.OrderCancelled and VideoStore.ECommerce-1.OrderPlaced subscriptions under the Videostore.Sales.Events topic. Or is that not how subscriptions are stored when using Azure Service Bus?

What am I missing here? I get the event on one of the ecommerce instances, but never on both. Even if this isn't the correct way to scale out SignalR, my use case extends to stuff like cache invalidation.

I also find it strange that two error and audit queues are being created. Why would that happen?

UPDATE

Yves is correct. The AzureServiceBusSubscriptionNamingConvention was not applying the correct individualized name. I was able to fix this by implementing the following EndpointConfig:

namespace VideoStore.ECommerce
{
    public class EndpointConfig : IConfigureThisEndpoint, IWantCustomInitialization
    {
        public void Init()
        {
            AzureServiceBusSubscriptionNamingConvention.Apply = BuildSubscriptionName;
            AzureServiceBusSubscriptionNamingConvention.ApplyFullNameConvention = BuildSubscriptionName;
        }

        private static string BuildSubscriptionName(Type eventType)
        {
            var subscriptionName = eventType != null ? Configure.EndpointName + "." + eventType.Name : Configure.EndpointName;

            if (subscriptionName.Length >= 50)
                subscriptionName = new DeterministicGuidBuilder().Build(subscriptionName).ToString();

            if (!SettingsHolder.GetOrDefault<bool>("ScaleOut.UseSingleBrokerQueue"))
                subscriptionName = Individualize(subscriptionName);

            return subscriptionName;
        }

        public static string Individualize(string queueName)
        {
            var parser = new ConnectionStringParser();
            var individualQueueName = queueName;
            if (SafeRoleEnvironment.IsAvailable)
            {
                var index = parser.ParseIndexFrom(SafeRoleEnvironment.CurrentRoleInstanceId);

                var currentQueue = parser.ParseQueueNameFrom(queueName);
                if (!currentQueue.EndsWith("-" + index.ToString(CultureInfo.InvariantCulture))) //individualize can be applied multiple times
                {
                    individualQueueName = currentQueue
                                              + (index > 0 ? "-" : "")
                                              + (index > 0 ? index.ToString(CultureInfo.InvariantCulture) : "");
                }
                if (queueName.Contains("@"))
                    individualQueueName += "@" + parser.ParseNamespaceFrom(queueName);
            }

            return individualQueueName;
        }
    }
}

I could not, however, get NServiceBus to recognize my EndpointConfig class. Instead, I had to call it manually before starting the bus. From my Global.asax.cs:

new EndpointConfig().Init();
bus = startableBus.Start();

Once I did this, the subscription names appeared as expected:

Correct Subscriptions

Not sure why it's ignoring my IConfigureThisEndpoint, but this works.

Was it helpful?

Solution

This sounds like a bug, can you raise a github issue on this at https://github.com/Particular/NServiceBus.Azure

That said, I think it's better to use signalr's scaleout feature instead of using QueuePerInstance as signalr needs to replicate other information like (connection/group mappings) internally as well when running in scaleout mode.

Update:

I think I see the issue, the subscriptions should be individualised as well, which isn't the case in current naming conventions

https://github.com/Particular/NServiceBus.Azure/blob/master/src/NServiceBus.Azure.Transports.WindowsAzureServiceBus/NamingConventions/AzureServiceBusSubscriptionNamingConvention.cs

while it is in the queuenamingconventions

https://github.com/Particular/NServiceBus.Azure/blob/master/src/NServiceBus.Azure.Transports.WindowsAzureServiceBus/NamingConventions/AzureServiceBusQueueNamingConvention.cs#L27

As these conventions are public you can override them to work around the problem by changing the func in IWantCustomInitialization until I can get a fix in, just copy the current method and add the individualizer logic. The queue individualizer is internal though, so you'll have to copy that class from

https://github.com/Particular/NServiceBus.Azure/blob/master/src/NServiceBus.Azure.Transports.WindowsAzureServiceBus/Config/QueueIndividualizer.cs

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top