Monitor Azure Service Bus endpoints with ServiceControl adapter

This sample shows how to configure ServiceControl to monitor endpoints and retry messages when using the advanced features of the Azure Service Bus transport not natively supported by ServiceControl.

The following diagram shows the topology of the solution:

graph RL subgraph Namespace 2 shipping["fa:fa-truck Shipping"] end subgraph Namespace 1 sales["fa:fa-money Sales"] end subgraph Namespace 3 sc["fa:fa-wrench Service Control"] end adapter{"fa:fa-exchange Adapter"} sales ==> adapter adapter .-> sales shipping==>adapter adapter .-> shipping adapter==>sc sc .-> adapter

Notice that Sales and Shipping are in two different namespaces. Cross-namespace routing is implemented using NServiceBus.Router community project. The other important thing to note is that ServiceControl is in a different namespace than the other endpoints, which means that it can't natively communicate with them. This is why the sample shows how to create multi-directional router to bridge between all the components.

Prerequisites

An environment variable named AzureServiceBus.ConnectionString with the connection string for the Azure Service Bus namespace.

  1. An environment variable named AzureServiceBus.ConnectionString.1 with the connection string for the Azure Service Bus namespace to be used by the Sales endpoint.
  2. An environment variable named AzureServiceBus.ConnectionString.2 with the connection string for the Azure Service Bus namespace to be used by the Shipping endpoint.
  3. An environment variable named AzureServiceBus.ConnectionString.SC with the connection string for the Azure Service Bus namespace to be used by ServiceControl and the adapter.
  4. Install ServiceControl.
  5. Using the ServiceControl Management tool, set up ServiceControl to monitor endpoints using the Azure Service Bus transport:
  • Add a new ServiceControl instance:
  • Use Particular.ServiceControl as the instance name (ensure there is no other instance of SC running with the same name).
  • Use the connection string supplied with the AzureServiceBus.ConnectionString.SC environment variable.
If other ServiceControl instances have been running on this machine, it's necessary to specify a non-default instance name and port number. Adjust ServicePulse settings accordingly to point to this location.
  1. Ensure the ServiceControl process is running before running the sample.
  2. Install ServicePulse

Running the project

  1. Start the projects: Adapter, Sales and Shipping. Ensure the adapter starts first because on start-up it creates a queue that is used for heartbeats.
  2. Open ServicePulse (by default it's available at http://localhost:9090/#/dashboard) and select the Endpoints Overview. The Shipping endpoint should be visible in the Active Endpoints tab as it has the Heartbeats plugin installed.
  3. Go to the Sales console and press o to create an order.
  4. Notice the Shipping endpoint receives the OrderAccepted event from Sales and publishes OrderShipped event.
  5. Notice the Sales endpoint logs that it processed the OrderShipped event.
  6. Go to the Sales console and press f to simulate message processing failure.
  7. Press o to create another order. Notice the OrderShipped event fails processing in Sales and is moved to the error queue.
  8. Press f again to disable message processing failure simulation in Sales.
  9. Go to the Shipping console and press f to simulate message processing failure.
  10. Go back to Sales and press o to create yet another order. Notice the OrderAccepted event fails in Shipping and is moved to the error queue.
  11. Press f again to disable message processing failure simulation in Shipping.
  12. Open ServicePulse and select the Failed Messages view.
  13. Notice the existence of one failed message group with two messages. Open the group.
  14. One of the messages is OrderAccepted which failed in Shipping, the other is OrderShipped which failed in Sales.
  15. Press the "Retry all" button.
  16. Go to the Shipping console and verify that the OrderAccepted event has been successfully processed.
  17. Go to the Sales console and verify that both OrderShipped events have been successfully processed.
  18. Shut down the Shipping endpoint.
  19. Open ServicePulse and notice a red label next to the heart icon. Click on the that icon to open the Endpoints Overview. Notice that the Shipping is now displayed in the Inactive Endpoints tab.

Code walk-through

The solution consists of four projects.

Shared

The Shared project contains the message contracts.

Sales and Shipping

The Sales and Shipping projects contain endpoints that simulate the execution of a business process. The process consists of two messages: ShipOrder command sent by Sales and OrderShipped reply sent by Shipping.

The Sales and Shipping endpoints include a message processing failure simulation mode (toggled by pressing f) which can be used to generate failed messages for demonstrating message retry functionality.

The Shipping endpoint has the Heartbeat plugin installed to enable uptime monitoring via ServicePulse.

Router

The Router project hosts the ServiceControl.TransportAdapter and NServiceBus.Router instances. It uses a helper class NamespaceRouter to configure the bridging. The class accepts a list of structures describing namespaces of the system. For each namespace that hosts endpoints it executes three steps.

var namespaces = new[]
{
    new NamespaceDescription("Sales", salesConnectionString),
    new NamespaceDescription("Shipping", shippingConnectionString)
};

var namespaceRouter = new NamespaceRouter("Router", namespaces, "Particular.ServiceControl", serviceControlConnectionString);

await namespaceRouter.Start().ConfigureAwait(false);

First, it creates a NServiceBus.Router interface for this namespace

routerConfig.AddInterface<AzureServiceBusTransport>(namespaceDescription.Name, t =>
{
    t.ConnectionString(namespaceDescription.ConnectionString);
    t.Transactions(TransportTransactionMode.ReceiveOnly);
});

Second, it configures a ServiceControl.TransportAdapter between that namespace and the ServiceControl namespace

transportAdapterConfig.CustomizeEndpointTransport(
    t =>
    {
        t.ConnectionString(namespaceDescription.ConnectionString);
        t.Transactions(TransportTransactionMode.ReceiveOnly);
    });

transportAdapterConfig.CustomizeServiceControlTransport(
    t =>
    {
        t.ConnectionString(serviceControlConnectionString);
        t.Transactions(TransportTransactionMode.ReceiveOnly);
    });

transportAdapterConfig.ServiceControlSideControlQueue = serviceControlMainQueue;

Last but not least, it configures convention-based routing between the endpoints. The convention configures the router to forward messages to the namespace that matches the last part of the endpoint name e.g. a command destined to MyPaymentGateway.Billing will be forwarded to the Billing namespace.

staticRouting.AddRoute(
    destinationFilter: (iface, destination) =>
    {
        return destination.Endpoint.EndsWith(namespaceDescription.Name);
    },
    destinationFilterDescription: $"To {namespaceDescription.Name}",
    gateway: null,
    iface: namespaceDescription.Name);

As a result, apart from the queues associated with the business endpoints, each namespace contains a set of queues used for the cross-namespace routing:

  • audit - forwarded to ServiceControl's audit queue
  • error - forwarded to ServiceControl's error queue
  • particular.servicecontrol (or other that matches the name of the ServiceControl instance)
  • router - endpoints send messages to this queue when they want these messages to be delivered to an endpoint in another namespace

The ServiceControl namespace, apart from regular set of queues, contains queues used for forwarding retried messages (one for each business namespace):

  • billing.adapter.retry
  • shipping.adapter.retry

Related Articles


Last modified