Monday, May 28, 2012

Anonymous type + Dynamic + ServiceStack == Consuming cloud have never been easier

In this post, I will show you how to request a service without all those codegen'd classes.

What's wrong with WCF? Everything revolves around the toolchain(i.e. Visual Studio), e.g. every classes must be generated not only for the returned objects, but also for the class(es) of your service(s). WCF would have versioning problem too when you update a method and add a new parameter to it or arranging its parameters, the calling app would break when the parameter mismatches. An example, you have this function:

public void Save(string firstname, string lastname)

And then you add a new parameter later on:

public void Save(string firstname, string lastname, string middlename)

The calling app would break as it cannot find a Save method that has three parameters. In order to fix that, the dev need to update the service reference, then it will code-generate the service's class with the updated method and parameter(s).


Then let's say you notice that the parameter arrangement is not ideal, you need to switch the Middle Name and Last Name.

public void Save(string firstname, string middlename, string lastname)


Then that will silently break the existing app saving of record, let's say you call it like this before:

Save("John", "Lennon", "Winston");

With the updated service, the Last Name: Winston, will fall on Middle Name; and the Middle Name: Winston, will fall on Last Name.


Though that is not exactly a problem of WCF, but since the mechanism for Request-Response protocol is not so formalized with WCF, most WCF developers could fall into that potential problem. If the Save method was made to only accept a class from the get-go, you could re-design your service's Save mechanism without affecting the dependent client using the service.


The following is how ServiceStack is able to prevent that problem. With ServiceStack, the notion of request and response is so formalized well that it's nigh impossible to make your code brittle as the one we discussed above. The following is a typical ServiceStack code:

class Hello      
{
    public string Name { get; set; }
            
}

class HelloResponse
{
    public string Result { get; set; }      
    public int Viva { get; set; }
}

class HelloService : ServiceStack.ServiceHost.IService<Hello>
{
    
    public object Execute(Hello request) 
    {
        return  new HelloResponse { Result = "Good morning " + request.Name + "!", Viva = 1976 };
    }
    
}   

For a complete walkthrough how to make a ServiceStack service, read it at http://www.ienablemuch.com/2012/04/servicestack-setup-screenshots-guide.html. Or read it at http://www.servicestack.net/ServiceStack.Hello/

With ServiceStack, even you add a property on before or after of property Name, the existing code that call the ServiceStack will not fail, as ServiceStack can enforce (via interface) that your service's method's paramater can only accept a class type.


The ServiceHost's IService is very simple:

namespace ServiceStack.ServiceHost
{
    public interface IService<T>
    {
        object Execute (T request);
    }
}


So here's how you will call the ServiceStack on front-end (JsonServiceClient is in ServiceStack.Common.DLL):

public class MainClass
{
    public static void Main()
    {               
        var client = new ServiceStack.ServiceClient.Web.JsonServiceClient();                
        HolaResponse r =  client.Send<HolaResponse>("GET","http://127.0.0.1:8080/yourServiceStack/como-esta", new Hola { Name = "Michael Buen" });
        System.Console.WriteLine ("* {0}\n* {1}", r.Result, r.Viva);
    }
    
}


class Hola {
    public string Name { get; set; }
}
                
        
class HolaResponse {
    public string Result { get; set; }
    public int Viva { get; set; }
}


You can also make your class name different from ServiceStack's returned class. And with ServiceStack, you can avoid code generated classes (as opposed to WCF every-classes-are-generated approach), making your code very lightweight. Even when there are new methods added on your ServiceStack, there will be no such thing on your ServiceStack-consuming code analogous to WCF's Update Service Reference(on which by itself is another code-generated stub class mess), you just call the ServiceStack services via their RESTful urls. How frictionless is that? ツ



And with C# 3's anonymous type and 4's dynamic, you can eliminate those two classes altogether.
public class MainClass
{
    public static void Main()
    {   
        var client = new ServiceStack.ServiceClient.Web.JsonServiceClient();                
        dynamic r =  client.Send<string>("GET","http://127.0.0.1:8080/yourServiceStack/como-esta", new { Name = "Michael Buen" }).JsDeserialize();
        System.Console.WriteLine ("* {0}\n* {1}", r.Result, r.Viva);
    }
}


Output:
* Good morning Michael Buen!
* 1976


Happy Coding! ツ


By the way, the following is the JSON dynamic deserializer. Code sourced here: http://www.drowningintechnicaldebt.com/ShawnWeisfeld/archive/2010/08/22/using-c-4.0-and-dynamic-to-parse-json.aspx


using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.Collections;
using System.Web.Script.Serialization;
using System.Collections.ObjectModel;



namespace DynamicJson
{
    public class DynamicJsonObject : DynamicObject
    {
        private IDictionary<string, object> Dictionary { get; set; }

        public DynamicJsonObject(IDictionary<string, object> dictionary)
        {
            this.Dictionary = dictionary;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = this.Dictionary[binder.Name];

            if (result is IDictionary<string, object>)
            {
                result = new DynamicJsonObject(result as IDictionary<string, object>);
            }
            else if (result is ArrayList && (result as ArrayList) is IDictionary<string, object>)
            {
                result = new List<DynamicJsonObject>((result as ArrayList).ToArray().Select(x => new DynamicJsonObject(x as IDictionary<string, object>)));
            }
            else if (result is ArrayList)
            {
                result = new List<object>((result as ArrayList).ToArray());
            }

            return this.Dictionary.ContainsKey(binder.Name);
        }
    }

    public class DynamicJsonConverter : JavaScriptConverter
    {
        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
        {
            if (dictionary == null)
                throw new ArgumentNullException("dictionary");

            if (type == typeof(object))
            {
                return new DynamicJsonObject(dictionary);
            }

            return null;
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override IEnumerable<Type> SupportedTypes
        {
            get { return new ReadOnlyCollection<Type>(new List<Type>(new Type[] { typeof(object) })); }
        }
    }
}


namespace DynamicJson.Extensions
{
    public static class Helper
    {
        public static string JsSerialize(this object obj)
        {
            return new JavaScriptSerializer().Serialize(obj);
        }

        public static dynamic JsDeserialize(this string str)
        {
            var jss = new JavaScriptSerializer();
            jss.RegisterConverters(new JavaScriptConverter[] { new DynamicJsonConverter() });

            // Mono don't have this method:
            // return jss.Deserialize(str, typeof(object)) as dynamic;          

            // Just call the generic-styled method(of which Microsoft C# also has). Portability FTW!
            return jss.Deserialize<object>(str) as dynamic;
        }
    }

    
}


P.S.

Why WCF can't give me the feeling I can make the next twitter? ツ With ServiceStack,the possibility and scaling is there.

No comments:

Post a Comment