23 Haziran 2008 Pazartesi

Unity Application Block

Smart Client Software Factory, Web Client Software Factory, Spring.net gibi bir çok popüler application framework yapıları kendilerine ait bir object builder nesnesi içerirler. Bu application framework yapıları asıl güçlerini kendi içinde barındırdıkları object builder nesnesini kulanarak injection yapabilmelerinden kazanırlar.

Injection yapabilme güçünü application framework yapılarından bağımsız olarak kullanma isteği Pattern&Practice gurubunun Unity Dependency Container Application Block ürünü tarafından yerine getirilmiştir. Unity application block zamanla SqlHelper/OracleHelper gibi tüm uygulamalara girecek çok geniş kullanım alanı olan bir uygulama bloğudur. Bu uygulama bloğunu anlamak için harcadığınız zaman kesinlikle boşa harcanmış bir zaman değildir.

Unity Application block projemiz için object builder ve dependency container sağlamaktadır.Unity consructor, property ve methodlara injection yapabilen hafif ve genişletilebilir dependecy container uygulama bloğudur. Builder Strategy ekleyerek uygulamanıza yeni 'aspect'ler kazandırablirsiniz. Builder Policy ekleyerek uygulamanıza yeni politikalar ekleyebilirsiniz. Nesneleri config dosyasından oluştabilirsiniz.

Bu makalede Unity dependency container nasıl kullanıldığını inceleyeceğiz. Bir sonra ki makalede uygulamaya yeni aspect ve politikaların nasıl kazandırıldığını ele alacağız. Sonra ki makalede Unity ve diğer Enterprise Library application bloklarının nasıl bir arada kullanıldığını, policy injection ve dependency injection nasıl bir arada çalıştığını inceleyeceğiz. Serinin son makalesinde Unity ve WCF, ASP.NET, MCV framework gibi uygulamaların içide Unity nasıl kullandığını inceleyeceğiz.

Hemen işe başlıyalım ve unity'nin güçünü sınayalım. Tüm projelerde karşılaştığımız bir senaryo vardır. Bu senaryo benim daha önce burada anlattığım ve injection pattern doğmasına sebeb olan ilişkisel katmanlar problemidir. Business logic katmanı data access, log, security gibi bileşenleri ihtiyaç duymaktadır. Log nesneside data access nesnelerine ihtiyaç duymaktadır. Burada klasik yöntem ile yapılan çözüm nesneler her ihtiyaç anında tekrar tekrar oluşturulmaktadır. Bu da hem lock hem fazla kaynak tüketimi sorunlarna yol açmaktadır.Bizim çözümümüz uygulama içinde ihtiyaç duyulan tüm ortak nesne tanımlarını Unity container üzerinde saklamak ve ihtiyaç duyulan yerde Unity containerdan bu nesneleri talep etmektir. Unity üzerinde nesne tanımları ile birlikte lifetime container, politikalar ve builder stratejileride mevcuttur. Unity her creation işlemini bu politika ve stratejilere göre yapmaktadır.

Şimdi unity container nesnemizi oluşturalım.

[cs]

// dependency container oluştur
IUnityContainer container = new UnityContainer();

Life time manager nesnelerin yaşam döngüsünü yönetirler. Nesnelerin tip tanımlarını yapılırken hangi life time manager ile saklanacağı seçilir.Kendi life time manager sınıfınızı oluşturup nesne yaşam döngülerini yönetebilirsiniz.Unity ile gelen iki adet life time manager vardır:

  • Nesne eğer singleton olarak oluşturulacaksa ContainerControlledLifetimeManager kullanılmalıdır.
  • Nesne eğer her ihtiyaçta tekrar oluşturulacaksa ExternallyControlledLifetimeManager kullanılır. Bu life time manager tip tanımlamasında default olarak kullanılır.

[cs]

//lifetime manager'ları oluştur
ContainerControlledLifetimeManager singletonLivetime = 
 new ContainerControlledLifetimeManager();
ExternallyControlledLifetimeManager externallyLivetime = 
 new ExternallyControlledLifetimeManager();

Artık tip tanımlarımızı ekleye biliriz.

[cs]

// tip tanımlarını ekle
//externallyLivetime varsayılan life time manager olduğu için parametre olarak eklemeye gerek yoktur

container.RegisterType(externallyLivetime);

// interface olmaksızın sadece sınıf tanımı ile containere eklenti yapılabilinir
container.RegisterType(singletonLivetime);

//IBusinessA çağrısı olduğu zaman geçerli BusinessA nesnesini döndür
container.RegisterType();

//IBusinessB çağrısı olduğu zaman geçerli BusinessB nesnesini döndür
container.RegisterType();

//ISecurity çağrısı olduğu zaman  Security nesnesini döndür
container.RegisterType();

//IDataAccessB tipinde ve dataAccessB anahtar kelimesi ile çağrı olduğu zaman geçerli DataAccessB nesnesini döndür
container.RegisterType("dataAccessB");

//IBusinessB tipinde 'singletonBusinessB' anahtarı ile çağrı olduğu zaman singleton BusinessB nesnesini döndür
container.RegisterType("singletonBusinessB", new ContainerControlledLifetimeManager());

Şimdi injectionların nasıl yapıldığını inceleyelim. Unity constructor method ve property'lere injection yapabilmektedir. Eğer sınıf tanımında tek bir constructor varsa unity o constructor çağıracak ve gerekli parametreleri enjekte edecektir

[cs]

public DataAccessA(Logger logger) {
 Console.WriteLine("Data access A build up");
 logger.AddLogger("Data access A build up");
}

Eğer sınıf tanımında birden cok constructor varsa unity'e hangi constructor'un kullanılacağını göstermemiz gerekmektedir.InjectionConstructor attribute ile unity'e nesneyi oluşturmak için kullanılacak constructor gösterilmektedir.

[cs]

public class DataAccessB : IDataAccessB {
 public DataAccessB() {
  Console.WriteLine("Data Access B build up with default constructor");
 }
 
 [InjectionConstructor]
 public DataAccessB(Logger logger) {
  Console.WriteLine("Data Access B build up with InjectionConstructor");
  logger.AddLogger("Data Access B build up with InjectionConstructor");
 }
 }

Unity constructor'e injection yaptıktan sonra sınıf tanımda injection bekleyen property'lere injection yapar. Tip tanımında isimlendirme var ise injection sırasında istediğimiz nesnenin ismini belirtmeliyiz.

[cs]

public class BusinessA : IBusinessA {
 public BusinessA() {
  Console.WriteLine("Business A build up");
 }
 
 private IDataAccessA dataAccessA;
 
 //property injection için dependency attribute kullanılır
 [Dependency]
 public IDataAccessA DataAccessA {
  set {
   this.dataAccessA = value;
   Console.WriteLine("IDataAccessA injecting to BusinessA");
  }
 }
}

public class BusinessB : IBusinessB {
 public BusinessB() {
  Console.WriteLine("BusinessB build up");
 }

 private IDataAccessB dataAccessB;
 //IDataAccessB tip tanımını yaparken isimlendirme kullandığımız için
 //injection sırasında istediğimiz nesnenin ismini veriyoruz
 [Dependency("dataAccessB")]
 public IDataAccessB DataAccessB {
  set {
   this.dataAccessB = value;
   Console.WriteLine("IDataAccessA injecting to BusinessA");
  }
 }
}

Unity object builder creation sırasında en son olarak metotlara injection işlemini uygular.

[cs]

public class Security : ISecurity {       
 [InjectionMethod]
 public void Init(IDataAccessA dataAccessA, 
  [Dependency("dataAccessB")]IDataAccessB dataAccessB, 
  Logger logger)
 {
  Console.WriteLine("Security init method");
  dataAccessA.GetData1();
  dataAccessB.UpdateData4();
  logger.AddLogger("security init ok");
 }
}

Böylelikle uygulamamıza ait tüm nesnelerimiz tanımladık. Gerekli injection'ları ayarladık. Şimdi uygulamamızı oluşturalım.

[cs]

// security nesnesini getir
ISecurity security = container.Resolve();
if (security.Check()) {
 security.Login();
 
 // geçerli BusinessA nesnesini getir
 IBusinessA businessA = container.Resolve();
 businessA.DoSameThing1();
 
 // geçerli BusinessB nesnesini getir
 IBusinessB businessB = container.Resolve();
 businessB.DoSameThing4();
 
 //singleton BusinessB nesnesi bir kez oluşturulacaktır
 IBusinessB singletonBusinessB1 = container.Resolve("singletonBusinessB");
 singletonBusinessB1.DoSameThing5();
 IBusinessB singletonBusinessB2 = container.Resolve("singletonBusinessB");
 singletonBusinessB2.DoSameThing6();
 
 // uygulama içinde oluşan nesneleride unity container'a ekleyebiliriz
 BusinessA singletonBusinessA = new BusinessA();
 singletonBusinessA.DataAccessA = container.Resolve();
 container.RegisterInstance("singletonBusinessA", 
  singletonBusinessA, new ContainerControlledLifetimeManager());
 
 //uygulama içinde oluşan singleton BusinessA nesnesi bir kez oluşturulacaktır
 IBusinessA singletonBusinessA1 = container.Resolve("singletonBusinessA");
 singletonBusinessA1.DoSameThing2();
 IBusinessA singletonBusinessA2 = container.Resolve("singletonBusinessA");
 singletonBusinessA2.DoSameThing3();

}

Son olarak childe unity container oluşturalım ve bu childe container ile config üzerinde yer alan tip tanımlamalarına göre yeni nesneler oluşturalım. Childe container ile parent container üzerinde ki tip tanımlamarını kullanabilirsiniz.

[cs]

//childe unity container oluştur
IUnityContainer childeContainer = container.CreateChildContainer();

// config dosyasına ac unity section oku
ExeConfigurationFileMap map = new ExeConfigurationFileMap();
map.ExeConfigFilename = @"D:\Project\ECoskun.UnitySample\ECoskun.UnitySample\dependency.config";
System.Configuration.Configuration config
 = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

UnityConfigurationSection section
 = (UnityConfigurationSection)config.GetSection("unity");
// config dosyasına göre childe container'ı konfigüre et
section.Containers["childe"].Configure(childeContainer);

// config üzerinde ki tanımlara göre nesneleri oluştur
IServiceA serviceA1 = childeContainer.Resolve(typeof(IServiceA)) as IServiceA;
IServiceA serviceA2 = childeContainer.Resolve();
IServiceB serviceB = childeContainer.Resolve();

serviceA1.OperationA();
serviceA2.OperationB();
serviceA2.OperationC();
serviceB.OperationD();
serviceB.OperationE();

Konfigürasyon dosyası kendi kendisini açıklar şekildedir.

<unity>
<typeAliases>
    <!-- Lifetime manager types -->
    <typeAlias alias="singleton"
        type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
        Microsoft.Practices.Unity" />
    <!-- User-defined type aliases -->
    <typeAlias alias="IServiceA"
        type="ECoskun.UnitySample.IServiceA, ECoskun.UnitySample" />
    <typeAlias alias="ServiceA"
        type="ECoskun.UnitySample.ServiceA, ECoskun.UnitySample" />
    <typeAlias alias="IServiceB"
        type="ECoskun.UnitySample.IServiceB, ECoskun.UnitySample" />
    <typeAlias alias="ServiceB"
        type="ECoskun.UnitySample.ServiceB, ECoskun.UnitySample" />
</typeAliases>
<containers>
    <container name="childe">
        <types>
            <type type="IServiceA" mapTo="ServiceA">
            <lifetime  type="singleton"/>
        </type>
        <type type="IServiceB" mapTo="ServiceB" />
        </types>
    </container>
</containers>
</unity>


Bu yapıyı Composite application pattern üzerinde görmüştük. Her bir use case WorkItem nesnelerine denk gelmekteydi. Tüm WorkItem nesnelerini taşıyan bir RootWorkItem nesnesi vardı. Unity container ile yukarda ki örnekte görüldüğü gibi composite application pattern vb yapıları kolaylıkla oluşturabilirsiniz.Artık uygulama nesneleriniz avucunuzun içinde uygulama nesneleri ile istediğiniz gibi oynayabilirsiniz. 

8 Haziran 2008 Pazar

WCF Unity

Smart client software factory – web client software factory gibi bir çok popüler uygulama mimarisinin kendi object builder nesnesi vardır. Fakat wcf için böyle bir uygulama mimarisi bulunmamaktadır. Buda bir eskikliktir.

Bu projede WCF için unity container nesnesinin nasıl kullanılacağını gösteriyorum. Unity ile policy injection application bloğu bereber kullandım.Böylelikle WCF servisleri için hem dependency injection hemde policy injection yapabilme imkanı doğdu. Tip tanımlarının ayrı config dosyalarında olduğunu varsayarak hareket ettim. Böylelikle hem config dosyasındakarmaşadan kaçındım hemde projeyi uygulama bağımsız olarak kullanma imkanı oluştu.

WCF için unity containerın kullanabilecek en iyi yer WCF servisin life time kontrolünü sağlayan IInstanseProvider arayüzüdür. Öncelikle dependency injection sağlayacak olan yeni bir IınstanceProvider yazdım.

[cs]

/// 
/// init dependency container
/// 
/// Dependency config file /// Dependency conainer name private void InitContainer(string dependencyFile, string containerName) {  string currentContainerKey = GetDependencyKey(dependencyFile, containerName);  if (!containers.ContainsKey(currentContainerKey)) {   lock (syncRoot) {    if (!containers.ContainsKey(currentContainerKey)) {     containers[currentContainerKey] = currentContainer = new UnityContainer();     ExeConfigurationFileMap map = new ExeConfigurationFileMap();     map.ExeConfigFilename = dependencyFile;     System.Configuration.Configuration config      = ConfigurationManager.OpenMappedExeConfiguration(       map, ConfigurationUserLevel.None);      UnityConfigurationSection section      = (UnityConfigurationSection)config.GetSection("unity");     section.Containers[containerName].Configure(currentContainer);     currentContainer.AddNewExtension();    }   }  } else {   currentContainer = containers[currentContainerKey];  } }

DependencyInjectionInstanceProvider iki tane parametreye ihtiyaç duymaktadır: dependencyFile tip tanımlamalarımızın bulunduğu config dosyasıdır. containerName config dosyası içinde tanımlı container adıdır. Her bir container sadece bir kez oluşturul ve saklanır. Oluşturulan container nesnelerine son olarak PolicyInjectionExtansion ile Policy injection destekler hale getirilir. PolicyInjectionExtansion sadece EnterpriseLibrary.PolicyInjection.ObjectBuilder altında ki strategy ve policy sınıflarını register eder. Son olarak Dependency injection instance provider'ı servislere uygulayacak yeni bir servis behavior yazmaya.

[cs]

public class DependencyInjectionServiceBehavior : Attribute, IServiceBehavior {
 public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
  ServiceHostBase serviceHostBase) 
 {
  foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers) {
   ChannelDispatcher cd = cdb as ChannelDispatcher;
   if (cd != null) {
    foreach (EndpointDispatcher ed in cd.Endpoints) {
     ed.DispatchRuntime.InstanceProvider =
      new DependencyInjectionInstanceProvider(
       serviceDescription.ServiceType, 
       configFile, 
       containerName);
   }
  }
 }
}

Evet artık WCF ile unity dependency container bir arada kullanılabilir.

[cs]

static void Main(string[] args) {
 ServiceHost hostA = null, hostB = null;
  try {
   hostA = new ServiceHost(typeof(ServiceA));
   hostB = new ServiceHost(typeof(ServiceB));
   hostA.Open();
   hostB.Open();
 

[ServiceBehavior]
public class ServiceA : IServiceAContract {
 public ServiceA() { }

 private IServiceBContract serviceB;
 private IServiceC serviceC;

 [InjectionConstructor]
 public ServiceA(IServiceBContract serviceB) {
  Console.WriteLine("Build up Service A");
  this.serviceB = serviceB;
  Console.WriteLine("Service A constructor calling to serviceB.OperationD");
  this.serviceB.OperationD("test1", "test2");
 }

 [Dependency]
 public IServiceC ServiceC {
  set {
   serviceC = value;
   Console.WriteLine("IServiceC injected to ServiceA parameter ");
  }
 }


public class ServiceB : IServiceBContract {

 public ServiceB() { }
 private IServiceC serviceC;

 [InjectionConstructor]
 public ServiceB(IServiceC serviceC) {
  Console.WriteLine("Build up Service B ");
  this.serviceC = serviceC;
  Console.WriteLine("Service B constructor calling to serviceC.OperationG");
  this.serviceC.OperationG("test");
  this.serviceC.OperationG("test");
 }