28 Ocak 2011 Cuma

Cin Fikir: No Client WCF !

Merhaba Arkadaşlar;

Bugünden itibaren, "Cin Fikir" diye ifade ettiğim; kodlamaktan da oldukça hoşlandığım bir kaç sıradışı fikrimi sizlerle paylaşmaya karar verdim. Cin Fikir'lerim genelde bir sorun görüp, bunu nasıl bir "Cin"lik ile çözebilirim ana fikrinden ortaya çıkan fikirler oluyor. Bugünkü fikrimiz WCF ile ilgili:

Problemin Tanımı: Outsource olarak bulunduğum bir iş yerinde, her şeyin olabildiğince servislere yıkılmasını ve gerektiğinde de farklı server ya da uygulamalara geçirilmesi; "Yazılım Geliştirme Stratejisi" olarak belirlenmişti. Ancak bu durum; küçük bir veri çekme işleminde dahi yazılan servislere bir client oluşturulması zorunluluğu getiriyordu. Üstelik de servis sayısı arttıkça; development için kullandığınız PC her servis çağrısı için TCP portlarına veri göndermek zorunda kalıyordu ve performansı çok düşük oluyordu. Üstelik geliştirilen uygulama gerçek ortama alınıp da servisler aynı PC'ye kurulursa, gereksiz olarak performans kaybına uğranıyordu.

Cin Fikir ! : Servis çağrılarını bir Façade sınıf üzerinden, interface üzerinden çekersek; concrete sınıfı da bir konfigurasyona göre ürettirirsek Bussiness kodlarını DLL üzerinden ya da WCF üzerinden çağırmamızı  bilmeden kod yazabiliriz. Bir de bu interface'i Servis sınıfına implemente ettirip, Client sınıfını da bu inteface'i implenmente edecek bir proxy nesnesiyle oluşturursak deymeyin keyfimize :D Daha fazla beklemeden kodlamaya geçelim...

Kodlama:
İlk olarak Service Interface'imize ve implemetasyonuna göz atalım.
[ServiceContract]
public interface IComplexService
{
    [OperationContract]
    int Sum(int x, int y);
}

public class ComplexService : IComplexService
{
    public int Sum(int x, int y)
    {
        return x + y;
    }
}
Karşınızda çok komplex bir servis :)
Şimdi de methodu çağıran sınıftaki kod'a göz atalım. Bu bir Console uygulaması..


class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(
           Core.ComplexService.Sum(2, 5)
        );
        Console.ReadLine();
    }
}
Burası sistemin can alıcı noktalarından birisi; Core sınıfı üzerindeki ComplexService static property'si IComplexService tipindedir. Bu property içinde Configuration içinden aldığımız değere göre gerçek nesneyi ya da WCF üzerinden çağrıyı gerçekleştirecek proxy sınıfını geri döndürüyoruz.

public static class Core
{
    public static IComplexService ComplexService
    {
        get
        {
            if (ConfigurationManager.AppSettings["userService"] == "1")
                return new ComplexService();

            return new ChannelFactory<IComplexService>("BasicHttpBinding_ComplexService")
                               .CreateChannel();
        }
    }
}
Burada belirtilen ChannelFactory sınıfı; Visual Studio'nun WCF Client kod üreticisinin ürettiği koda benzer şekilde verilen konfigurasyona ve service interface'ine göre, verilen interface tipinde WCF client nesnesi üretmekte.. Yani bir tür proxy nesnesi.. Buradaki sıkıntımız, bizim WCF method çağrımlarında her method'dan önce ve sonra işlem yapmak zorunda olduğumuzu varsaydığımızda sıkıntı çıkartması..

Bu durumda kendi proxy nesnelerimiz üretebileceğimiz bir yapıya ihtiyaçımız var. Bunun için bir çok çözüm var; biz burada RealProxy nesnesini kullanıyor olacağız. (Konu hakkında ayrıntılı bilgi için Sefer Algan Hocam'ın yazdığı CSharpNedir'deki şu makalesine göz atabilirsiniz).


internal class ProxyFactory<TObject> : RealProxy
{
    public TObject RealObject { get; private set; }

    public ProxyFactory(TObject realObject)
        : base(typeof(TObject))
    {
        this.RealObject = realObject;
    }


    private Func<Func<object>, object> _surroundDelegate;
    public TObject GetProxy(Func<Func<object>, object> surroundDelegate)
    {
        this._surroundDelegate = surroundDelegate;
        return (TObject)this.GetTransparentProxy();
    }

    public override IMessage Invoke(IMessage msg)
    {
        //Bu örnek gelen çağrımın bir metot olduğu varsayımı altında yapılmıştır.
        //Eğer çağrım bir Property ise ona göre bu kodu değiştirmek gerekir.Yani set_ ve get_ önekleri ile metodu dinamik bir şekilde çağırmak gerekir.

        IMethodCallMessage message = (IMethodCallMessage)msg;

        if (message != null)
        {
            //MessageBox.Show(message.MethodName + " metodu çağrılmak üzere. Biz araya girdik çağrılmadan önce");

            object methodRetval = _surroundDelegate(() => message.MethodBase.Invoke(RealObject, message.InArgs));

            ReturnMessage retVal = new ReturnMessage(methodRetval, null, 0, message.LogicalCallContext, message);

            return retVal;
        }
        return null;
    }
}

Burada göz atılacak 3 önemli satır var.

  1. 5 numaralı satır'da; bir nesnenin proxy'ini üretmek için ProxyGenerator sınıfını üretirken; gerçek nesneyi de vermeyi zorunlu tutuyoruz.
  2. 13 numaralı satırda method parametresi :"Func<Func<object>, object>". Yani içerisine object tipinde değer döndüren; verilen Delegate'i çalıştırıp, dönen değeri geri bize veren bir Delegate istenmektedir. Biliyorum cümle çok karmaşık oldu ama kod yeterince açık değil mi zaten :D
  3. 30 numaralı satırda ise; proxy nesne üzerinden çağrılan, gerçek sınıfın methodlarını çağırdığında GetProxy nesnesinde aldığımız delegate; içerisine gerçek nesnenin ilgili methodunu barındıran delegate verilerek çalıştırılıyor.
Son olarak da: Core Sınıfımızın son hali :
public static class Core
{
    public static IComplexService ComplexService
    {
        get
        {
            if (ConfigurationManager.AppSettings["useService"] == "0")
                return new ComplexService();
            //return new ChannelFactory<IComplexService>("BasicHttpBinding_ComplexService").CreateChannel();

            var proxyCreater = new ProxyFactory<IComplexService>(
                            new ChannelFactory<IComplexService>("BasicHttpBinding_ComplexService")
                                .CreateChannel()
                            );

            return proxyCreater.GetProxy(fnc =>
                {
                    using (OperationContextScope scope = new OperationContextScope(proxyCreater.RealObject as IContextChannel))
                    {
                        return fnc.Invoke();
                    }
                });
        }
    }
}
Proxy methodunu alırken Lambda Expression'lar gerçek methodu, OperationScope içerisinde gerçeklemiş oldum. İstersek, kendi case'lerimize özel çalışmalar da yapabiliriz.

Artık istediğimiz an basit bir konfigurasyon ayarıyla sistemimizi WCF üzerinden çalışır hale getirebiliriz. Üstelik servis method'larını çalıştırıken tekrar etmek zorunda kaldığımız kodları da merkezi noktalara çekebilmiş olduk.

Çalışan örneğin kaynak kodlarını buradan indirebilirsiniz.

Umarım fikrimi yeterince "Cin" bulumuşsunuzdur :)

Sıradışı günler, sıradışı başarılar dilerim.

Hiç yorum yok:

Yorum Gönder