Posts
58
Comments
103
Trackbacks
10
A Unit of Work Implementation for Webservices

This post is in response to my last one.  After a bit of refactoring (and lots of thinking), here's what I've come up with.

The first enabling refactoring I must make is to remove the response from the request.  Instead, I flip the relationship and wrap the response inside the request.  Yes, the relationship gets flipped.

Secondly, I'm introducing a base class for Requests.  This base class will include the Correlation Id for the message.  The correlation id is an optional, client-provided value.  Typically, it would be used to correlate responses and requests.  I'm including it for the convenience of my clients (in case they want to use it).

    public abstract class Request
    {
        private string _correlationId;

        public string CorrelationId
        {
            // this is a client-provided identifier for
            // easily matching request/response pairs on the 
            // client side
            get { return _correlationId; }
            set { _correlationId = value; }
        }
    }

We also want to embed the Response inside the request, so I'm introducting a generic class for convenience.

    public abstract class Request<T> : Request
    {
        private T _response;

        public T Response
        {
            get { return _response; }
            set { _response = value; }
        }
    }

Here's an example request message for our webservice:

    public class HelloRequest : Request<HelloResponse>
    {
        private string _from;

        public string From
        {
            get { return _from; }
            set { _from = value; }
        }
    }


And it's corresponding response:

    public class HelloResponse : Response
    {
        private string _message;

        public string Message
        {
            get { return _message; }
            set { _message = value; }
        }
    }

Ah, yes, don't forget the common type for our Responses:

    public abstract class Response
    {
        private bool _wasSuccessful;
        private string[] _errors;

        public bool WasSuccessful
        {
            get { return _wasSuccessful; }
            set { _wasSuccessful = value; }
        }

        public string[] Errors 
        {
            // a better implemention would use a dto for an error
            // providing an error code, error name, language-specific error,
            // etc
            get { return _errors; }
            set { _errors = value; }
        }
    }

This is our example scenario.  Let's get this simple case working first.  The pleasant side-effect from encapsulating the response inside the request is that it makes writing a message handler simpler.  Here's our interface:

    public interface IRequestHandler<T>
    {
        T Handle(T request);
    }

By removing the notion of input and output pair, and sticking simply with a single type to contain the interaction, we now have a very simple interface to implement.  This is akin to the client sending us a document to fill out.  We take the information they have provided to us and append the information they requested.

Our webservice implementation is now extremely simple:

        [WebMethod]
        public HelloRequest Hello(HelloRequest request)
        {
            IRequestHandler<HelloRequest> handler = DependencyContainer.Resolve<IRequestHandler<HelloRequest>>();
            return handler.Handle(request);
        }

And our handler (in this case), is also extremely simple:

    public class HelloHandler : IRequestHandler<HelloRequest>
    {
        public HelloRequest Handle(HelloRequest request)
        {
            HelloResponse response = new HelloResponse();
            response.Message = string.Format("Hello {0}!", request.From);
            response.WasSuccessful = true;
            request.Response = response;
            return request;
        }
    }

Let's look at some client code:

        private void btnHello_Click(object sender, EventArgs e)
        {
            HelloRequest request = new HelloRequest();
            request.From = txtName.Text;
            HelloRequest result;
            using (Service service = new Service())
            {
                result = service.Hello(request);
            }
            if (result == null)
            {
                throw new Exception("The result was null.");
            }
            MessageBox.Show(string.Format("The result message was:\n\n{0}",result.Response.Message));
        }


That's great, but where's my unit of work?  The beauty lies in the simplicity.  Here's the request:

    public class UnitOfWorkRequest : Request
    {
        private Request[] _requests;

        public Request[] Requests
        {
            get { return _requests; }
            set { _requests = value; }
        }
    }

Here's the handler:

    public class UnitOfWorkHandler : IRequestHandler<UnitOfWorkRequest>
    {
        public UnitOfWorkRequest Handle(UnitOfWorkRequest request)
        {
            for (int i = 0; i < request.Requests.Length; i++)
            {
                Request work = request.Requests[i];
                request.Requests[i] = Handle(work);
            }
            return request;
        }

        private Request Handle(Request request)
        {
            Type messageType = request.GetType();
            Type handlerType = typeof(IRequestHandler<>).MakeGenericType(messageType);
            object handler = DependencyContainer.Resolve(handlerType);
            object[] args = { request };
            return (Request)handlerType.GetMethod("Handle").Invoke(handler, args);
        }
    }

And here's the Webservice implementation:

        [WebMethod]
        public UnitOfWorkRequest ProcessWork(UnitOfWorkRequest request)
        {
            IRequestHandler<UnitOfWorkRequest> handler = DependencyContainer.Resolve<IRequestHandler<UnitOfWorkRequest>>();
            return handler.Handle(request);
        }


The client code looks exactly like what you would imagine:

        private void btnBoth_Click(object sender, EventArgs e)
        {
            List<Request> requests = new List<Request>();
            
            PingRequest ping = new PingRequest();
            ping.CorrelationId = "ping-123";
            ping.From = "Win App";
            requests.Add(ping);

            HelloRequest hello = new HelloRequest();
            hello.CorrelationId = "hello-124";
            hello.From = txtName.Text;
            requests.Add(hello);            

            UnitOfWorkRequest unitOfWork = new UnitOfWorkRequest();
            unitOfWork.CorrelationId = "unitofwork-1";
            unitOfWork.Requests = requests.ToArray();

            UnitOfWorkRequest result;
            using (Service service = new Service())
            {
                result = (UnitOfWorkRequest)service.Process(unitOfWork); //using generic webmethod
            }
            if (result == null)
            {
                throw new Exception("The result was null.");
            }

            if (result.Requests[0].CorrelationId != "ping-123")
            {
                throw new Exception("The Ping was not found or is out of order.");
            }
            PingRequest pingResult = (PingRequest)result.Requests[0];
            MessageBox.Show(string.Format("The ping was successfully received at {0}.", pingResult.Response.Received));

            if (result.Requests[1].CorrelationId != "hello-124")
            {
                throw new Exception("The Hello was not found or is out of order.");
            }
            HelloRequest helloResult = (HelloRequest) result.Requests[1];
            MessageBox.Show(string.Format("The message was:\n\n{0}", helloResult.Response.Message));
        }

If you notice the above code, I actually used my next (potential) step in refactoring.  Now that the Handlers are all injectable based on the message type, we technically only need a single webservice method for all our messages:

        [WebMethod]
        public Request Process(Request request)
        {
            // food for thought
            Type messageType = request.GetType();
            Type handlerType = typeof(IRequestHandler<>).MakeGenericType(messageType);
            object handler = DependencyContainer.Resolve(handlerType);
            object[] args = {request};
            return (Request)handlerType.GetMethod("Handle").Invoke(handler, args);
        }

The client can pass any of our messages into our application, then simply cast the result back to the same message type.  The entire interaction is encapsulated in a single message.

There is one caveat with the generic method above.  The xml serializer won't, by default, figure out how to handle your types if you remove the other webmethods.  You could either leave the old ones in place, or I believe this could be mitigated by building your dto's contract first (ie..either wsdl or xsd first).  I'll be trying this shortly.  If it works, I will be looking very hard at building a DSL (in boo?) to generate the wsdl or xsd for me.

I'm attaching a working prototype of all of the above for anyone who's interested.  There are no unit tests since it was only a prototype.  It is simply a proof of concept.

This particular example was building using legacy webservices (asmx), but given that the entire interaction between the service consumer and the service is through a very simple interface (IRequestHandler), using this with WCF should be a non-issue.  In fact, going the DSL route, it should be merely a configuration switch when generating the dto's (use WCF attributes or not).

As a side note, I ran into a set of new patterns while building this.  They are the patterns of Xml Schema design.  Did you know the Russian Doll pattern is the least extensible? lol

I would also recommend reading up on the Notification pattern, as creating a supertype for the Request Handlers would be fairly trivial (ie.. a stateless version of Martin's ServerCommand).  Also see Dave's post on the same topic.

One last thing, if you look closely at the resulting webservice, you will notice a subtle redefinition.  The webservice is not defined by the methods it exposes (ala RPC) but rather by the documents it can process (ala messaging).

You can grab the bits from here.

posted on Thursday, June 28, 2007 10:07 PM Print