Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Service Bus Architecture based on WCF (3): Build your ESB

4.87/5 (18 votes)
10 Feb 2011CPOL4 min read 77.7K   8.3K  
Service Bus Architecture based on WCF (3): build your ESB

Introduction

It was almost one year ago that I finished the last chapter. One year is kind of too long to remember everything, I have to spend a couple of hours to review what I have done one year ago. The good thing is I still remember how to bring up each application and connect them together, the better thing is they are still working great. The best thing is I can go on writing the article.

In the last chapter, we have successfully abstracted WCF service out of business logic. Based on what we have done, I’m going to create our service bus. Let's go back to the FuctionService and MessageService. FunctionService is responsible for providing service to client call. MessageService is responsible for subscripting message to clients.

For example, there are HubBeijing Service managing Beijing product inventory and HubChina Service managing China product inventory. They open FunctionService and MessageService respectively. And they manage their own inventory respectively. I put a simple rule that each Hub will send out Out of Stock message if client requests a product number that is more than product inventory. Let’s assume these two systems are independent. Beijing is a part of China, when Beijing hub is out of stock, China Hub will provide the number requested from client. Since Beijing hub and China hub are independent, they will not communicate directly, we need a third party to connect them together. This third party is what we said Service Bus. We have abstracted service out of business logic, now we are going to abstract the Service Bus out of business logic to be an independent component.

Background

Please check How to abstract a WCF Service out of business logic and How to implement subscription based on WCF to get more context.

Using the Code

ESB component is actually a client connecting different services and accept different subscriptions. ESB component has the ability to connect many service providers. It is able to call different service providers’ methods, and it is also able to listen to different service providers’ subscriptions.

C#
BusEngine.RegisterNewService("Beijing Hub", "net.tcp://localhost:8886/Inventory");
BusEngine.RegisterNewService("China Hub", "net.tcp://localhost:8887/Inventory");
//In RegisterNewService, there is a reference to SAOTEnpoint. 
//The method will open RemoteCall service as well as Subscription Service
BusEngine.RegisterNewService
//Disconnected event will happen when any registered services connection is broken, 
//in the event, arg sender will tell you which service is broken.
BusEngine.Disconnected += new EventHandler(BusEngine_Disconnected);
//HeartBeatMessage will tell you if one service connection is still connected.
BusEngine.HeartBeatMessage += new EventHandler(BusEngine_HeartBeatMessage);
//MessageDelivered Event will get all messages from all registered services. 
//In the event, argument Trilobita.SAOT.Object.Message e will let you know 
//the detail information of the message.
//Trilobita.SAOT.Object.Message.ServiceName indicates the message is from which 
//registered service.
//Trilobita.SAOT.Object.Message. FunctionCode indicates customer defined flag like: 
//OUTOFSTOCK means the message is to notify one particular product is out of stock.
BusEngine.MessageDelivered += new EventHandler<trilobita.saot.object.message>
				(BusEngine_MessageDelivered);

//In order to provide an abstracted service bus, The message structure is predefined. 
//Users can use the predefined structure to define the customized data. 
//Content and Context is prepared for complex customized data. See the below diagram:

public class Message: EventArgs
    {
        #region Properties
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public string Content { get; set; }
        [DataMember]
        public string Context { get; set; }
        [DataMember]
        public DateTime Createtime { get; set; }
        [DataMember]
        public string PrimaryID { get; set; }
        [DataMember]
        public string Sender { get; set; }
        [DataMember]
        public string To { get; set; }
        [DataMember]
        public string CC { get; set; }
        [DataMember]
        public DateTime ExpiredTime { get; set; }
        [DataMember]
        public string Attachment { get; set; }
        [DataMember]
        public string FunctionCode { get; set; }
        [DataMember]
        public string ServiceName { get; set; }
        #endregion
    }

ESBComponent.jpg

We still use the Hub example. ESB component connects HubBeijing service as well as HubChina service. When Beijing client requests a product more than HubBeijing inventory, HubBeijing will subscribe a message to all registers. ESB component will get the message as well and bring up MessageEvent. Now, we need HubChina to provide the requested product. Therefore, we have an external application to capture the MessageEvent from ESB component, and write relevant logic in to invoke HubChina service to provide the product. See the below diagram:

Step 0: Start Service [project: SAOTEngine]

C#
//service engine has been abstracted, it is not able to recognize unknown type object. 
//AddKnowType method will tell service engine to be able to serialize the 
//given type object 
ServiceController.AddKnowType(typeof(Inventory));
            ServiceController.AddKnowType(typeof(Inventory[]));
//open, close event to notify external application if the service is successfully opened.
            ServiceController.ServiceOpenedEvent += 
		new EventHandler(ServiceController_ServiceOpenedEvent);
            ServiceController.ServiceFaultEvent += 
		new EventHandler(ServiceController_ServiceFaultEvent);

//open service for client invoking
ServiceController.OpenFunctionService<serviceadapter />(int.Parse(this.txtPort.Text), this.txtName.Text);
//open service for subscription
ServiceController.OpenMessageService(int.Parse(this.txtPort.Text), this.txtName.Text);

Step 0: Client connect service [Project:SAOTEndPoint]

C#
//client also need know the object type to deserialize the object 
FunctionServiceConnector.AddKnownType(typeof(SAOT.Inventory));
FunctionServiceConnector.AddKnownType(typeof(SAOT.Inventory[]));

//connect service, including subscription service 
this.MyService = new SAOTServiceStructre<istandardservice>
			("myservice",this.txtServer.Text);

//get subscription service client
MessageDistributor messager = this.MyService.MessageService;
            
//HeartBeat message indicate the connection is still ok
messager.HeartBeatFromServer += new EventHandler(Instance_HeartBeatFromServer);
//Disconnected message indicate the connection is broken
messager.ServerDisconnectedEvent += new EventHandler(messager_ServerDisconnectedEvent);
//Normal Message, the structure is predefined, please refer to the previous section
messager.MessageDelivered += new EventHandler<trilobita.saot.object.message>
				(messager_MessageDelivered);

Step 0: Service Bus connect Beijing hub as well as China hub [Project: ServiceBusMonitor, SAOTServiceBus]

C#
//client also need know the object type to deserialize the object 
FunctionServiceConnector.AddKnownType(typeof(SAOT.Inventory));
FunctionServiceConnector.AddKnownType(typeof(SAOT.Inventory[]));

//add beijing service
BusEngine.RegisterNewService("Beijing Hub", "net.tcp://localhost:8886/Inventory");
//add china service
BusEngine.RegisterNewService("China Hub", "net.tcp://localhost:8887/Inventory");
BusEngine.Disconnected += new EventHandler(BusEngine_Disconnected);
BusEngine.HeartBeatMessage += new EventHandler(BusEngine_HeartBeatMessage);
BusEngine.MessageDelivered += new EventHandler
		<trilobita.saot.object.message>(BusEngine_MessageDelivered);

Step 1: Beijing Client Request New Product [Project: InventoryClient]

C#
//since the service is abstracted, we use reflect to invoke remote method
this.GetService().Inovke("ChangeInventory", this.txtUser.Text, inventory.ProductID, 
                    inventory.CurrentInventory-inv.CurrentInventory);
//T  is the unknown type. we use AddKnowType to let service know T
object obj = typeof(T).InvokeMember(methodname, BindingFlags.Static |
BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.InvokeMethod, 
		null, null, args);
return obj;

Step 2: Beijing Hub Service subscribe the inventory is not enough [Project: InventoryServer]

C#
//Inventory.cs
public bool ChangeInventory(string user, int number)
        {
            int restnumber = this.CurrentInventory + number;
            if (restnumber > 0)
            {
                this.CurrentInventory += number;
                if (InventoryChangedEvent != null) 
                    InventoryChangedEvent(null, new InventoryArgs 
		{ Inventory = this, ChangedNumber = number, Operator=user });
                return true;
            }
            else
            {
            //when the inventory is out of stock, trigger OutofStockEvent
                if (OutofStockEvent != null)
                    OutofStockEvent(null, new InventoryArgs 
		{ Inventory = this, ChangedNumber = number, Operator = user });
                return false;
            }
            
        }
 //ServiceAdapter.cs       
Inventory.InventoryChangedEvent += 
	new EventHandler<inventoryargs>(Inventory_InventoryChangedEvent);
static void Inventory_OutofStockEvent(object sender, InventoryArgs e)
        {
            Trilobita.SAOT.Object.Message message = new Trilobita.SAOT.Object.Message();
            message.Sender = e.Operator;
            message.PrimaryID = e.Inventory.ProductID.ToString();
            message.Title = e.ChangedNumber.ToString();

            Trilobita.SAOT.Service.MessageBox box = 
		new Trilobita.SAOT.Service.MessageBox("OutofStock");
            box.MessageEnable = true;
            box.EmailEnable = false;
            box.DatabaseEnable = false;
            //subscribe that inventory is out of stock
            box.SendMessage(message);
        }
</inventoryargs>

Step 3: Service Bus request inventory from China hub [Project: ServiceBusMonitor]

C#
//get china hub service and request for change inventory
//FmMonitor.cs
this.ChinaFunction.Inovke("ChangeInventory",e.Sender,productid,changednumber);

Step 4: ... I didn't really implement it. By default, china hub will fulfill the request.

ESB.jpg

How to Demonstrate the Application

  1. Double click SAOT.exe, confirm the ServicePort textbox value is 8886, Service Name is Inventory. Click Confirm to open the service, a message will display on the form if the service is successfully opened. SAOT Service is for remote call, MessageSender is for subscription. This service simulates Beijing Hub.
  2. Repeat step 1, only change the ServicePort value to 8887. This service simulates China Hub.
  3. Double click ServiceBusMonitor.exe, make sure the list services are connected. This is ESB application.
  4. Double click ClientTest.exe and click the confirm button if you exactly followed the upper steps. The light on top-left corner will flash if the connection is good.
  5. In ClientTest form, input one product name and default inventory, like product name: computer, inventory:50. Switch to first SAOT.exe (Beijing Hub), you will see computer with inventory 50. Switch to the second SAOT.exe (China Hub), you will see computer with inventory 500, because I put one logic in ESB application, if there is a new product from Beijing Hub, China Hub will prepare 10 times inventory. Here is the point, the first SAOT.exe (Beijing Hub) doesn’t connect the second SAOT.exe (China Hub), and ClientTest.exe only connects the first SAOT.exe (Beijing Hub). New product inventory is started from ClientTest. Why the second SAOT.exe (China hub) can add 10 times inventory is because ServiceBusMonitor detected the new product inventory message from Beijing hub and added the China hub inventory automatically.
  6. In ClientTest form, change computer inventory from 50 to 40 (to put the demonstration simple, I didn’t pup up a form to request the number of product). It equals to we request 10 computers. Switch to Beijing hub, you will see the product inventory changed to 40.
  7. In ClientTest form, change computer inventory from 40 to -10 (sorry, I just want to take 50 computers). Switch to Beijing hub, the number didn’t change. Switch to China hub, the number is 450. The logic here is, when client requests 50 computer, Beijing hub will send out OutOfStock message, ServicebusMonitor gets the message and transfers 50 computers to Beijing hub to keep Beijing hub inventory on a safe level.

Picture_2.png - Click to enlarge image

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)