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.
11 // dependency container oluştur
12
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.
13 //lifetime manager'ları oluştur
14
ContainerControlledLifetimeManager singletonLivetime = new ContainerControlledLifetimeManager();
15
ExternallyControlledLifetimeManager externallyLivetime = new ExternallyControlledLifetimeManager();
Artık tip tanımlarımızı ekleye biliriz.
17 // tip tanımlarını ekle
18
19 //externallyLivetime varsayılan life time manager olduğu için parametre olarak eklemeye gerek yoktur
20 container.RegisterType<IDataAccessA, DataAccessA>(externallyLivetime);
21
22 // interface olmaksızın sadece sınıf tanımı ile containere eklenti yapılabilinir
23 container.RegisterType<Logger>(singletonLivetime);
24
25 //IBusinessA çağrısı olduğu zaman geçerli BusinessA nesnesini döndür
26 container.RegisterType<IBusinessA, BusinessA>();
27
28 //IBusinessB çağrısı olduğu zaman geçerli BusinessB nesnesini döndür
29 container.RegisterType<IBusinessB, BusinessB>();
30
31 //ISecurity çağrısı olduğu zaman Security nesnesini döndür
32 container.RegisterType<ISecurity, Security>();
33
34 //IDataAccessB tipinde ve dataAccessB anahtar kelimesi ile çağrı olduğu zaman geçerli DataAccessB nesnesini döndür
35 container.RegisterType<IDataAccessB, DataAccessB>("dataAccessB");
36
37 //IBusinessB tipinde 'singletonBusinessB' anahtarı ile çağrı olduğu zaman singleton BusinessB nesnesini döndür
38 container.RegisterType<IBusinessB, BusinessB>("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.
20 public DataAccessA(Logger logger) {
21 Console.WriteLine("Data access A build up");
22 logger.AddLogger("Data access A build up");
23 }
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.
57 public class DataAccessB : IDataAccessB {
58 public DataAccessB() {
59 Console.WriteLine("Data Access B build up with default constructor");
60 }
61
62 [InjectionConstructor]
63 public DataAccessB(Logger logger) {
64 Console.WriteLine("Data Access B build up with InjectionConstructor");
65 logger.AddLogger("Data Access B build up with InjectionConstructor");
66 }
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.
98 public class BusinessA : IBusinessA {
99 public BusinessA() {
100 Console.WriteLine("Business A build up");
101 }
102
103 private IDataAccessA dataAccessA;
104 //property injection için dependency attribute kullanılır
105 [Dependency]
106 public IDataAccessA DataAccessA {
107 set {
108 this.dataAccessA = value;
109 Console.WriteLine("IDataAccessA injecting to BusinessA");
110 }
111 }
141 public class BusinessB : IBusinessB {
142 public BusinessB() {
143 Console.WriteLine("BusinessB build up");
144 }
145
146 private IDataAccessB dataAccessB;
147 //IDataAccessB tip tanımını yaparken isimlendirme kullandığımız için
148 //injection sırasında istediğimiz nesnenin ismini veriyoruz
149 [Dependency("dataAccessB")]
150 public IDataAccessB DataAccessB {
151 set {
152 this.dataAccessB = value;
153 Console.WriteLine("IDataAccessA injecting to BusinessA");
154 }
155 }
Unity object builder creation sırasında en son olarak metotlara injection işlemini uygular.
193 public class Security : ISecurity {
194
195 [InjectionMethod]
196 public void Init(IDataAccessA dataAccessA,
197 [Dependency("dataAccessB")]IDataAccessB dataAccessB,
198 Logger logger)
199 {
200 Console.WriteLine("Security init method");
201 dataAccessA.GetData1();
202 dataAccessB.UpdateData4();
203 logger.AddLogger("security init ok");
204 }
Böylelikle uygulamamıza ait tüm nesnelerimiz tanımladık. Gerekli injection'ları ayarladık. Şimdi uygulamamızı oluşturalım.
42 // security nesnesini getir
43 ISecurity security = container.Resolve<ISecurity>();
44 if (security.Check()) {
45 security.Login();
46
47 // geçerli BusinessA nesnesini getir
48 IBusinessA businessA = container.Resolve<IBusinessA>();
49 businessA.DoSameThing1();
50
51 // geçerli BusinessB nesnesini getir
52 IBusinessB businessB = container.Resolve<IBusinessB>();
53 businessB.DoSameThing4();
54
55 //singleton BusinessB nesnesi bir kez oluşturulacaktır
56 IBusinessB singletonBusinessB1 = container.Resolve<IBusinessB>("singletonBusinessB");
57 singletonBusinessB1.DoSameThing5();
58 IBusinessB singletonBusinessB2 = container.Resolve<IBusinessB>("singletonBusinessB");
59 singletonBusinessB2.DoSameThing6();
60
61 // uygulama içinde oluşan nesneleride unity container'a ekleyebiliriz
62 BusinessA singletonBusinessA = new BusinessA();
63 singletonBusinessA.DataAccessA = container.Resolve<IDataAccessA>();
64 container.RegisterInstance<IBusinessA>("singletonBusinessA", singletonBusinessA, new ContainerControlledLifetimeManager());
65
66 //uygulama içinde oluşan singleton BusinessA nesnesi bir kez oluşturulacaktır
67 IBusinessA singletonBusinessA1 = container.Resolve<IBusinessA>("singletonBusinessA");
68 singletonBusinessA1.DoSameThing2();
69 IBusinessA singletonBusinessA2 = container.Resolve<IBusinessA>("singletonBusinessA");
70 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.
72 //childe unity container oluştur
73 IUnityContainer childeContainer = container.CreateChildContainer();
74 // config dosyasına ac unity section oku
75 ExeConfigurationFileMap map = new ExeConfigurationFileMap();
76 map.ExeConfigFilename = @"D:\Project\ECoskun.UnitySample\ECoskun.UnitySample\dependency.config";
77 System.Configuration.Configuration config
78 = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
79 UnityConfigurationSection section
80 = (UnityConfigurationSection)config.GetSection("unity");
81 // config dosyasına göre childe container'ı konfigüre et
82 section.Containers["childe"].Configure(childeContainer);
83
84 // config üzerinde ki tanımlara göre nesneleri oluştur
85 IServiceA serviceA1 = childeContainer.Resolve(typeof(IServiceA)) as IServiceA;
86 IServiceA serviceA2 = childeContainer.Resolve<IServiceA>();
87 IServiceB serviceB = childeContainer.Resolve<IServiceB>();
88
89 serviceA1.OperationA();
90 serviceA2.OperationB();
91 serviceA2.OperationC();
92 serviceB.OperationD();
93 serviceB.OperationE();
Konfigürasyon dosyası kendi kendisini açıklar şekildedir.
9 <unity>
10 <typeAliases>
11 <!-- Lifetime manager types -->
12 <typeAlias alias="singleton"
13 type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
14 Microsoft.Practices.Unity" />
15 <!-- User-defined type aliases -->
16 <typeAlias alias="IServiceA"
17 type="ECoskun.UnitySample.IServiceA, ECoskun.UnitySample" />
18 <typeAlias alias="ServiceA"
19 type="ECoskun.UnitySample.ServiceA, ECoskun.UnitySample" />
20 <typeAlias alias="IServiceB"
21 type="ECoskun.UnitySample.IServiceB, ECoskun.UnitySample" />
22 <typeAlias alias="ServiceB"
23 type="ECoskun.UnitySample.ServiceB, ECoskun.UnitySample" />
24 </typeAliases>
25 <containers>
26 <container name="childe">
27 <types>
28 <type type="IServiceA" mapTo="ServiceA">
29 <lifetime type="singleton"/>
30 </type>
31 <type type="IServiceB" mapTo="ServiceB" />
32 </types>
33 </container>
34 </containers>
35 </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.