Uygulamalarımızın modüler olması için .Net tarafında kullanabileceğimiz alt kütüphane topluluklarından birisidir. .Net' in bir parçası olduğu için .Net ile birlikte her yerde kullanılabilir.
Aslında .Net ile geliştirilmiş ürünlerde MEF kullanımının avantajını dile getirmeden önce Late Binding ve Early Binding kavramlarının bir uygulama yaşam döngüsü açısından ne anlama geldiğine bakmamızda yar var. Aşağıdaki amatör çizim bu konuda biraz fikir verebilir.
Şimdi duruma bir bakalım. Modüler olarak düşünülen bir API tasarımı Windows tarafında başlatıldığında derleyici başlangıçta gerekli olan ne kadar kod varsa yükleyecektir. En azından bu şekilde düşünebiliriz. Bu durumda gerekli kod parçalarına Compiler' ın erken bağlandığından bahsedebiliriz.
Diğer yandan kullanıcı veya uygulamanın kendi seçimlerine göre çalışma zamanında yüklenmesi istenen modüller varsa bunlar geç bağlama(Late Binding) tekniği ile ortama dahil edilebilirler. Bu tip bir sürecin sağladığı pek çok avantaj vardır. Söz gelimi uygulama başlangıçta minimum gereksinim ile belleğe açılır ve yaşamı boyunca kullanıcı seçimine bağlı olarak ek modüllerin yüklenmesi için tekrardan başlatılmasına gerek kalmaz. İşte MEF için bu sürecin .Net API versiyonu olduğunu ifade edebiliriz.(Yani Late Binding desteği olan bir yapı)
Temelde IoC container' lar ile MEF arasında benzerlik olduğu düşünülebilir. Ancak temel bir fark vardır. Neredeyse tüm IoC Container' lar Early Binding tekniğine benzer bir şekilde yüklenirler ve genellikle X nesnesinin sadece bir örneğinin bağlanmasına izin verirler. MEF ise birden fazla nesnenin bağlanmasına olanak sunar.
Örnek Uygulama
Gelin basit bir örnek ile MEF kullanımına merhaba diyelim/diyeyim. Senaryomuza göre şirketteki farklı kaynaklardan, kurum müşterilerine ait bir takım verileri toplayan modüllerimiz olduğunu farz edebiliriz. .Net ile geliştirilen bu modüllerin her biri farklı ortamlardan farklı şekillerde veri toplayacaktır. Toplanan verileri bir ortamda da sakladıklarını düşünebiliriz. Aslında veriyi toplama ve saklama şekilleri şu an önemli senaryo gereği çok önemli değil. Kavramamız gereken, ana uygulamanın bu modülerliğe nasıl kavuşabileceği. Yani kodu tekrar derlemeden bakması gereken yerlerdeki dll' lere ulaşarak, aynı fonksiyonelliklerin farklı işleniş şekillerini çalışma zamanı bünyesine nasıl entegre edebileceğini görmek.
Solution
İlk olarak aşağıdaki Solution yapısını oluşturarak işe başlayalım. (Unutmayın bu sadece bir Hello World uygulması, fazlası değil)
İlk olarak Contracts isimli Class Library içerisine IContractModule interface tipini ekleyerek yola çıkalım.
namespace Contracts
{
public interface IContractModule
{
string Name { get; }
string Description { get;}
string Author { get;}
void Initialize();
void FindData();
void LoadData();
void SaveData();
}
}
IContractModule arayüzünü tasarlamaktaki amacımız aslında genişletilebilir modüller için ortak bir sözleşme sunmaktır. Sözleşme modüller için ortak bir kurallar bütünü sunacaktır. Bu sözleşmeyi uygulayan modüller, ana uygulama tarafından kullanılabilir hale gelecektir. Bunun içinse MEF tarafında çalışma zamanına bağlanmaları gerekmektedir. İyi ama nasıl?
Modül İçerikleri
Şimdi tüm konsantrasyonumuzu modüllerimize vereceğiz. Her bir modül bilinçli olarak ayrı birer Class Library olarak tasarlanmıştır. Gerçek hayat senaryosunda her bir kütüphanenin farklı ekiplerce farklı Solution' lar içerisinde yazılması da söz konusu olabilir. Kritik olan nokta, modüler olması istenen uygulamanın, ilgili modül kütüphanelerini bir şekilde tarayabilmesidir. Modüllere ait kodları aşağıdaki gibi geliştirerek yolumuza devam edelim.
CRM Modüle
using Contracts;
using System.ComponentModel.Composition;
namespace CRMModule
{
[Export(typeof(IContractModule))]
public class CRMMiner
: IContractModule
{
public string Author
{
get
{
return "Burki";
}
}
public string Description
{
get
{
return "CRM sistemindeki müşteri verilerini yükler.";
}
}
public string Name
{
get
{
return "CRM Data Miner";
}
}
public void FindData()
{
//Bir takım kodlar
}
public void Initialize()
{
//Bir takım kodlar
}
public void LoadData()
{
//Bir takım kodlar
}
public void SaveData()
{
//Bir takım kodlar
}
}
}
HRMiner Modül
using Contracts;
using System.ComponentModel.Composition;
namespace HRModule
{
[Export(typeof(IContractModule))]
public class HRMiner
: IContractModule
{
public string Author
{
get
{
return "Coni bi gud";
}
}
public string Description
{
get
{
return "İnsan Kaynakları biriminden ilgili müşteri verilerini yükler";
}
}
public string Name
{
get
{
return "HR Customer Data Miner";
}
}
public void FindData()
{
//Bir takım kodlar
}
public void Initialize()
{
//Bir takım kodlar
}
public void LoadData()
{
//Bir takım kodlar
}
public void SaveData()
{
//Bir takım kodlar
}
}
}
ve son olarak
Mernis Modül
using Contracts;
using System.ComponentModel.Composition;
namespace MernisModule
{
[Export(typeof(IContractModule))]
public class MernisMiner
: IContractModule
{
public string Author
{
get
{
return "ToBe Tuuba";
}
}
public string Description
{
get
{
return "Mernis sistemindeki müşteri verilerimizi yükler";
}
}
public string Name
{
get
{
return "Mernis Customer Data Miner";
}
}
public void FindData()
{
//Bir takım kodlar
}
public void Initialize()
{
//Bir takım kodlar
}
public void LoadData()
{
//Bir takım kodlar
}
public void SaveData()
{
//Bir takım kodlar
}
}
}
Her üç modül
IContractModule sözleşmesini uygulayan birer sınıf içermektedir. Bu üç sınıfın en önemli özelliği ise
Export niteliğini(attribute) kullanmalarıdır. Bu nitelik
MEF altyapısına ilgili tipin hangi sözleşmeyi sunduğunu bildirmektedir ki bu sayede
IContractModule arayüzünü uyarlayan sınıflara ait örnekler
MEF tarafından değerlendirilebilsinler. Dikkat edileceği üzere
Export niteliğine
IContractModule arayüz tipi
typeof operatörü ile parametre olarak geçilmiştir. Gelelim asıl kahramanımıza.
Ana Uygulama
Her ne kadar ana uygulama basit bir Console projesi olarak tasarlanmış olsa da içerisinde MEF kullanım şekline Hello World dememiz için yeterlidir. Tabiki ana uygulamanında System.ComponentModel.Composition.dll assembly' ını referans etmiş olması gerekmektedir. Diğer yandan modüllerin bu uygulama tarafından kolayca taranabilir bir yerde olmasında da yarar vardır. Bu amaçla ortak bir klasör kullanılabilir ve dll' lerin buraya atılması sağlanabilir.
Hatta bu modüllerin uygulamanın çeşidine göre ortak bir sunucundan indirilerek lullanılması da sağlanabilir. Nuget paket yönetim aracında olduğu gibi.
Ben örnek olarak Debug\Extensions klasörü altına Build edilen modül dll' lerini xCopy ile kopyaladım.
Gelelim ana uygulama kodlarına. Program.cs içeriğini aşağıdaki gibi yazabiliriz.
using Contracts;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
namespace Miner
{
class Program
{
static void Main(string[] args)
{
Host houston = new Host();
foreach (var module in houston._modules)
{
Console.WriteLine("{0} {1}\n{2}", module.Name,module.Author,module.Description);
module.Initialize();
module.FindData();
module.LoadData();
module.SaveData();
}
}
}
class Host
{
[ImportMany(typeof(IContractModule))]
public IEnumerable<IContractModule> _modules;
public Host()
{
Bootsrap();
}
void Bootsrap()
{
AggregateCatalog catalog = new AggregateCatalog();
string extensionPath= Path.Combine(Environment.CurrentDirectory,"Extensions");
catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
}
Dikkat edilmesi gereken yer
Host isimli sınıf içeriğidir. Burada
IEnumerable<IContractModule> tipinden bir değişken tanımlıdır. Söz konusu liste n adet
IContractModule arayüz uyarlamasını taşıyabilir. Bu n sayıda bağlama tanımını
MEF üzerinden gerçekleştirmek için alanın
ImportMany niteliği ile imzalanması yeterlidir.
Yapıcı metod(Constructor) içerisinden çağırılan
Bootsrap isimli metod, geç bağlama işlemlerini üstlenmektedir. Nasıl mı?
İlk olarak bir modül kataloğu tanımlanır. AggregateCatalog tipinden olan bu nesne örneğine farklı klasörlerden genişletmeler yüklenebilir. Bu nedenle Catalogs isimli bir özelliği vardır ve DirectoryCatalog ile modül klasörlerine ait yer bildirimleri yapılmaktadır. N adet modülün ilgili klasör içerisinde yer alan IContractModule uyarlamaları için düzenlenmesi gerekir. Bu noktada devreye CompositionContainer tipi girer. Dikkat edileceği üzere ilgili nesne örneklenirken parametre olarak AggregateCatalog nesnesini almaktadır. Son olarak bu kompozisyon o an çalışmakta olan canlı nesne örneği ile bağlanır. ComposeParts metodunun this anahtar kelimesi ile çağırılmasının sebebi ilgili modüllerin o anki çalışma zamanı sahibine bağlanmasıdır. Gelelim çalışma zamanı sonuçlarına.
Dikkat edileceği üzere Extensions klasörü içerisinde yer alan ne kadar dll varsa içlerinde yer alan IContractModule uyarlamaları çalışma zamanına bağlanmış ve kullanılmıştır.
Bu noktada akla şöyle bir soru gelebilir. Extensions klasöründe MEF ile Import edilemeyecek assembly' lar olursa ne olur? Hiç bir şey olmaz. Sadece MEX' in Import edebileceği sözleşmeleri(Contract) uygulayabilen tipler değerlendirilir ve çalışma zamanı için bir Exception fırlatılması söz konusu olmaz. Diğer yandan Export edilen Contract ilgili klasördeki her bir tip için aranmakta mıdır ben de bilemiyorum. Bunu derinlemesine araştırmak gerekiyor. Nitekim MEF'in tüm dll' leri taraması ve Export edilen tipleri taraması ciddi bir performans kaybına neden olabilir. O halde taramamasının bir yolu var mıdır? Varsa nasıldır? ;)