Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Fx 3.5, Workflow, WCF and "Durable Services"

Blogs

Mike Taulty's Blog

Elsewhere

I came across this idea of "Durable Services" in Workflow V3.5 and I thought I'd have a stab at explaining what I saw here.

"Durable Services" look to be a set of extensions to WCF that ship in System.WorkflowServices.dll.

From what I can see, "Durable Services" provide a mechanism for automatically persisting the state that your service needs to maintain during a conversation with a particular client until that client is "done" with the service. That state can survive both service re-starts and client re-starts (as long as the client persists enough information to get back to it).

In some ways, that sounds a bit like "session" to me. My understanding of session in WCF being;

  1. We attribute up our contract to mark operations that begin or complete a session.
  2. New client calls service. Infrastructure checks session token and because there's none present it ensures that the call is to a begin method.
  3. Service returns some additional header to client with session token in it.
  4. Client calls service again. Infrastructure checks session token and identifies client as a return visitor, allows calls to "intermediary" operations.
  5. Client calls complete and infrastructure tidies up session token in SOAP headers.

In a WCF service implementation we can either pick up the session id ourselves and use it to index into some kind of state that we're maintaining for each client that has a session with us or we can allow the WCF to help us out with that by using the PerSession instancing mode on the service class. If we go with PerSession instancing then session state is simply stored in the service implementation class itself and the WCF uses the session id in order to route messages from the right client through to the right implementation instance. The former is probably easier to get to work from the point of view of load-balancing and so on and the latter is probably an easier way to program.

If I remember correctly, session sits on top of the reliable messaging support in WCF and so to switch session on you also have to switch on reliable messaging.

Comparing that to "Durable Services" it looks like;

  1. We attribute up our contract to mark operations that begin or complete a conversation.
  2. New client calls service. Infrastructure checks context token and because there is none present ensures that the call is to a begin method.
  3. Infrastructure spins up a new service class instance. Operation completes. Infrastructure serializes state of service instance, stores it through a provider (SQL by default) and returns an identifier to that state bag back to the client in a context token (SOAP header or HTTP cookie).
  4. Client calls service again providing the context token. Infrastructure checks context token to identify the right serialized state, loads it from the DB and deserializes the service-class instance in order to deliver the call to the operation before serializing again.
  5. Client calls completion operation. Infrastructure uses context token to get rid of the state bag.

So this feels an awful lot like "session" to me except;

  1. There is the automatic facility to serialize state and deliver it back "inside of" a service instance.
    1. This presumably helps a lot with the service host process getting recycled during operation.
    2. This also presumably plays well with load-balancing as long as the serialization store is accessible to the server farm.
  2. It's based on a context token rather than a session id. I don't think there's any reliance on session (WS) or reliable messaging (WS) from what I can see.

With all of that said, I set out to build a simple service using the traditional method and then using the "Durable Service" method.

The Traditional Method

I wanted the simplest service possible so I defined the "Unique Number Service". All it does is return a unique number to a client for the duration of the client's conversation. Contract is;

[ServiceContract]
public interface IGenerateUniqueNumbers
{
    [OperationContract]
    Guid GetNewClientId();

    [OperationContract]
    int GetNextNumber(Guid clientId);

    [OperationContract]
    void ClientDone(Guid clientId);
}

and then the implementation (undoubtedly not the best in the world and using a flat file for persistence of the state) looks like;

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class UniqueNumberService : IGenerateUniqueNumbers
{
    private Dictionary<Guid, int> clientNumbers;

    public Guid GetNewClientId()
    {
        Guid clientNumber = Guid.NewGuid();

        lock (clientNumbers)
        {
            clientNumbers[clientNumber] = 0;
        }
        return (clientNumber);
    }
    public int GetNextNumber(Guid clientId)
    {
        int nextNumber = 0;

        lock (clientNumbers)
        {
            if (clientNumbers.ContainsKey(clientId))
            {
                clientNumbers[clientId]++;
                nextNumber = clientNumbers[clientId];
            }
            else
            {
                throw new InvalidOperationException("Not a client!");
            }
        }
        return (nextNumber);
    }
    public void ClientDone(Guid clientId)
    {
        lock (clientNumbers)
        {
            if (clientNumbers.ContainsKey(clientId))
            {
                clientNumbers.Remove(clientId);
            }
            else
            {
                throw new InvalidOperationException("Not a client!");
            }
        }
    }
    public void Load()
    {
        try
        {
            using (FileStream fs = new FileStream("persistence.bin", FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                clientNumbers = formatter.Deserialize(fs) as Dictionary<Guid, int>;
            }
        }
        catch
        {
        }
        if (clientNumbers == null)
        {
            clientNumbers = new Dictionary<Guid,int>();
        }
    }
    public void Persist()
    {
        try
        {
            using (FileStream fs = new FileStream("persistence.bin",
                FileMode.OpenOrCreate, FileAccess.Write))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(fs, clientNumbers);
                fs.Close();
            }
        }
        catch
        {
        }
    }
}

with a little bit of console hosting code;

 

 static void Main(string[] args)
    {
        UniqueNumberService uns = new UniqueNumberService();
        uns.Load();

        ServiceHost host = new ServiceHost(uns);
        host.Open();

        Console.ReadLine();

        host.Close();
        uns.Persist();
    }

 

and I just used some standard configuration in order to host this over the netTcpBinding with security switched off. That was it. So, essentially the "complexity" is that we manage a Dictionary<Guid,int> and we dish out integers to clients from that Dictionary. We would also have to know when the host process was unloading because that's when we save the dictionary although I guess we could just serialize it at the end of each method call.

 

The Durable Service Method

I re-implemented what I've got using the durable service method. This simplifies things quite a bit - firstly, the contract only now has 2 methods as the GetNextNumber operation becomes both the means to initiate the conversation and to continue it;

[ServiceContract]
public interface IGenerateUniqueNumbers
{
    [OperationContract]
    int GetNextNumber();

    [OperationContract]
    void ClientDone();
}

The implementation gets quite a bit simpler as well;

[DurableServiceBehavior]
[Serializable]
public class UniqueNumberService : IGenerateUniqueNumbers
{
    private int nextNumber;

    [DurableOperationBehavior(CanCreateInstance=true)]
    public int GetNextNumber()
    {
        int next = Interlocked.Increment(ref nextNumber);

        return (next);
    }
    [DurableOperationBehavior(CompletesInstance=true)]
    public void ClientDone()
    {
    }
}

Note that the service class itself is marked as Serializable so that the framework can serialize my state (the integer in my case) before each method call is delivered and after each method call completes. Whilst this code will work it doesn't really make sense unless I configure it to use one of the context-ful bindings in FX 3.5 (or string together one of my own) so I configured it with;

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="myConnection" connectionString="server=.;database=servicepersistence;integrated security=sspi"/>
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service name="UniqueNumberService" behaviorConfiguration="serviceBehave">
        <endpoint address="net.tcp://localhost/uns"
                  binding="netTcpContextBinding"
                  contract="IGenerateUniqueNumbers"
                  bindingConfiguration="serviceConfig"/>
      </service>
    </services>
    <bindings>
      <netTcpContextBinding>
        <binding name="serviceConfig">
          <security mode="None"/>
        </binding>
      </netTcpContextBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehave">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:9100/uns"/>
          <persistenceProvider type="System.ServiceModel.Persistence.SqlPersistenceProvider, System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                               connectionStringName="myConnection" 
                               persistenceOperationTimeout = "00:00:10"
                               lockTimeout="00:01:00"
                               serializeAsText="true"/>          
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Note that the state persistence store is pluggable and notice that the netTcpContextBinding brings in this new context token header that flows from client to service and back again. Note also that the scripts to create the database that this code relies upon are in;

c:\windows\microsoft.net\framework\V3.5.20404\SQL

With that in place, my client is a simple program;

    static void Main(string[] args)
    {
        GenerateUniqueNumbersClient proxy = new GenerateUniqueNumbersClient();

        while (true)
        {
            Console.WriteLine("Hit return to request next number, X to end");

            if (string.Compare(Console.ReadLine(), "x", true) == 0)
            {
                break;
            }
            Console.WriteLine("Latest number [{0}]", proxy.GetNextNumber());
        }
    }

with a similar config (not given here as it's as you would expect). Now, if in my client I wanted to save a particular context and return to it when I re-ran the program then I would do something like below where we pull out the context (seems to just be a IDictionary<XmlQualifiedName, string>) and we store/load it on the client side so that the client can get back to the same state bag on the service should it want to.

class Program
{
    static void Main(string[] args)
    {
        GenerateUniqueNumbersClient proxy = new GenerateUniqueNumbersClient();

        IContextManager mgr = proxy.InnerChannel.GetProperty<IContextManager>();

        IDictionary<XmlQualifiedName, string> ctx = LoadContext();

        if (ctx != null)
        {            
            mgr.SetContext(ctx);
        }

        while (true)
        {
            Console.WriteLine("Hit return to request next number, X to end");

            if (string.Compare(Console.ReadLine(), "x", true) == 0)
            {
                break;
            }
            Console.WriteLine("Latest number [{0}]", proxy.GetNextNumber());
        }
        Console.WriteLine("Do you want to keep the context for next time?");

        if (string.Compare(Console.ReadLine(), "y", true) == 0)
        {
            ctx = mgr.GetContext();
            PersistContext(ctx);
        }
        else
        {
            proxy.ClientDone();
            File.Delete(fileName);
        }
    }
    static void PersistContext(IDictionary<XmlQualifiedName, string> context)
    {
        try
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                BinaryFormatter fb = new BinaryFormatter();
                fb.Serialize(fs, context);
                fs.Close();
            }
        }
        catch
        {
        }
    }
    static IDictionary<XmlQualifiedName, string> LoadContext()
    {
        IDictionary<XmlQualifiedName, string> ctx = null;

        try
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                BinaryFormatter fb = new BinaryFormatter();
                ctx = fb.Deserialize(fs) as IDictionary<XmlQualifiedName, string>;
                fs.Close();
            }
        }
        catch
        {
        }
        return (ctx);
    }
    static readonly string fileName = "context.bin";
}

That's it - if I had to sum it up then I'd describe it as;

If you're happy storing your service-side state as serializable members of your service instance class and with using runtime serialization to persist that for you and if you're happy with the way in which the context identifier is flowed for you then it's a nice way of getting an easier programming model around managing state for multiple clients.


Posted Mon, Apr 30 2007 6:43 AM by mtaulty

Comments

» Fx 3.5, Workflow, WCF and "Durable Services" wrote &raquo; Fx 3.5, Workflow, WCF and &quot;Durable Services&quot;
on Mon, Apr 30 2007 8:37 AM
Christopher Steen wrote Link Listing - April 30, 2007
on Mon, Apr 30 2007 7:40 PM
Using enterprise architecture framework to map services and set their granularity [Via: nattYGUR ] Silverlight...