Makale Özeti

Bu makalemde sizlerle Windows Communication Foundation'ın gelen isteklere yanıt vereceği hizmet sınıfı örneklerini nasıl oluşturduğunu ve yazılım geliştirici olarak bizlerin hizmet sınıf örneği oluşturulmasına nasıl müdahale edebileceğimizi paylaşıyor olacağım.

Makale

    Windows Communication Foundation ile kod geliştirme konusunda sizlerle bilgiler paylaştığım makalelerimde şimdiye kadar giriş düzeyinde sunucu ve istemci tarafında yapılacak olan işlemleri gördük. Makalelerimdeki adımları takip ederek WCF ile kolaylıkla bir istemci ve sunucu uygulaması geliştrirebildiğinizi umuyorum. Bu adımlar ardından artık biraz daha zevkli konulara geçmenin sanırım zamanı geldi. Bu makalemde sizlerle Windows Communication Foundation'ın gelen isteklere yanıt vereceği hizmet sınıfı örneklerini nasıl oluşturduğunu ve yazılım geliştirici olarak bizlerin hizmet sınıf örneği oluşturulmasına nasıl müdahale edebileceğimizi paylaşıyor olacağım.

   Önceki makalerimde geliştirmiş olduğumuz WCF sunucu uygulamamız basit bir arayüz ile açtığı hizmette istemciden Say fonksiyonuna gelen her bir çağrıda private olarak tuttuğu bir değişkenin değerini arttırmakta ve yeni değerini dönmekteydi.

public interface IOrnekHizmet {
    [OperationContract]
    int Say();
}

internal class OrnekHizmet : IOrnekHizmet {
    private int sayac = 0;

    public OrnekHizmet() { }

    public OrnekHizmet(int sayacBaslangici) {
        sayac = sayacBaslangici;
    }

    #region IOrnekHizmet Members

    public int Say() {
        return ++sayac;
    }

    #endregion
}

   Aşağıda bulabileceğiniz istemci kodumuz, WCF hizmetimize tek bir istemci ile ardıl olarak 10 istek gönderek sayacı arttırmakta ve genel sayaç bilgisini ekrana vermekte. İçerik olarak önceki makalemde paylaştıklarımdan çokta farklı bir kod değil;

var istemci = istemciFabrikasi.CreateChannel();
var oturum = 1;

for (int cagri = 1; cagri <= 10; cagri++) {
    Console.WriteLine("{0}. oturum {1}. çağrı, yanıt : {2}", oturum, cagri, istemci.Say());
} 

   Uygulamayı çalıştırdığımızda aşağıda ekran görüntüsünü alacaksınızdır;

   Sunucumuza tek oturum ile istek gönderdiğimiz uygulamamızın çıktısı tam da istediğimiz gibi görünüyor. İstemcimize sunucunun açık olduğu süre boyunca gelen toplam istek sayısı dönmekte; ama emin konuşmak için henüz erken. İsterseniz aynı kodu bu defa iki istemci ile deneyelim. Bunun için istemci uygulamasını iki defa çağırabileceğiniz gibi aşağıdaki şekilde ek bir döngü ile de istemci sayısını arttırmanız mümkün;

for (int oturum = 1; oturum <= 2; oturum++) {
    var istemci = istemciFabrikasi.CreateChannel();

    for (int cagri = 1; cagri <= 10; cagri++) {
        Console.WriteLine("{0}. oturum {1}. çağrı, yanıt : {2}", oturum, cagri, istemci.Say());
    }
}

   Durun bir dakika; uygulamada bir terslik var gibi, hiçte beklediğimiz gibi bir çıktı yok karşımızda. Uygulamamız ikinci istemciyi oluşturup istek göndermeye başladığında sayacımızın 10'dan devam etmesini beklerken baştan başlayarak 1'den itibaren saymış. Bunun sebebini anlayabilmek için birlikte WCF örnek yönetimi inceleyelim;

   Windows Communication Foundation bir hizmetin örneği oluşturmada bize 3 farklı yol sunmaktadır;

  • Her bir açılan oturuma tek bir hizmet örneği atanmasını sağlayan PerSession
  • Yapılan her bir çağrı için bir hizmet örneği atanmasını sağlayan PerCall
  • Gelen istek ve istemcilerden bağımsız olarak hizmetin yaşam döngüsü boyunca tek bir örneğinin olmasını sağlayan Single

   Bu 3 yöntemin kendilerine göre artıları ve eksileri bulunmakta, dolayısıyla iş mantığınıza uygun olanı seçmeniz gerekecektir. Pek çok iş mantığında bu yöntemlerin seçimindeki temel belirleyici kullanılacak kaynakların ne olduğu ve hizmet örneklerinin bu kaynaklara ne şekilde ulaşacağıdır. Bir WCF hizmeti oluşturduğunuzda varsayılan olarak bu 3 yöntemden oturum başına bir hizmet örneği oluşturulması (PerSession) kullanılmakta, bu sebeple uygulamamızı iki istemci ile istek gönderecek şekilde yeniden düzenlediğimizde beklediğimizde farklı bir çıktı görmüş, sayacımızın yeni oturumda sıfırlandığını anlamıştık.

   Programsal olarak WCF hizmetinin ne şekilde hizmet örneği oluşturabileceği bilgisini hizmet sınıfımızın üzerine ekleyeceğimiz ServiceBehaviorAttribute özniteliği ile belirleyebiliriz. Aslında WCF hizmetinin pek çok özelliğini yapılandırmamıza izin veren ve detaylarını ilerleyen makalelerimde paylaşmayı planladığım bu öznitelik içerisindeki InstanceContextMode özelliği doğrudan müdahale etmemizi sağlamakta. Bu noktada akıllara bu özelliğin neden hizmetimizi tanımladığımız arayüze değil de doğrudan hizmet sınıfının kendisine eklediğimiz sorusu gelebilir. Bunun yanıtı bize bir esneklik sağlamak; ama asıl olarak da hizmet sınıf örneğinin ne şekilde oluşturulacağı bilgisinin arayüz ile doğrudan ilişkili bir konu olmamasıdır. Bu tanımlama arayüz üzerinden bulunmuş olsaydı arayüzü kullanan tüm hizmetleri aynı örnek yönetimini kullanmaya zorlamış olurduk. Pek çok WCF örneğinde arayüzün dışarıda, üçüncü bir assembly içerisinde olduğunu düşünürsek, aslında arayüz bizim için sadece hizmetimizin sınırlarını belirleyen bir kontrat olduğu ve istemci ile de paylaşıldığı noktasına varabilirsiniz. İstemcimizle paylaştığımız böylesi bir bilgi içerisine hizmetimizin örnek yönetiminin nasıl olduğunuda eklemek hem gereksiz olacak hem de bazı kullanımlarda güvenlik zaafiyeti oluşturabilecektir.

   Uygulamamızın başta düşündüğümüz şekilde hareket ederek toplam gelen istek sayısını doğru sayabilmesini sağlamak için hizmet sınıfımızın üzerine aşağıdaki şekilde ServiceBehaviorAttribute özniteliğini eklememiz gerekecektir;

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class OrnekHizmet : IOrnekHizmet {
    private int sayac = 0;

    public OrnekHizmet() { }

    public OrnekHizmet(int sayacBaslangici) {
        sayac = sayacBaslangici;
    }

    #region IOrnekHizmet Members

    public int Say() {
        return ++sayac;
    }

    #endregion
}

   Bu şekildeki düzenleme  sonrasında uygulamamız aşağıdaki ekran görüntüsünde de görebileceğimiz gibi istediğimiz yönde çalışacaktır.

   Hizmetimizi her bir istek için yeni bir hizmet örneği oluşturacak şekilde yapılandırdığımızda ise aşağıdaki çıktı ile karşılaşırız;

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
internal class OrnekHizmet : IOrnekHizmet {
    private int sayac = 0;

    public OrnekHizmet() { }

    public OrnekHizmet(int sayacBaslangici) {
        sayac = sayacBaslangici;
    }

    #region IOrnekHizmet Members

    public int Say() {
        return ++sayac;
    }

    #endregion
}

   Yukarıda her üç yönteminde kendilerine göre artıları ve eksileri olduğundan bahsetmiştim. Oturum ya da istek bazlı hizmet örneği oluşturulması her istemcide/istekte yeni bir örnek oluşturacağından işlemci zamanında götürecektir. Örneğimizde kullandığımız hizmet sınıfımız örnek oluşturulması sırasında yoğun bir işlem yapmaması nedeniyle hissedilen bir fark yaratmayacak olsa da örneğin veritabanı bağlantısını ilklendiren ve ardından da yapılandırma bilgisi okuyan bir hizmet iş mantığında her bir istekte oluşacak hizmet sınıfı hem uygulama sunucusuna hem de veritabanına ek bir maliyet getirecektir. Böylesi bir örnekte uygulama yaşam döngüsü boyunca ayakta kalacak bir hizmet örneğinin olması daha mantıklı olabilir.

   Öte yandan tek bir hizmet örneği olması durumunda paylaşılan kaynakların doğru yönetilmesi gerekecektir. Örneğimiz üzerinden konuşacak olursak; InstanceContextMode.Single tanımlaması ile tek bir hizmet örneği oluşmasını istekdikten sonra hizmetimize farklı istemcilerden gelecek aynı andaki istekler sayacın aynı anda okunarak aynı anda arttırılmasına, dolayısıyla da toplam istek sayısının hatalı olmasına neden olacaktır. Bu gibi durumlardan kaçınmak adına paylaşılan kaynakların doğru tespit edilerek doğru yönetilmesi gereklidir. Aşağıda, böylesi bir durumu engelleyemek için nasıl kodlanması gerektiğine dair basit bir örnek bulabilirsiniz;

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class OrnekHizmet : IOrnekHizmet {
    private object monitor = new object();
    private int sayac = 0;

    public OrnekHizmet() { }

    public OrnekHizmet(int sayacBaslangici) {
        sayac = sayacBaslangici;
    }

    #region IOrnekHizmet Members

    public int Say() {
        lock (monitor) {
            return ++sayac;
        }
    }

    #endregion
}

   Bazı iş mantıklarında hizmet örneği oluşturulurken çeşitli parametrelerin geçilmesi gerekebilir. Örneğin sayacımızın uygulama kapanırken son değerini veritabanına yazarak, bir sonraki çalışmasında da veritabanından bu değeri okuyarak başlamasını isteyebiliriz. Bu durumda programsal olarak bir wcf hizmetinin nasıl oluşturulabileceğini paylaştığım makalemde belirttiğim sunucu uygulamamızda hizmetisimizi oluşturduğumuz aşağıdaki satırı;

ServiceHost hizmetSunucusu = new ServiceHost(typeof(OrnekHizmet), hizmetTemelAdresi);

   Hizmet sınıf örneğimizi verecek şekilde yeniden düzenlemeliyiz; 

var sayac = 0;

//Veritabanından sayacın son değerini okuyan
//iş mantığı buraya gelecek

ServiceHost hizmetSunucusu = new ServiceHost(new OrnekHizmet(sayac), hizmetTemelAdresi);

   Büyük resme baktığımızda, Windows Communication Foundation ile örnek yönetimi konusunda ileri düzey kullanımlarda özelleştirme yapılabilemesine olanak sunulurken, basit kullanımlar da düşünülerek varsayılan genel geçer bir tanımlama da yapılmıştır. Biz yazılım geliştiriciler iş mantığımız doğrultusunda bu yöntemlerden uygun olanı seçmeli, kodumuz içerisinde ortak kullanılan kaynaklarımızın bulunması durumunda bu kaynakların kullanımına daha fazla özen göstererek istenmeyen durumların oluşmasının önüne geçmeliyiz.

Fatih Boy

http://www.enterprisecoding.com
http://twitter.com/fatihboy