Introduction
There�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 Applications
This is a 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.
The 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 a 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 a Windows Form, or in DHTML or some other user interface component. On the other hand, if we had a 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. Lastly, 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 have 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, learning 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 the 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 Introduction
A 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 a new way in designing systems. We now think of a system as a well designed suite of components that is entirely based on 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 the 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 by which these messages can pass back and forth. These models are known as Message Exchange Patterns.
Message Exchange Patterns
There 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 error that occurs during the processing of the message is 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 on 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 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 its subscribers.
Implementation Details
With 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 are the SoapReceiver
class and the SoapSender
class.
SoapReceivers
A 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.
SoapSender
A 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 by setting its property explicitly. Next, call the Send
or BeginSend
methods Synchronously and Asynchronously respectively, and send a SoapEnvelope
class to the Destination
.
SourceCode Explanation
The 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 SOAPAction
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);
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 )
{
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":
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" );
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();
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>";
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 an 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 application 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!!!