JasperFx 0.2.0


Next

Customizing Bus Logging

Previous

Cascading Messages

Channels Edit on GitHub


The actual connectors in a Jasper service bus application are channel's that are backed and created by transport's. Today, Jasper only supports a transport based on the Lightning Queues project and an in memory transport for testing.

Configuring Channels

Channels require a little more work. First off, channels are identified and configured by a Uri matching the desired transport, port, and queue name.

We first need to build a "Settings" object for two channels identified as Pinger and Ponger:


public class SampleSettings
{
    public Uri Pinger { get; set; } =
        "lq.tcp://localhost:2352/pinger".ToUri();

    public Uri Ponger { get; set; } =
        "lq.tcp://localhost:2353/ponger".ToUri();
}

All the SampleSettings class is is a way to identify channels and act as a means to communicate the channel Uri's to the service bus model. I'm hard coding the Uri's in the code above, but that information can be pulled from any kind of configuration using the built in support for strong typed configuration in Jasper.

To configure service bus channels, it's easiest to define your application with a JasperBusRegistry class.


public class PingApp : JasperRegistry
{
    public PingApp(SampleSettings settings)
    {
        // Configuring PingApp to send PingMessage's
        // to the PongApp
        Messaging.Send<PingMessage>()
            .To(settings.Pinger);

        // Listen for incoming messages from "Pinger"
        Channels.ListenForMessagesFrom(settings.Pinger);
    }
}

public class PongApp : JasperRegistry
{
    public PongApp(SampleSettings settings)
    {
        // Listen for incoming messages from "Ponger"
        Channels.ListenForMessagesFrom(settings.Ponger);
    }
}

The JasperBusRegistry is a subclass of the JasperRegistry class that adds additional options germaine to the service bus feature.

Listening to Messages from a Channel

While you can always send messages to any configured channel, you must explicitly state which channels should be monitored for incoming messages to the current node. That's done with the ListenForMessagesFrom() method shown below:


public class ListeningApp : JasperRegistry
{
    public ListeningApp(SampleSettings settings)
    {
        // Listen for incoming messages from "Pinger"
        Channels.ListenForMessagesFrom(settings.Pinger);
    }
}

Static Message Routing Rules

When you publish a message using IServiceBus without explicitly setting the Uri of the desired destination, Jasper has to invoke the known message routing rules and dynamic subscriptions to figure out which locations should receive the message. Consider this code that publishes a PingMessage:


public class SendingExample
{
    public async Task SendPingsAndPongs(IServiceBus bus)
    {
        // Publish a message
        await bus.Send(new PingMessage());

        // Request/Reply
        var pong = await bus.Request<PongMessage>(new PingMessage());
    }
}

To route PingMessage to a channel, we can apply static message routing rules by using one of the SendMessage**** methods as shown below:


public class StaticRoutingApp : JasperRegistry
{
    public StaticRoutingApp(AppSettings settings)
    {
        // Explicitly add a single message type
        Messaging.Send<PingMessage>()
            .To(settings.Transactions);

        // Publish any types matching the supplied filter
        // to this channel
        Messaging.SendMatching("Message suffix", type => type.Name.EndsWith("Message"))
            .To(settings.Transactions);

        // Publish any message type contained in the assembly
        // to this channel, by supplying a type contained
        // within that assembly
        Messaging.SendFromAssemblyContaining<PingMessage>()
            .To(settings.Transactions);

        // Publish any message type contained in the named
        // assembly to this channel
        Messaging.SendFromAssembly(Assembly.Load(new AssemblyName("MyMessageLibrary")))
            .To(settings.Transactions);

        // Publish any message type contained in the
        // namespace given to this channel
        Messaging.SendFromNamespace("MyMessageLibrary")
            .To(settings.Transactions);

        // Publish any message type contained in the namespace
        // of the type to this channel
        Messaging.SendFromNamespaceContaining<PingMessage>()
            .To(settings.Transactions);
    }
}

Do note that doing the message type filtering by namespace will also include child namespaces. In our own usage we try to rely on either namespace rules or by using shared message assemblies.

See also the Dynamic Subscriptions

Message Persistence

If the transport you're using supports this switch (the LightningQueues transport does), you can declare channels to publish messages with either a delivery guaranteed, persistent strategy or by a non-persistent strategy. The non-guaranteed delivery mode is significantly faster, but probably only suitable for message types where throughput is more important than message reliability.

Below is a sample of explicitly controlling the channel persistence:


public class AppSettings
{
    // This channel handles "fire and forget"
    // control messages
    public Uri Control { get; set; }
        = new Uri("lq.tcp://localhost:2345/control");


    // This channel handles normal business
    // processing messages
    public Uri Transactions { get; set; }
        = new Uri("lq.tcp://localhost:2346/transactions");
}

public class BigApp : JasperRegistry
{
    public BigApp(AppSettings settings)
    {
        // Declare that the "Control" channel
        // use the faster, but unsafe transport mechanism
        Channels[settings.Control]
            .DeliveryFastWithoutGuarantee()
            .UseAsControlChannel();


        Channels[settings.Transactions]
            // This is the default, but you can
            // still configure it explicitly
            .DeliveryGuaranteed();

    }
}

Control Queues

Jasper may need to send messages between running service bus nodes to coordinate activities, register for dynamic subscriptions, or perform health checks. Some of these messages are time sensitive, so it will frequently be valuable to set up a separate "control" channel for these messages so they aren't stuck in a backed up queue with your normal messages.

Also, the control messages fit the "fire and forget" messaging model, so we recommend using the non-persistent channel mode with these channels.

Below is a sample of setting up a control channel:


public class ControlChannelApp : JasperRegistry
{
    public ControlChannelApp(AppSettings settings)
    {
        Channels[settings.Control]
            .UseAsControlChannel()
            .DeliveryFastWithoutGuarantee();
    }
}