21 Kasım 2015 Cumartesi

XAF Ortamında İş Modelini Çoklu Kalıtım Kullanarak Tasarlamak 1

Yazılım geliştirme süreçlerinin olmazsa olmazı, uygulamaya özgü bir model oluşturmaktır. Küçük ölçekli, ya da kısa süreli bir kullanım ömrüne sahip olacağı beklenen projelerde, model oluşturma adımının "adhoc" tarzda yapıldığına şahit olsak ta, genellikle bu adım dikkatli ve kurallı bir şekilde yapılmak zorundadır. Model, yazılımın kodlanması, kullanım performansı, kullanıcı deneyimi, yazılımın uzun vadede bakımı açısından merkezi bir önemdedir.

Nesne tabanlı tasarım paradigması yazılım geliştime süreçlerinde baskın olmaya başladığından bu yana, yazılımcılar nesne tabanlı dünyaları ile, nesnelerin kayıt altına alındığı ve genelikle nesne tabanlı olmayan yapılar (veri tabanları, dosya sistemi, vb) arasındaki ilişkiyi kurmakla yükümlü oldular. "Serializer" yardımına kavuşmadan önce, uygulama geliştimenin önemli bir bölümünü, bu alanda yapılması gerekenler oluşturmakta idi. Nesne tabanlı geliştirme dünyamız ile, kullandığımız teknoloji arasında ki bu boşluğu doldurmak için epeyi emek harcadık. Bu gün bu alanda iki temel yardımcımız var. İlki özellikle bilimsel, mühendislik uygulamarında kullanımı artan nesne tabanlı veri tabanları. NoSQL veri tabanlarının giderek web ve büyük veri alanında yaygınlık kazanmaları da gelecekte farklı olanaklara sahip olacağımızı ima ediyor. İkinci olarak ise nesne tabanlı geliştirme ortamı ile çizelge temelli ilişkisel veri tabanları arasındaki yaklaşım farklarını gidermeyi hedefleyen nesne-ilişkisel bağıntılama (object relational mapping - ORM) yöntemi. ORM katı kodlama dünyasındaki nesnel yaklaşımı, kalıcı hale getirme, yeni nesneleri kalıcı ortamdan okuyarak yaratma, kalıcı hale getirilmiş nesneler üzerinde sorgulama yapabilme gibi pek çok işlevi, ilişkisel veritabanlarına karşı nesne ara yüzü üzerinden kullanabilmemize olanak vermekte. ORM katı için gerek açık kaynak kodlu, gerek değişik ticari şekiller altında pek çok ürün bulmak mümkün. En yaygın olanları arasında Hybernate, Entity Framework bulunmakta.

Devexpress firması, kullanıcı arayüzü bileşenleri alanındaki ürünleri ile tanınıyor. Bunun yanısıra, yazılımcılara XPO (Express Persistent Object) denilen bir ORM katını da sunmakta. Devexpress bileşen çözümleri yanı sıra, çoklu platform uygulama geliştirme çerçevesi olarak XAF (Express Application Framework) ürününü geliştirmiş. XAF ile aynı kod tabanını kullanarak, hem Windows masaüstü, hem Web uygulamaları geliştirmek mümkün. İş uygulamaları geliştirmek amaçlı bir çerçeve uygulama olarak tanımlamak mümkün. XAF, ORM katı olarak ya XPO ya da Entity Framework kullanımına olanak tanımakta.

ORM kullanarak, veri tabanı işlemlerini, nesne tabanlı bir şekilde halletmek, gereken bütün ara dönüşümlerin ORM tarafından yapılması, yazılımcı olarak bizi oldukça rahatlatan ve asıl işimize, uygulamanın konu aldığı problemin çözümüne odaklanmamızı sağlayan bir olanak. XAF altında uygulama geliştirmenin en önemli adımlarından biri model tasarımının yapılması. XAF model tarafından belirlenen bir uygulama büyük ölçüde. Modeli tanımlamış olmakla, temel işlevlere sahip bir uygulama XAF altında hazır hale gelebilmekte. Modelin XAF uygulamasında merkezi rolü nedeniyle artan önemi, nesne tabanlı tasarım imkanları ile birleştiğinde, model tasarımına farklı yaklaşmamızı gerektirmekte.

Nesne tabanlı paradigmanın önemli imkanlarından birisi kalıtım mekanizması. Sınıfları birbirlerinden türeterek, yüksek seviyede soyutlamalar yapabilmeyi, bu sayede daha yalın kod üretebilmeyi mümkün kılan kalıtım imkanı, model tasarımına da uzanmakta. XAF (kullandığı ORM katının olanaklarını kullanarak) modelimizin bir sınıf hiyerarşisi olarak tanımlanabilmesini sağlamakta.

Örnek olarak, iş yazılımlarında sıklıkla kullanılan kimi elemanlar düşünebiliriz. Cari elemanı (sınıfı) çoğunlukla bir ticari işletmeyi göstermektedir. Bir başka deyişle Cari elemanı TicariIsletme elemanından türetilebilir. TicariIsletme elemanı ise esas olarak Adres elemanından türetilebilir.

Örnek:


public class Cari : TicariIsletme
{

     public double Borc;
     public double Alacak;

}

public class TicariIsletme : Adres
{

     public string VergiDairesi;
     public string VergiNo;

}

public class Adres:XPObject
{

     public string BinaNo;
     public string DaireNo;
     public string Sokak;
     public string Cadde;
     public string Mahalle;
     public string Ilce;
     public string Il;
     public string Ulke;
     public string PostaKodu;

}

Modelin kalıtım/türetme yoluyla belirlenmesi burada anmamızı gerektirmeyecek avantajlara sahiptir. Kısaca soyutlamanın gerek tasarım, geliştirme gerek bakım safhalarında hayatımızı kolaylaştırdığını söyleyip geçelim.

Yukardaki nesne hiyerarşisinin veri tabanı tablolarında ki yansısı nasıl olacak? ORM yazılım katı nesneleri birebir veri tabanı şemasına (yapısına) yansıttığında beklentimiz aşağıdaki gibi bir veri tabanı yapısı olacaktır :



İlişkisel cebir esasen bu tür bir yapıya karşıt olmadığı gibi tersine bunu önerir. Normalleştirmenin "aynıların aynı yerde olması" kuralı, elde ettiğimiz veri tabanı yapısını ima etmektedir. Gene de bu yapının çok alışık olduğumuz türde olmadığını teslim etmeliyiz. Bir cari kaydını okuyabilmek için üç tablo arasında join kullanmak zorunda olmak ürkütücü görünmektedir. Hele hele raporlama aşamasında. Bu konuda bir çok tartışma yapılmakta. Bu günün veri tabanları için ortaya çıkacak başarım farkının minimal olduğu, bu farkın istendiğinde veri tabanı sunucusuna ek kaynak tahsisi ile aşılabileceği söylenebilir. İtirazların haklı olduğunu kabul etsek dahi, tasarımın aynı zamanda bir stil meselesi olduğu ortadadır. Kİmi durumlarda nesne hiyerarşisini yansıtan bir veri tabanı yapısını istememiz, kimi durumlarda, nesne hiyerarşisinin sonuçlarını yansıtan, örneğimizdeki Cari elemanının kalıcı hale getirileceği veri tabanı tablosunun gereken bütün alanları barındırdığı (daha klasik) bir yapı istememiz mümkündür. XPO altında bu "attribute" kullanımı ile kolaylıkla sağlanabilir. Kodumuzu aşağıdaki gibi bir parça değiştirirsek, istediğimiz sonucu elde edebiliriz:


[MapInheritance(MapInheritanceType.ParentTable)]
public class Cari : TicariIsletme
{

     public double Borc;
     public double Alacak;

}

[MapInheritance(MapInheritanceType.ParentTable)]
public class TicariIsletme : Adres
{
     public string VergiDairesi;

     public string VergiNo;

}

public class Adres:XPObject
{
     public string BinaNo;
     public string DaireNo;
     public string Sokak;
     public string Cadde;
     public string Mahalle;
     public string Ilce;
     public string Il;
     public string Ulke;
     public string PostaKodu;
}

Bu durumda TicariIsletme ve Adres elemanlarımız kendi başlarına veri tabanı içinde saklanmalarına gerek yoktur. Bütün gereken alanlar zaten Cari tablosunun içinde yer alacaktır. Bu nedenle bu elemanları kalıcı olmayacak şekilde işaretlememiz gerekir.

[MapInheritance(MapInheritanceType.ParentTable)]
public class Cari : TicariIsletme
{
     public double Borc;

     public double Alacak;

}

[NonPersistent]
[MapInheritance(MapInheritanceType.ParentTable)]
public class TicariIsletme : Adres
{
     public string VergiDairesi;


     public string VergiNo;

}

[NonPersistent]
public class Adres:XPObject
{
     public string BinaNo;
     public string DaireNo;
     public string Sokak;
     public string Cadde;
     public string Mahalle;
     public string Ilce;
     public string Il;
     public string Ulke;
     public string PostaKodu;


     
}

Böylelikle alışageldiğimiz (klasik) yapıya dönmüş olduk. Artık veri tabanımızda yalnızca Cari tablosu, gereken bütün alanları ile birlikte bulunacaktır.

Veri tabanı yapısının ORM katı yardımıyla, nesne tabanlı bir şekilde tasarlanması işlerimizi kolaylaştırmaktadır ama isteklerimizin burada kalması söz konusu değildir. Kalıtım gerçek gücünü ancak çoklu kalıtım altında gösterebilir. Tekli kalıtım altında kurulan nesne hiyerarşileri ister istemez karmaşık olmak, pek çok vekil (proxy) nesne tanımı barındırmak ve derin olmak dezavantajlarına sahiptirler. Çoklu kalıtım, soyutlamayı birden çok eksen üzerinden yapabilmeyi, neticede daha yalın nesne hiyerarşileri ile tasarımı bitirebilme şansını sunmaktadır. Problemimiz, yaygın kullanılan nesne tabanlı dillerin pek çoğunun çoklu kalıtımı desteklememesinde yatmaktadır. Özellikle dotNET altında çoklu kalıtım söz konusu değildir. XAF ise bir dotNET uygulama çerçevesidir. Devexpress tam bu noktada gerçek bir "hack" kullanarak sorunu aşıyor. "Domain Component" diye adlandırdığı teknolojiyi kullanarak dotNET ortamında -yalnızca iş modeli sınıflarımız için- çoklu kalıtım imkanlarını sunuyor. 


"Domain Component" teknolojisi bir kaç adımdan oluşuyor. İlki sınıflar yerine arayüz (interface) tanımları üzerinden çalışmak gerekiyor. İkincisi, yalnızca arayüzleri yazdığımız ama bu arayüzleri uygulayan sınıfları yaz(a)madığımız için, gereken sınıf tanımlarının XAF tarafından oluşturulmasını sağlamak üzere, yazdığımız arayüzlerin XAF'e bildirilmesi (register edilmesi) adımı. Bu iki adım ile yalnızca alanlardan (field/property) oluşan bir tasarımı bitirmek mümkün. Arayüzleri yazıyoruz, XAF'e bildiriyoruz, o sınıf tanımlarını ve bunların saklanacağı veri tabanı yapısını oluşturuyor. Sanırım fark ettiniz, burada eksik kalan bir nokta var. Eğer iş modelinde yer alan sınıflarımız kendilerine has işlevler, olaylar vb. sahibi iseler ne olacak? Yazdığımız arayüzler salt birer bildirim oldukları için gereken iş mantığı (business logic) nerede uyguılanacak, yazılacak. Arayüzlerden sınıf üretimi XAF tarafından çalışma anında (runtime) yaratılacakları için bu sınıflara müdahale edebilmemiz olanaksız durumda. Bu noktadaki zorluk, DomainLogic diye adlandırılan bir yardımcı sınıfın yazılması ve bu sınıfın ilgili arayüzün iş mantığı olarak XAF'e bildirilmesi ile çözülüyor.

Bir parça karışık ve anlaşılmaz gelmesinden ürkülecek bir şey yok. Bir örnekle görünür kılmaya çalışalım. İş modelimizde pek çok eleman hem bir kod hem bir isim taşımakta olsunlar. Cari ve Stok gibi elemanlar bu tür örnekler. Gene bir çok eleman, adres bilgisi taşımakta olsun. Personel, Cari gibi. Modelimizde pek çok eleman, ayrıntılı açıklamalar girilebildiği bir tipte olsunlar. Stok, Cari gibi. Gene hemen dikkatimizi çekecek bir başka ortaklık, Cari, Stok, Kasa, Banka gibi pek çok elemanın giren/çıkan/kalan bilgilerinin tutulduğu alanlara sahip olması. Böylesi bir yapıyı tasarlarken, bu özelliklerin her birinin ayrı birer eksende soyutlanması ve nihai (sonul) elemanların bu soyutlama nesnelerinden türetimi işimizi çok kolaylaştırabilecektir.

Öncelikle soyutlamamız neticesinde elde ettiğimiz eleman tanımlarını ara yüz biçiminde yazalım :


[DomainComponent]
public interface IAdres
{
     string BinaNo{get;set;}
     string DaireNo{get;set;}
     string Sokak{get;set;}
     string Cadde{get;set;}
     string Mahalle{get;set;}
     string Ilce{get;set;}
     string Il{get;set;}
     string Ulke{get;set;}
     string PostaKodu{get;set;}
}

[DomainComponent]
public interface IKodluEleman
{
     string Kod{get;set;}
     string Ad{get;set;}
}

[DomainComponent]
public interface IMemo
{
     [Size(SizeAttribute.Unlimited)]
     string Memo{get;set;}
}

[DomainComponent]
public interface IHesap
{
     double Borc{get;set;}
     double Alacak{get;set;}
}

Bu temel elemanları kullanarak Cari, Stok, Banka, Personel gibi elemanları tasarlamak artık daha kolay olacaktır:

[DomainComponent]
public interface ICari:IKodluEleman,IMemo,IHesap,IAdres
{
     string Yetkili{get;set;}
     string VergiDairesi{get;set;}
     string VergiNo{get;set;}
}

[DomainComponent]
public interface IStok:IKodluEleman,IMemo,IHesap
{
     ICari Saglayıcı{get;set;}
     boolean Emtia{get;set;}     
}

[DomainComponent]
public interface IBanka:IKodluEleman,IMemo,IHesap,IAdres
{
     string HesapNo{get;set;}
     string HesapDovizi{get;set;}     
}

[DomainComponent]
public interface IPersonel:IMemo,IHesap,IAdres
{
     string Ad{get;set;}
     string Soyad{get;set;}
     string SSKNo{get;set;}
     string Unvan{get;set;}
     datetime DogumTarihi{get;set;}
     datetime IseGirisTarihi{get;set;}
     datetime IstenCikisTarihi{get;set;}
}

Bu aşamada gereken arayüzlerimizi XAF'e bildirmek. Uygulama'nın başlangıç kodlarının içinde bunu yapabiliriz:

using DevExpress.Persistent.BaseImpl;
// ... 
public override void Setup(XafApplication application) {
    base.Setup(application);
    XafTypesInfo.Instance.RegisterEntity("Personel", typeof(IPersonel));
    XafTypesInfo.Instance.RegisterEntity("Cari", typeof(ICari));
    XafTypesInfo.Instance.RegisterEntity("Stok", typeof(IStok));
    XafTypesInfo.Instance.RegisterEntity("Banka", typeof(IBanka));


    XafTypesInfo.Instance.RegisterSharedPart(typeof(IMemo));
    XafTypesInfo.Instance.RegisterSharedPart(typeof(IKodluEleman));
    XafTypesInfo.Instance.RegisterSharedPart(typeof(IAdres));
    XafTypesInfo.Instance.RegisterSharedPart(typeof(IHesap));



}

Elemanların XAF'e bildirilmesi (register) sırasında sonul eleman olarak kullanmak istediğimiz Cari, Stok gibi elemanların bildirimi ile birden çok elemanın yapı taşı olarak kullanılacak soyut elemanların bildirimleri arasındaki farka dikkat etmek önemli. Paylaşılan elemanlar RegisterSharedPart ile bildirilirken, nihai elemanlar RegisterEntity  ile bildirilmekte.

XAF çoklu kalıtım kullanılan "Domain Component" bildirimleri uyarınca veri tabanı yapısını oluştururken, bildiğimizin biraz dışında bir yapı oluşturacaktır. Veri tabanında gerek nihani elemanlar, gerek paylaşımlı olarak kullanılan elemanların her biri için ayrı bir tablo açılacak ve ilgili alanlar bu tabloların içinde bulunacaktır. Bu tablolardan veriyi çekip, sonul elemanları yaratmak için ise XAF her bir kayıt ile tutulan OID alanlarının değerlerini kullanmaktadır. Örneğin belirli bir carinin OID'si "AAA" ise, bu Cari'nin türediği IMemo, IKodluEleman, IAdres, IHesap tanımlarının ilgili tablolarındaki ilgili kayıtlar da "AAA" OID'sine sahip olacaklardır. XAF bir Cari kaydını okurken hem Cari tablosunu hem paylaşılan tiplerin ilgili tablolarını aynı OID üzerinden sorgulayarak okumakta ve toplu sonucu bir Cari nesnesi olarak döndürmektedir.

Tasarım sürecimizi daha yalın bir hale getirmesine karşın, saptanmış haliyle oluşan veri tabanı yapısı standart olmaktan uzaktır. XAF kullandığımız sürece, veri tabanından kayıtların okunması bizim konumuz olmadığından bu duruma katlanılabilir ama eğer aynı veri tabanına XAF dışında bir ortamdan erişim gerekiyorsa, ortaya çıkan tasarım o tarafta işleri kolaylaştırmak bir yana daha da zorlaştıracaktır.

XAF altında çoklu kalıtım kullanarak veri tabanı yapısının nasıl daha standart bir hale getirilebileceği ve iş mantığının "Domain Logic" kullanarak nasıl uygulanacağını "
XAF Ortamında İş Modelini Çoklu Kalıtım Kullanarak Tasarlamak 2" başlıklı yeni bir blogta tartışacağım.


1 yorum: