Wednesday 6 September 2017

Enterprise Integration Patterns with Windows Azure Service Bus (1) – Dynamic Routers

The book Enterprise Integration Patterns by Gregor Hohpe et al. has been sitting on my desk for several years now, and I’ve always been planning writing a library that brings those patterns into easy-to-use building blocks for .Net developers. However , the motion of writing a MOM system by myself doesn’t sound quite plausible to myself, even though I’ve been taking on some insane projects from time to time. On the other hand, the idea is so fascinating that I keep looking for opportunities to actually start the project. This is why I was really excited when I first learned about Windows Azure Service Bus. Many of the patterns, including (but not limited to) Message Filter, Content-Based Router, and Competing Consumers, are either supported out-of-box or can be implemented with easy customizations. However, I want more. I want ALL the patterns in the book supported and can be applied to applications easily. So, after a couple years of waiting, I finally started the project to implement Enterprise Integration Patterns.

About the project

The project is open source, which can be accessed here. I’m planning to add more patterns to the project on a monthly basis (as I DO, maybe surprisingly, have other things to attend to). I’d encourage you, my dear readers, to participate in the project if you found the project interesting. And you can always send me comments and feedbacks to hbai@microsoft.com. At the time when this post is written, the code is at its infant stage and needs lots of work. However, it does demonstrates the basic ideas such as independent processing units and configuration supports.

Extended Windows Azure Service Bus constructs

As this is the first post of the series, I’ll spend sometime to explain the basic entities used in this project, and then give you an overview of the overall architecture.
  • Processing Units

Windows Azure Service Bus provides two types of “pipes” – queues and topics, which are fundamental building blocks  of a messaging system. Customization points, such as filters and rules, are provided on these pipes so you can control how these pipes are linked into processing pipelines. This works for many of the simple patterns perfectly. However, for more complex patterns, these pipes are linked in more convoluted ways. And very often the connectors among the pipes assume enough significance to be separate entities. In this project I’m introducing “Processing Unit” as separate entities to provide greater flexibilities and capabilities to building processing pipelines.  A Processing Unit can be linked to a number of inbound channels, outbound channels as well as control channels. It’s self-contained so it can be hosted in any processes  –  it can be hosted on-premises or on cloud, in cloud services or console applications, or wherever else. And in most cases you can easily scale out Processing Units by simply hosting more instances. In addition, Processing Units support events for event-driven scenarios.
  • Channel

Channels are the “pipes” in a messaging system. These are where your messages flow from one place to another. Channels connect Processing Units, forming a Processing Pipeline.
  • Processing Pipeline

Process Pipelines comprise Processing Units and Channels. It provides management boundaries so that you can construct and manage messaging routes more easily. Here I need to point out that Processing Pipeline is more a management entity than runtime entity. Parts of  a Processing Pipeline can be (and usually are) scattered across different networks, machines and processes.
  • Processing Unit Host

Processing Unit Host, as its name suggests, hosts Processing Units. It manages lifecycle of hosted Processing Units as well as governing overall system resource consumption.

Pattern 1: Dynamic Router

  • Problem

Message routing can be achieved by a topic/subscription channel with filters that control which recipients get what messages. In other words, messages are broadcasted to all potential recipients before they are filtered by receiver-side filters. If we only want to route messages to specific recipients, Message Routers are more efficient because there’s only one message sent to designated recipient. Further more, routing criteria may be dynamic, requiring the routing Processing Unit to be able to take in feedbacks from recipients so that it can make informed decisions when routing new messages.
  • Pattern

Dynamic Router pattern is well-defined in Gregor’s book, so I won’t bother to repeat the book here, other than stealing a diagram from it (see below). What makes this pattern interesting is that the recipients are provided with a feedback channel so that they can report their updated states back to the router to affect future routing decisions. As you can see in the following diagram, the rule processor (as well as its associated storage, which is not shown in the following diagram) doesn’t belong to any specific channels, so it should exist as a separate entity.image
In my implementation the Dynamic Router is implemented as a Processing Unit, which is hosted independently from message queues. Specifically, DynamicRouter class, which has the root class ProcessingUnit, defines a virtual PickOutputChannel method that returns a index to the channel that is to be selected for next message. The default implementation of this method doesn’t take feedbacks into consideration. Instead, it simply selects output channels in a round-robin fashion:
protected virtual int PickOutputChannel(ChannelMessageEventArgs e)
{
  mChannelIndex = OutputChannels.Count > 0 ? (mChannelIndex + 1) % OutputChannels.Count : 0;
  return mChannelIndex;
}
Obviously the method can be overridden in sub classes to implement more sophisticated routing logics. In addition, the Processing Unit can subscribe to MessageReceviedOnChannel on its ControlChannels property to collect feedbacks from recipients. The architecture of the project doesn’t mandate any feedback formats or storage choices, you are pretty much free to implement whatever fits the situation.
  • Sample Scenario – dynamic load-balancing

In this sample scenario, we’ll implement a Dynamic Message Router that routes messages to least pressured recipients using a simple greedy load-balancing algorithm. The pressure of a recipient is defined as the current workload on that recipient. When the router tries to route a new message, it will always routes the message to the recipient with least workload. This load-balancing scheme often yields better results than round-robin load-balancing. The following screenshot shows the running result of such as scenario. In this scenario, we have 309 seconds worth of work to be distributed to 2 nodes. If we followed round-robin pattern, this particular work set will take 161.5 seconds to finish. While using the greedy load-balancing, it took the two nodes 156.4 seconds to finish all the tasks, which is pretty close to the theoretical minimum processing time – 154.8 seconds. Note that it’s virtually impossible to achieve the optimum result as the calculation assumes any single task can be split freely without any penalties.image
This scenario uses GreedyDynamicRouter class, which is a sub-class of DynamicRouter. The class subscribes to MessageReceivedOnchannel event of its ControlChannels collection to collect recipient feedbacks, which are encoded in message headers sent back from the recipients. As I mentioned earlier, the architecture doesn’t mandate any feedback formats, the recipients and the router need to negotiate a common format offline, or use the “well known” headers, as used by the GreedyDynamicRouter implementation. This implementation saves all its knowledge about the recipients in memory. But obviously it can be extended to use external storages or distributed caching for the purpose.

Pattern 2: Content-Based Router

  • Problem

In many integration scenarios, we need capability to route messages to different recipients based on message contents. The messages should be sent to designated recipients instead broadcasted to all recipients to filter on.  In addition, it’s desirable to have routing logic defined in a centralized place instead of scattered across different recipients. Centralized routing logic is easier to maintain and it can avoid the risk of inconsistent filtering that may cause lose of message or duplicated processing.
  • Pattern

The library doesn’t have a specific Content-Based Router - DynamicRouter class suffices in this case. You can easily override its virtual PickOutputChannel method to implement your custom routing logics. The method is triggered whenever a message is about to be routed, and you have access to the triggering channel as well as the message via method parameters.image
  • Sample Scenario – dynamic load-balancing

In this sample scenario, we’ll generate a number of messages with a header value randomly set to “Cars” or “Trucks”. Then, we implement a Content-Based Router and send all “Cars” requests to one channel and the rest to another. You can check out ContentBasedRouter method in test console application for detailed implementation. It’s really easy to implement such a router, as show in the following code snippet:
public class HeaderBasedRouter: DynamicRouter
{
  protected override int PickOutputChannel(Infrastructure.ChannelMessageEventArgs e)
  {
    if (e.Message.Headers.ContainsKey("ProductType"))
      return (string)e.Message.Headers["ProductType"] == "Cars" ? 0 : 1;
    else
      return base.PickOutputChannel(e);
  }
}
Note: because the routing logic is externalized, none of the recipients is aware of any routing logics or conditions. In the following screenshot you can see how tasks are routed to different processors based on their headers:image

Summary


In this post we had a brief overview of the overall architecture of Enterprise Integration Patterns project, and we also went through the first couple of patterns – Dynamic Router and Content-Based Router. We are starting with harder patterns in this project so that we can validate and improve the overall infrastructure during earlier phases of the project. At this point I haven’t decided which pattern to go after next, but message transformation patterns and other routing patterns are high in the list. See you next time!

No comments:

Post a Comment