SOA: The Subscriber-Publisher Model, Introduction and Implementation
By DGoins
This article explains a brief introduction to SOA and a Subscriber-Publisher model, along with how to implement one using WSE Soap Receiver and WSE Soap Sender classes inside a Windows .NET Application.
Download source files - 30 Kb IntroductionThere’s a new buzzword going around the Developer’s ear. Have you heard it yet? It’s called SOA. It is an acronym for Service Oriented Architecture. This new concept is not really new; it is old in its definition; however the marketing departments of all the big wig companies are re-singing its tune. Microsoft is singing its SOA pop song with .NET building blocks, WSE, ASP.NET and XML. IBM is using another artist called On Demand, and others are following suit as well.
Traditional Distributed ApplicationsThis is new way in thinking especially for a Component architect. Let’s think about it for a second. In Object Oriented Programming from a windows developer perspective, we designed basic Com applications and Com Components around a basic three tier approach. Three Layers: one called the user interface layer. The second called the business logic layer, and the third known as the data layer. Our basic design was simple in concept, if a class had code that interacted with the User interface such as a windows application, or a web page, then we wrote the code on the user interface side. We wrote the code in the windows form, or in DHTML or some other user interface component. On the other hand, if we had logic that dealt with business rules, processes, day to day logic that was the real problem we were trying to solve, we’d place that code in one to many separate components within our business layer, and install that file on a business logic server such as MTS/COM+ services. Last, any logic that dealt with accessing a data source, such as a file, database, server and etc, we’d place that on the server or in a component that ran on the server like using stored procedures.
In OOP, we dealt with many sometimes frustrating terms and concepts. We had to learn Abstraction, Inheritance, Implementation, Encapsulation, Interfaces, Composition, Aggregation and other terms. OOP became so complex that we created diagrams to visually represent all of these terms and conditions called UML. We invested a lot of time and effort, and learning into this paradigm, yet, we still ran into One MAJOR subtly, one major flaw: Interoperability.
COM did not communicate with EJB, ISAM databases did not communicate with COM, CRM systems did not communicate with EJB, nor COM. These technologies did not allow for a smooth communication pattern automatically. We had to finesse and “tweak” each technology to be able to link these disparate designed systems together. The effort and hard work to make this possible caused many strains, and morphing of technologies. Even to the point where some technologies had many add-on features that are a 180 degree turn from its original designed architecture. Just look at ADO from its first versions to its current one, version 2.8 or so, and the most important change from COM technology to .NET. This eventually led to the inevitable.
SOA IntroductionA new paradigm in software design: SOA. So you mentioned all these songs the big wig companies are singing what are they all about? SOA is new way in designing systems. We now think of a system as a well designed, suite of components that is entirely based off of message communication patterns of what a component does (service). It is an idea to center the design of a system(s) on an “orchestrated” suite of services through message communication. These services talk to each other by passing Xml forms of messages back and forth to each other; this is the focal point.
An SOA System
The image to the left shows a standard depiction of a service, with three prongs sticking out from a triangle. These stubs, or points are known as “Endpoints” or “touchpoints”. They are the portals that allow the xml messages to come into and be sent out of some network using any protocol. This is quite the opposite of Distributed COM, where we were forced to use a proprietary protocol, and a special port to send network packets of data across the wire.We could talk about services and how to architect them and all the new benefits that SOA offers, however the main point of this introduction is to talk about how these services talk to one another: messages. The basis of a message is Xml. Xml is the format, and within this format we have a very specific format called Simple Object Access Protocol (SOAP). SOAP messages contain all data needed to perform some unit of work. It may also contain security specifics like username and password, certificate information, and other secure concepts such as encryption and hashing.
SOAP messages give different platforms such as Unix a way to communicate with others like Microsoft. SOA, and SOAP solves our interoperability problems. The data being passed is all text, and all platforms understand text. Knowing this, the industry has comprised a set of templates, or models of ways in which these messages can pass back and forth. These models are known as Message Exchange Patterns.
Message Exchange PatternsThere are many Message Exchange Patterns (MEP). There is the first implementation of SOA called Xml web services which uses the Request/Response (Remote Procedure Call) MEP. There is also the Dead Letter Channel pattern, where a message is sent to a service, and any errors that occur during the processing of the message are sent to a special “node” or Channel. These errors, more often referred to as Soap Faults are then queued on to a stack. A client application can then retrieve the messages from this queue. There is the Message Router pattern where a message is routed to another service(s) based off of its content, or security credentials. There is the Message Splitter pattern, which splits or combines messages and sends them to other destinations, and most notably, there is the Publisher-Subscriber pattern.
The Publisher-Subscriber pattern is where a message comes into a service to notify it that it wants to listen for “messages that a publisher broadcasts to its listeners”. A Client application sends a “subscription” request message about who it is, and where it can receive these “responses” from the publisher. The application, be it physically installed on the Client or the Server, can then run, and wait on the Publisher service to generate these responses and send it back to its subscribers.
Client Application makes a Subscription Request
The Publisher service would then execute its logic and eventually loop through its collection of subscribers and send the message on. The publisher service may even send a “Fire and Forget” type of message to all the subscribers. This is because the Publisher service may not necessarily be concerned with who gets the message successfully, such as message confirmation.
Publisher service sends a copy of the message to subscribers
Implementation DetailsWith Microsoft’s implementation of the community Standard: WSA, we can implement the Publisher Subscriber model of SOA. Microsoft implements this standard using an Add-on tool called Web Service Enhancements (WSE). Microsoft is currently in version 2.0 service pack 2 of the WSE toolkit, which is the version we’ll use to implement this model.
WSE gives us many classes and technologies we can use inside of a .NET based application. The two classes we’ll focus on is a SoapReceiver class and the SoapSender class.
SoapReceiversA SoapReceiver is a class that inherits/implements from the IHttpHandler interface. (see my article on using WSE with SimpleHandlerFactory) This class provides all the functionality you need to receive soap messages. To use this class just create a custom class and inherit from the SoapReceiver class. This class asks that you override the Receive method. This is the method that receives the soap message from a SoapSender class. Here is the signature of the Receive Method:
protected override void Receive ( SoapEnvelope envelope )
The receive method, takes in a WSE SoapEnvelope class as its argument, which is the Soap message passed in by the SoapSender. The SoapEnvelope class inherits from the System.Xml.XmlDocument class and contains many properties and instance methods that allow a developer to read and parse the Xml soap message being sent in.
SoapSenderA SoapSender is a class that inherits from the abstract SoapPort class. This class basically corresponds to a Filter that allows you modify the input of the Soap Message and the output of the message. This base class allows you to control the sending and receiving of the soap message. To use the SoapSender class, create an instance of this class and set its Destination property (an Uri) either through its constructor or setting its property explicitly. Next, call the Send or BeginSend methods to Synchronously and Asynchronously respectively, send a SoapEnvelope class to the Destination.
Source Code ExplanationThe Demo application is separated into two separate projects. A Publisher Windows Application that hosts the Publisher Service, and the Client Subscription Application that hosts the Client Subscription Response Service.
The Publisher Application is broken up into two pieces. A Publisher Windows .NET application, and a Publisher Class. The Publisher Application is a basic windows forms application that displays subscribers through a listbox in real time subscribing to the Publisher, and unsubscribing from the Publisher. It also contains a Textbox that gives the Publisher the ability to publish an article or data to all the listed subscribers. When the Publisher clicks on the Publish Article button, a file is created on the server, and then a copy of its contents is sent to all the subscribers.
The publisher Class is a custom class that inherits from the SoapReceiver Class. It overrides the Receive method and checks for a Soap Action on the SoapEnvelope. It parses the SoapAction to determine if the message being sent in is a Subscription Request, or a Unsubscription request. Also, while the Publisher application continues to run, if the Publisher decides to publish an article, it is then sent using a SoapSender to all the listening Subscribers.
Publisher Code:using System;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Messaging;
using Microsoft.Web.Services2.Addressing;
using System.Web.Services.Protocols;
using System.Xml;
using System.Collections;
using System.IO;
using System.Collections.Specialized;
namespace ArticlePublisherApp
{
internal class Literals
{
static Literals()
{
Literals.LocalhostTCP = "soap.tcp://" +
System.Net.Dns.GetHostName() + ":";
}
internal readonly static string LocalhostTCP;
}
public delegate void NewSubscriberEventHandler(string subscriberName,
string ID, Uri replyTo );
public delegate void RemoveSubscriberEventHandler( string ID);
/// <SUMMARY>
/// Summary description for Publisher.
/// </SUMMARY>
public class Publisher : SoapReceiver
{
public event NewSubscriberEventHandler NewSubscriberEvent;
public event RemoveSubscriberEventHandler RemoveSubscriberEvent;
public Publisher()
{
_subscribers = new Hashtable();
fsw = new FileSystemWatcher();
System.Configuration.AppSettingsReader configurationAppSettings =
new System.Configuration.AppSettingsReader();
string folderWatch = ((string)(configurationAppSettings.GetValue("Publish." +
"PublishFolder", typeof(string))));
try
{
fsw = new System.IO.FileSystemWatcher(folderWatch);
}
catch
{
throw new Exception("Directory '" + folderWatch
+ "' referenced does not exist. " +
"Change the fileName variable or create this directory in " +
"order to run this demo.");
}
fsw.Filter = "*.txt";
fsw.Created += new FileSystemEventHandler(fsw_Created);
fsw.Changed += new FileSystemEventHandler(fsw_Created);
fsw.EnableRaisingEvents = true;
}
protected void OnNewSubscriberEvent(string Name, string ID, Uri replyTo)
{
if (NewSubscriberEvent != null)
NewSubscriberEvent(Name, ID, replyTo);
}
protected void OnRemoveSubscriberEvent(string ID)
{
if (RemoveSubscriberEvent != null)
RemoveSubscriberEvent(ID);
}
private void AddSubscriber(string ID, Uri replytoAddress, string Name)
{
SoapSender ssend = new SoapSender(replytoAddress);
SoapEnvelope response = new SoapEnvelope();
response.CreateBody();
response.Body.InnerXml = String.Format("<?xml:namespace prefix=x />" +
"<x:AddSubscriber xmlns:x=\"urn:ArticlePublisherApp:Publisher\">"
"<NOTIFY>Name: {0} ID: {1}</NOTIFY></x:AddSubscriber>", Name, ID);
Action act = new Action("response");
response.Context.Addressing.Action = act;
ssend.Send(response);
_subscribers.Add ( ID, new Subscriber(Name,replytoAddress, ID) );
OnNewSubscriberEvent(Name, ID, replytoAddress);
}
private void RemoveSubscriber(string ID, Uri replytoAddress)
{
if (_subscribers.Contains(ID) )
{
_subscribers.Remove(ID);
SoapSender ssend = new SoapSender(replytoAddress);
SoapEnvelope response = new SoapEnvelope();
response.CreateBody();
response.Body.InnerXml = String.Format("<x:RemoveSubscriber xmlns:x=\"" +
"urn:ArticlePublisherApp:Publisher\">" +
"<NOTIFY>ID: {0} Removed</NOTIFY>" +
"</x:RemoveSubscriber>", ID);
Action act = new Action("response");
response.Context.Addressing.Action = act;
ssend.Send(response);
OnRemoveSubscriberEvent(ID);
}
}
protected override void Receive( SoapEnvelope envelope )
{
//Determine Action if no SoapAction throw exception
Action act = envelope.Context.Addressing.Action;
if (act == null)
throw new SoapHeaderException("Soap Action must be set",
new XmlQualifiedName());
string subscriberName = String.Empty ;
string subscriberID = String.Empty;
switch (act.ToString().ToLower())
{
case "subscribe":
//add new subscriber
subscriberName = envelope.SelectSingleNode ( "//name").InnerText ;
subscriberID = System.Guid.NewGuid().ToString();
AddSubscriber(subscriberID,
envelope.Context.Addressing.From.Address.Value,
subscriberName);
break;
case "unsubscribe":
subscriberID = envelope.SelectSingleNode("//name") .InnerText ;
RemoveSubscriber(subscriberID,
envelope.Context.Addressing.From.Address.Value);
break;
default:
break;
}
}
private void fsw_Created(object sender, System.IO.FileSystemEventArgs e)
{
Uri uriThis = new Uri (Literals.LocalhostTCP + "9090/Publisher" );
// Send each subscriber a message
foreach(object o in _subscribers)
{
DictionaryEntry de = (DictionaryEntry)o;
Subscriber s = (Subscriber)_subscribers[de.Key];
SoapEnvelope responseMsg = new SoapEnvelope ();
FileStream fs = new FileStream(e.FullPath ,FileMode.Open,
FileAccess.Read , FileShare.ReadWrite );
StreamReader sr = new StreamReader(fs);
string strContents = sr.ReadToEnd() ;
sr.Close();
fs.Close();
// Set the From Addressing value
responseMsg.Context.Addressing.From = new From ( uriThis );
responseMsg.Context.Addressing.Action = new Action( "notify");
responseMsg.CreateBody();
responseMsg.Body.InnerXml = "<x:ArticlePublished xmlns:x=\"" +
"urn:ArticlePublisherApp:Publisher\">" +
"<NOTIFY><FILE>" + e.Name + "</FILE><CONTENTS>"
+ strContents + "</CONTENTS></NOTIFY></x:ArticlePublished>";
// Send a Response Message
SoapSender msgSender = new SoapSender (s.ReplyTo );
msgSender.Send ( responseMsg );
}
}
internal StringCollection GetSubscribers()
{
StringCollection coll = new StringCollection();
foreach(Subscriber s in _subscribers)
{
coll.Add(String.Format("Name - {0}\t ID - {1}\t Reply To Uri {2}",
s.Name, s.ID, s.ReplyTo.ToString()));
}
return coll;
}
private Hashtable _subscribers;
private FileSystemWatcher fsw;
}
public class Subscriber
{
public string Name;
public Uri ReplyTo;
public string ID;
public Subscriber(string name, Uri replyTo, string id)
{
Name = name;
ReplyTo = replyTo;
ID = id;
}
}
}
Client Subscriber The Client Subscriber application is also broken up into two pieces: A client Subscriber Windows .NET application, and a Subscriber Class. The Client Subscriber application is a basic windows forms application that contains a MainMenu, and a StatusBar, along with a ReadOnly Textbox. The MainMenu has a MenuItem that has the ability to Send a Subscription request to the Publisher, along with a Unsubscription Request to stop subscribing to the Publisher. The StatusBar displays the Register ID of the Client when registered. The ReadOnly Textbox displays any article that is published from the Publisher, at any given time the Publisher decides to publish the article/data. The Subscriber Class is a custom class that inherits from the SoapReceiver class. It overrides the Receive method and checks for a SoapAction Header on the SoapEnvelope. It parses the SoapAction to determine if the message being sent back from the Publisher is a simple response to the subscription or unsubscription request, or a notify message to let the Subscriber Form know that an article is being sent from the Publisher. To really test the applicaiton out, start multiple instances of the Client Subscription Application.
Subscriber Class Code: using Microsoft.Web.Services2.Messaging;using Microsoft.Web.Services2.Addressing;
using System.Web.Services.Protocols;
using System.Xml;
namespace ClientSubscriptionApp
{
public delegate void ResponseFromServerEventHandler(string Response);
public delegate void SubscriptionNotificationEventHandler(string Notification);
public class SubscriberNotification : SoapReceiver
{
public event ResponseFromServerEventHandler ResponseFromServerEvent;
public event SubscriptionNotificationEventHandler SubscriptionNotificationEvent;
public SubscriberNotification()
{ }
protected void OnResponseFromServer (string Response)
{
if (ResponseFromServerEvent != null)
ResponseFromServerEvent(Response);
}
protected void OnSubscriptionNotification(string Notification)
{
if (SubscriptionNotificationEvent != null)
SubscriptionNotificationEvent(Notification);
}
protected override void Receive(Microsoft.Web.Services2.SoapEnvelope envelope)
{
string sResponse = string.Empty;
Action act = envelope.Context.Addressing.Action;
if (act == null)
throw new SoapHeaderException("Soap Action must be present",
new XmlQualifiedName()) ;
switch (act.Value.ToLower() )
{
case "response":
sResponse = envelope.SelectSingleNode("//notify").InnerText ;
OnResponseFromServer(sResponse);
break;
case "notify":
sResponse = envelope.SelectSingleNode("//notify").InnerText ;
OnSubscriptionNotification(sResponse);
break;
default :
break;
}
}
}
}
Happy Coding!
If you like this SOA tune… Stay tuned for BizTalk Server 2004 Articles as well!!!