How to use NServiceBus.Router to incrementally migrate one endpoint at time from Msmq to SqlTransport

Hi,
we have 20+ endpoints deployed in our client’s network, spanning from nsb version 3 to 7 with msmq transport.
We are going to use netcore for new application, so we will need to use sql transport. We would also like to migrate some of the existing application to dotnetcore, how can we do it one endpoint at time while keeping the rest of the sistem the same ? From what i tested, Router need awareness on the routing, this is not an option for some projects.
Szymon has created NServiceBus.Router.Migrator, but it looks like working in memory, is there a way to have it out of process, so that a project with sqlTransport can target dotnetcore and the project with the “bridge or migrator” targets net472 to listen to the “old” msmq queue for all the services that will keep using it ?
Or do someone know of a better strategy to do this transition ?

@valeriob

If you don’t want to touch your old endpoints what you can do it use one Router instance per each SQL transport endpoint. For example is you have two endpoints: Legacy (NServiceBus V3) and BrandNew (NServiceBus V7, .NET Core) you can add a Router named BrandNew_Router with two interfaces, MSMQ and SQL. By default the interfaces use queues with the same name as the router (BrandNew_Router in this case) but you can override that so that the MSMQ interface uses the name BrandNew while the SQL interface keeps the suffix to make sure it does not conflict with the endpoint.

In the V3 endpoint you now don’t need to use the Router.Connector package. You can you use the good old endpoint mappings to configure sending message MyMessage to endpoint BrandNew. That message would get to the router. The router has to be configured to forward messages between interfaces. It will then send it to the BrandNew endpoint via SQL Server transport.

You can also make it work for Pub/Sub but you need to configure the router to know where the Publishers are if you have a new publisher and a legacy Subscriber.

Szymon

Thanks Szymon,
could you be more specific about
“You can also make it work for Pub/Sub but you need to configure the router to know where the Publishers are if you have a new publisher and a legacy Subscriber.”
What would i have to do in practice ?

Thanks
Valerio

Sure

Here’s a test that shows this scenario: https://github.com/SzymonPobiega/NServiceBus.Router/blob/master/src/NServiceBus.Router.AcceptanceTests/SingleRouter/When_subscriber_is_not_aware_of_router.cs

The subscriber is not aware of the router (e.g. can be NServiceBus V3 lagacy endpoint). It registers the router endpoint name as a publisher. Here it is done in code (NSB V7) but in V3-V5 it would be via message-endpoint mappings config. Line 35 shows the extension to the router that is required to forward the subscribe message to the actual endpoint that handles the message.

Assuming our endpoints are Legacy and BrandNew and the router’s queues are named BrandNew on MSMQ side and BrandNew_Router on SQL side, your message-endpoint-mappings would look like this

<MessageEndpointMappings>
    <add Assembly="MyMessages"
        Type="MyMessages.EventPublishedByBrandNew"
        Endpoint="BrandNew" />
</MessageEndpointMappings>

and the router extension would look like:

class PublisherRule : IRule<SubscribePreroutingContext, SubscribePreroutingContext>
{
    public Task Invoke(SubscribePreroutingContext context, Func<SubscribePreroutingContext, Task> next)
    {
        context.Destinations.Add(new Destination("BrandNew", null));
        return next(context);
    }
}

The BrandNew in the MEM’s refer to the MSMQ interface of the router while the BrandNew in the router extension behavior refers to the BrandNew queue in the SQL transport – the queue of the publisher.

1 Like

Thanks Szymon,
i’m trying what you suggested, thanks !
I’m stuck here: the router crashes handling the subscription coming from Legacy for BrandNew endpoint.
It looks like trying to send the subscription into a msmq queue instead of sql one.
In the code below Sql_A = BrandNew, MSMQ_B = Legacy.

What am i missing ?
The same router is working fine with another msmq endpoint with the ConnectToRouter feature.
Thanks
Valerio

2020-04-04 15:17:39.069 ERROR Persistent error while processing messages in nsb_router. Entering throttled mode.
NServiceBus.Unicast.Queuing.QueueNotFoundException: Failed to send message to address: [sql_a@NBK2018100] —> System.Messaging.MessageQueueException: The queue does not exist or you do not have sufficient permissions to perform the operation.
at System.Messaging.MessageQueue.MQCacheableInfo.get_WriteHandle()
at System.Messaging.MessageQueue.StaleSafeSendMessage(MQPROPS properties, ITransaction transaction)
at System.Messaging.MessageQueue.StaleSafeSendMessage(MQPROPS properties, IntPtr transaction)
at System.Messaging.MessageQueue.SendInternal(Object obj, MessageQueueTransaction internalTransaction, MessageQueueTransactionType transactionType)
at System.Messaging.MessageQueue.Send(Object obj, String label, MessageQueueTransactionType transactionType)
at NServiceBus.Transport.Msmq.MsmqMessageDispatcher.ExecuteTransportOperation(TransportTransaction transaction, UnicastTransportOperation transportOperation)
— End of inner exception stack trace —
at NServiceBus.Transport.Msmq.MsmqMessageDispatcher.ExecuteTransportOperation(TransportTransaction transaction, UnicastTransportOperation transportOperation)
at NServiceBus.Transport.Msmq.MsmqMessageDispatcher.Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context)
at NServiceBus.Raw.RunningRawEndpointInstance.Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context)
at ThrottlingRawEndpointConfig`1.NServiceBus.Transport.IDispatchMessages.Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context).

I was able to forward the subscribe message adding the interface name in the “site” variable of the destination :
context.Destinations.Add(new Destination(Constants.SqlAEndpoint, “SqlServer”));

Hmmm that’s strange. So you subscription message has been returned to the MSMQ transport and failed there? Can share your router configuration i.e. the code to set up forwarding in the router?

Szymon

Hi,
sure, here is the router config :
I deleted the subscriptions in msmq subscription table, and the problem disappeared, i probably had some rogue subscription from previous attempt. Anyway, what is the SiteId parameter for ?

    async Task ConfigureNsbRouter(string NServiceBusSqlTransportConnectionString, string routerEndpointName)
    {
        var sqlInterfaceName = "SqlServer";
        var msmqInterfaceName = "MSMQ";

        var routerConfig = new RouterConfiguration(routerEndpointName);
        routerConfig.PoisonQueueName = routerEndpointName + ".error";

        var msmqInterface = routerConfig.AddInterface<MsmqTransport>(msmqInterfaceName, t =>
        {

        });

        var msmqInterfaceSubscriptions = new SqlSubscriptionStorage(() =>
        {
            return new System.Data.SqlClient.SqlConnection(NServiceBusSqlTransportConnectionString);
        }, $"{routerEndpointName}_{msmqInterfaceName}_", new SqlDialect.MsSqlServer(), TimeSpan.FromSeconds(60));
        msmqInterface.EnableMessageDrivenPublishSubscribe(msmqInterfaceSubscriptions);


        var sqlServerInterface = routerConfig.AddInterface<SqlServerTransport>(sqlInterfaceName, t =>
        {
            t.ConnectionString(NServiceBusSqlTransportConnectionString);
        });


        var staticRouting = routerConfig.UseStaticRoutingProtocol();
        staticRouting.AddForwardRoute(msmqInterfaceName, sqlInterfaceName);
        staticRouting.AddForwardRoute(sqlInterfaceName, msmqInterfaceName);

        routerConfig.AutoCreateQueues();

        routerConfig.AddRule(_ => new PublisherRule());

        var router = Router.Create(routerConfig);

        await msmqInterfaceSubscriptions.Install();

        await router.Start();
    }

The site is used if you would like to replace NServiceBus.Gateway with a Router. In that case your system is partitioned into sites and some sites contain the same set of endpoints e.g. there is one HQ site with endpoints Sales and Billing and multiple ProductionFacility site that contain Shipping and Manufactoring endpoint. Each physical factory has a datacentre with these two endpoints.

In such a system it is not enough to specify endpoint name as a destination when sending from HQ. You would use SendToSites extension to SendOptions to specify the destination site(s). Then you would have a router sitting in the middle. This router would have a routing table that maps site IDs to interfaces.

Unfortunately I don’t have a sample for that (yet).

Thanks,
understood ! I have a follow up question :smiley:
If the Sql endpoint BrandNew wants to send a message to an msmq endpoint in a remote machine, how do i specify the endpoing mapping ? I guess it should be in the router?

I found this, it should do the trick :

Now pub sub works both way between Legacy and BrandNew,
however to be able to send messages also, i had to implement this other part, is it correct ?:

class SenderRule : IRule<SendPreroutingContext, SendPreroutingContext>
{
    public Task Invoke(SendPreroutingContext context, Func<SendPreroutingContext, Task> next)
    {
        if (context.IncomingInterface == "SqlServer")
        {
            context.Destinations.Add(new Destination("Legacy", null));
        }
        return next(context);
    }
}

in the PublisherRule i had to filter those messages out:

class PublisherRule : IRule<SubscribePreroutingContext, SubscribePreroutingContext>
{
    public Task Invoke(SubscribePreroutingContext context, Func<SubscribePreroutingContext, Task> next)
    {
        if (context.IncomingInterface == "MSMQ")
        {
            context.Destinations.Add(new Destination(Constants.SqlCEndpoint, null));
        }
        return next(context);
    }
}

I think this is due to the BrandNew is also subscribing to msmq events, so without that filter it gives this error : NServiceBus.Unicast.Queuing.QueueNotFoundException: Failed to send message to address: [sql_c@NBK2018100] —> System.Messaging.MessageQueueException: The queue does not exist or you do not have sufficient permissions to perform the operation.

Thanks again
Valerio

Assuming BrandNew is at least NServiceBus 6 (or even 7), it could use Router.Connector to specify the routing. This way you don’t need these routing rules in the Router for the path from the new endpoints to the old ones, only if the path leads from the legacy to the new.

You are right, SenderRule is not needed !
Does it sound right that i have to filter if (context.IncomingInterface == “MSMQ”) in the PublisherRule ?
Thanks

Yeah. Or you can use AddRule method on the interface itself (instead of using the one on the router config object). Contrary to what the XML documentation says (I am fixing it at this moment), it would add a rule only to the interface you want it to be added to.