A simple distributed asynchronous component

With the Xcoordination Application Space (AppSpace) distributing asynchronous components is (almost) transparent. Whether an async component is running locally or remotely does not make a difference in your code. To prove this, here´s a simple ping service which just returns a string you send to it.

Ping service v1 - one way communication

The first version of the ping service will focus on establishing the communication between client and service. So its contract is kept most simple. No return value is passed back to the client yet.
Ping service contract v1
using Microsoft.Ccr.Core;
using XcoAppSpace.Core;

public class PPingContract : Port<string>
{}
Ping service implementation v1
public class PingService : PPingContract
{
    [XcoConcurrent]
    void ProcessPing(string text)
    {
        Console.WriteLine("processing: {0}", text);
    }
}
So much for the service definition. It´s as simple as it can get. But how do you publish it?
Ping service host
using(XcoAppSpace host = new XcpAppSpace("tcp.port=9001"))
{
    host.RunWorker<PPingContract, PingService>("pingservice");
   
    Console.ReadLine(); // keep the host running while waiting for connections
}
That´s it. Just 2 lines of code to host an asynchronous component with the AppSpace to be used remotely. Compare this with using an async component locally. There´s no difference.

Or, to be more precise, there is no difference in the programming model. You instanciate an AppSpace and run the async service in it.

The difference is just in how you initialize the AppSpace. By passing "tcp.port=9001" to it, you tell the instance to listen for connection requests on TCP port 9001. This config string option causes the AppSpace to start with the build-in TCP socket communication service. If you´d pass in "wcf.port=9001" it would create a WCF ServiceHost instead and make the service available via the net.tcp binding.

Naming the async component instance is optional, but best practice.

So, if hosting the async service is just 2 lines of code, how much code do you need to use it? How would a ping service client look like?
Ping client v1
using Microsoft.Ccr.Core;
using XcoAppSpace.Core;

using(XcoAppSpace client = new XcoAppSpace("tcp.port=0"))
{
    PPingContract p = client.ConnectWorker<PPingContract>("localhost:9001/pingservice");

    p.Post("hello, world!");

    Console.ReadLine(); // keep client alive while messages get sent in the background
}
Can you spot the difference between client and service host? There is almost none. That´s the beauty of the AppSpace. Client and server are symmetric. Both instanciate an AppSpace. Both get access to an async component instance by calling xxxWorker<>(). The host is calling RunWorker<>() to actually run and host it; the client is calling ConnectWorker<>() to use an existing remote instance of an async component.

Of course the client needs to use the same transport medium like the server. That´s why the config string uses tcp.port=, too. But the port is 0 to let the AppSpace select one itself. No need for a client to know its port. (But if you later want to know which one got assigned to the client space instance, check its Address property.)

The parameter passed to ConnectWorker<>() designates the remote worker instance. It´s a combination of the remote space´s address and the worker´s name (see the name parameter to RunWorker<>() in the host). The space´s address is defined by the transport medium you selected. It´s servername:port for TCP oder HTTP communication, but if you had selected the Jabber communication service with this config string in the host - "jabber.jid=xco001@jabber.org;jabber.password=xyz" -, then the worker´s address would have been "xco001@jabber.org/pingservice".

As you can see, once you start working with local asynchronous components it´s just a tiny step to distribute them. The difference is just in selecting a transport protocol when instanciating an AppSpace. All async components started in a space then are automatically published. (If you don´t like that, set their publishing flag to Unpublished when calling RunWorker.)

Ping service v2 - Request/Response

One way communication is easy, as you can see. But how communicate in a bidirectional manner, how to return a result from a service? We haven´t shown you this when talking about async components.

The solution is easy. Remember: the relationship between async clients and services is symmetric. So if a client sends service a request through a port, then it´s pretty obvious to also send back a result through a port. But which port? You can use a global, well-known port accessible to clients and services. Or you can let the client specify the port he wants to receive results on when sending the request. Let´s do that:
Ping service contract v2
public class PPingContract : Port<PPingContract.PingRequest>
{
    [Serializable]
    public class PingRequest
    {
         public string Text;

         public Port<string> Response;
    }
}
Since now the request no longer just is the data to process, but also carries the response port, the contract needs to be extended. You need a special request message type. Think of it as the async variant of a method signature.

ReturnType DoWork(ParamType0 p0, ParamType1 p1, ...)
becomes

[Serializable]
class DoWork
{
    public ParamType0 P0;
    public ParamType1 P1;
    ...
    public Port<ReturnType> Result;
}
The service then just needs to retrieve the port from the request, do its work, and in the end send back the result through this request-specific port:
Ping service implementation v2
public class PingService : PPingContract
{
    [XcoConcurrent]
    void ProcessPing(PingRequest req)
    {
        Console.WriteLine("processing: {0}", req.Text);
        ...
        req.Response.Post(req.Text);
    }
}
Although this is a symmetric (or duplex) 2-way communication the host application does not know anything about the client. The AppSpace is magically marshalling the client port to the server. No special measures needed. Just set up the response port before sending the request:
Ping client v2
using Microsoft.Ccr.Core;
using XcoAppSpace.Core;
using XcoAppSpace.Core.Extensions;

using(XcoAppSpace client = new XcoAppSpace("tcp.port=0"))
{
    PPingContract p = client.ConnectWorker<PPingContract>("localhost:9001/pingservice");

    //--- set up response port
    Port<string> pingResponses = client.Receive<string>(s => Console.WriteLine("  received: {0}", s));

    p.Post(new PPingContract.PingRequest() { Text="hello, world!", Response=pingResponses });

    Console.ReadLine();
}
Creating a port and binding a message handler to it is easy with the AppSpace extension method XcoAppSpace.Receive<>() as you can see above. No need to worry about CCR arbiters and a DispatcherQueue.

Once the response port is in place include it in the request and send it off. So much for the most common scenario in distributed applications.
Async messages are always one-way messages, so setting up a request/response communication is a tad cumbersome, you´ll soon come to appreciate its conceptual simplicity. To prove this let´s turn the screw once more:

Ping service v3 - Notifications

Once you start distributing your code you´ll soon stumble across task taking some time to complete. A client asynchronously sends off data to be processed on a remote server - but how does the user know how far the processing has progressed? When is she to expect a result back? So why not let the server process notify a client about its progress? That´s what events are for in local synchronous communication.

As a tiny excercise for you please stop a minute or two and think about how you would go about to implement notifications with WCF. How much extra work would that be on top of the ping service had you developed it using WCF? If you´re no WCF expert you now might want to do some research on WCF callback contracts and duplex channels and instance contexts.

Or you might just as well skip it. Don´t bother about how much work notifications would mean in a WCF solution. Just see how easy notifications are with async components and the AppSpace. No need to learn any new concepts. You´ve all that´s required already in your hands.

So let´s quickly expand the ping scenario: For each character in the text received from the client the service sends back a notification to the client:

for(int i=0; req.Text.Length; i++)
    // notify client
How does the contract and the client need to be adapted for this new requirement?
Ping service contract v3
public class PPingContract : Port<PPingContract.PingRequest>
{
    [Serializable]
    public class PingRequest
    {
         public string Text;

         public Port<string> Response;
         public Port<int> Progress;
    }
}
Physically there is no difference between a result returned to the client and a notification. Conceptually they differ, but for both just a port is needed.
Ping service implementation v3
public class PingService : PPingContract
{
    [XcoConcurrent]
    void ProcessPing(PingRequest req)
    {
        Console.WriteLine("processing: {0}", req.Text);

        //--- notify client
        for(int i=0; i<req.Text.Length; i++)
             req.Progress.Post(i);

        req.Response.Post(req.Text);
    }
}
The service receives a port to send notifications to with the request from the client. Just add a port to the message contract and set it up in the client.
Ping client v3
using Microsoft.Ccr.Core;
using XcoAppSpace.Core;
using XcoAppSpace.Core.Extensions;

using(XcoAppSpace client = new XcoAppSpace("tcp.port=0"))
{
    PPingContract p = client.ConnectWorker<PPingContract>("localhost:9001/pingservice");

    //--- set up response port
    Port<string> pingResponses = client.Receive<string>(s => Console.WriteLine("  received: {0}", s));

    //--- set up notification port
    Port<int> pingProgress = client.Receive<int>(i => Console.WriteLine("    progress info: {0}", i));

    p.Post(new PPingContract.PingRequest() { Text="hello, world!", Response=pingResponses, Progress=pingProgress });

    Console.ReadLine();
}
That´s all notifications are about in async components. This works locally as well as remotely.

Summary

We hope to have shown you, how easy it is to distribute services using the Xcoordination Application Space. Think local components, then think local asynchronous components. Become used to the CCR based local asynchronous programming model. Use all its tricks to connect local async components hosted in an Application Space. And then, finally, take those components and host them in difference Application Spaces. You´ll need to just create different processes and select a transport medium. If you like you can even locally simulate distribution like this:

using(XcoAppSpace host = new XcoAppSpace("tcp.port=9001"))
using(XcoAppSpace client = new XcoAppSpace("tcp.port=0"))
{
    host.RunWorker<PPingContract, PingService>("pingservice");
    
    PPingContract p = client.ConnectWorker<PPingContract>("localhost:9001/pingservice");
    ...
}
Our goal at Xcoordination is to make distributed software much easier to write than it is today. Upon the CCR´s very useful abstraction for local asynchronous programming we have built the Application Space to reach this goal. Now it´s your turn to tell us, if we´ve done a good job.

The samples here just scratched the surface of the AppSpace programming paradigm for component oriented applications. But they hopefully have wetted your appetite for more.

Last edited Jul 19, 2010 at 12:03 PM by thomass, version 10

Comments

No comments yet.