Merhaba Arkadaşlar,
Günümüzde uygulamaların genişletilebilir olması önemli bir konu. Modüler olarak da nitelendirebileceğimiz bu felsefe ile bir uygulamanın kullanıcıları tarafından kolayca genişletilebilmesi amaçlanır. Hatta akıllı uygulamaların kendilerini bu şekilde genişletmesi de mümkündür.
Modülerliği kazandırmak için kullanabileceğimiz farklı yöntemler vardır. Bunlardan belki de en basiti Interface tiplerini ve Reflection' ı kullanarak uygulamanın standart fonksiyonelliklerini genişletilebilir şekilde dışarıya açmaktır. Basittir ancak geliştiricinin iyi tasarlamasını gerekitir ve kod maliyeti yükselebilir.
Pek tabii Plug-In tabanlı bir yaklaşım için IoC(Inversion of Control) Container' larından da yararlanılabilir. Ninject, Windsor Castle, Unity gibi pek çok IoC Container bu noktada devreye girer. Ne var ki .Net cephesinde modülerlik adına Framework 4.0 sürümünden beri gündemde yer alan MEF alt yapısı ile de bu iş gerçekleştirilebilir.(ki benim Hello World demem epey zamanımı almıştır)
Örnekler
Konuyu iyi analiz edebilmek adına gerçek hayat örneklerine bakmamızda da yarar var. Modüler uygulamalara günümüzde pek çok örnek verebiliriz aslında. Plug-In veya Extension desteği olan ürünlerin tamamında modüler bir tasarım olduğundan bahsedebiliriz. Photoshop' a kullanıcı bazlı efektlerin yüklenmesinden Warcraft' a yeni oyuncların ilave edilmesine, Winamp' da yeni bir Skin' in yüklenmesinden Visual Studio' da yazılım takımının geliştirdiği bir TFS Policy' sinin entegre edilmesine kadar pek çok örnek verebiliriz.
MEF Nedir?
İşin gerçeği kurumsal uygulamalarda IoC Container' ların kullanıldığı ama n sayıda nesnenin Bind edilme ihtiyacının bulunduğu senaryolarda MEF kullanımı düşünülebilir.
MEF'in Avantajı
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.
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
Solution
Contracts kütüphanesi dışındaki tüm projelerin System.ComponentModel.Compositions.dll assembly' ını referans etmesi gerektiğini ifade edelim. Bu sayede MEF alt yapısını kullanabileceğiz.
Kodlar
İ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(); } }
Modül İçerikleri
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
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.
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ı?
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? ;)