Sayfalar

23 Aralık 2014 Salı

AngularJs ve Dependency Injection (DI)

Google tarafından geliştirilen AngularJs yazılım dünyasında uzun süredir kullanılan yapılardan biri olan Dependency Injection (DI) mimarisini istemci taraflı Front-End uygulamalarda kullanabilmemize olanak veren ender ve en güçlü Javascript Framework'lerinden biri. İlk etapta çok kolay kullanılabilen bu kütüphane işler detaylandıktan sonra haliyle bazı zorluklar da ortaya çıkartmakta. Bunlardan biri de DI süreci ile ilgili.

Bildiğimiz gibi bir modülle bir bileşen eklemek için modül nesnesi üzerindeki .controller(), .service(), .directive() gibi api metotlarını kullanmaktayız. AngularJs in sağladığı DI mekanizması eklenen ilgili bileşenlerin bağımlılıklarını önceden biliyorsa onları çalışma anında enjekte edecektir.

Örneğin elimizde InvoiceController tipindeki aşağıdaki gibi bir controller olsun

function InvoiceController (invoiceService, loggingService) {
    ...
}

Görüldüğü gibi bu controller üzerinde 2 adet bağımlılık (dependency) bulunmaktadır. AngularJs bu InvoiceController nesnesini oluşturabilmek için üzerindeki diğer 'invoiceService' ve 'loggingService' bileşenlerini de biliyor olmalı. Bu yüzden bu bileşenleri de kayıt etmemiz gerekiyor.

Sonuç olarak bu controller ve bileşenlerini bir test modülüne en basit kullanımıyla aşağıdaki gibi ekleyebiliriz:

angular('testModule')
    .controller('invoiceController', InvoiceController)
    .service('invoiceService', InvoiceService)
    .service('loggingService', LoggingService);

Üstteki blok ile modüle 3 adet bileşen tanımlamış olduk ve AngularJs InvoiceController oluşturulacağı zaman üzerinde kullanacağı 'invoiceService' ve 'loggingService' bağımlılıklarını da konteynerde bulunan servis kayıtlarının anahtar değerleri ile karşılaştırıp hangi nesneyi yaratacağını veya döndüreceğini biliyor.

Peki AngularJs hangi bileşenin neye bağımlığı olduğunu nereden anlıyor?

Cevap: Reflection benzeri bir mekanizma ile. Javascript nesne tabanlı (OO) bir dil olmadığından dolayı burada sınıf (class) ve yaratıcı metot (constructor) kavramı farklı bir şekilde ele alınıyor. Bir nesne bir fonksiyona karşılık geliyor ve constructor ise bu fonksiyonun kendisi oluyor. Yani constructor üzerindeki bağımlılıklar fonksiyonun aldığı parametreler olup bu parametrelerin yaşam döngüsü de nesne var olduğu sürece devam ediyor.

AngularJs bir fonksiyonun parametrelerini reflection benzeri bir mekanizma ile elde edip, kendi konteyneri içinden bu parametrelerle aynı isimli anahtarı bularak nesneleri çözebiliyor. Ancak eğer MVC uygulamalarındaki gibi bundling & minification gibi bir mekaniğiniz varsa ve bu InvoiceController nesnesi gibi bağımlılıkları olan bir nesneniz varsa işler değişiyor.

Çünkü minification, obfuscation tarzı işlemler sonucu değişen kodunuzu takip edemez hale geliyor. Şöyle ki bir obfuscation işlemi sizin InvoiceController fonksiyonunuzu

function a(b, c) {
    ...
}

olarak değiştirip en iyi ihtimalle kayıt yaptığınız kodları da

angular('testModule')
    .controller('invoiceController', a)
    .service('invoiceService', b)
    .service('loggingService', c);

olarak değiştirse bile AngularJs a'nın üzerindeki b, c bağımlılıklarını henüz bilmiyor olacak çünkü onlar aşağıdaki gibi eklenmedi:

angular('testModule')
    .service('b', b)
    .service('c', c);

Ama güzel haber şu ki AngularJs bunun farkında ve bunun için bir yöntem sağlamış durumda. Bu yönteme bir bileşen eklenirken anahtarın karşılığı olan değeri fonksiyonun tipini vermek yerine bağımlılıklarıyla birlikte bir dizi şeklinde vermek gerekiyor. Yani şu şekilde yapmalıyız:

angular('testModule')
    .controller('invoiceController', ['invoiceService', 'loggingService' , InvoiceController])
    .service('invoiceService', InvoiceService)
    .service('loggingService', LoggingService);

Böylece bu fonksiyon adları obfuscation sonucu değişse bile AngularJs hangi bağımlılığın fonksiyona hangi sırayla ve hangi bileşen kullanarak enjekte edileceğini biliyor olarak. Böylece obfuscate edilmiş olan kod aşağıdaki gibi olsa bile sistem çalışıyor olacak:

angular('testModule')
    .controller('invoiceController', ['invoiceService', 'loggingService' , a])
    .service('invoiceService', b)
    .service('loggingService', c);

function a(b, c) {
    ...
}

Evet herhangi bir teknik problemimiz kalmadı. Ancak nesnelerin çoğaldığını ve çok sayıda bağımlılık içermeye başladığını düşünürseniz modül konfigürasyonunuz çok kalabalıklaşmaya başladığını göreceksiniz. Ayrıca bir nesneye her eklediğiniz dependency için gidip modül konfigürasyonunu güncellemeniz gerekecek. Ancak bunun bir avantajı da hangi bileşenlerin hangi bileşenlere bağımlı olduğunu liste halinde görebileceksiniz.

Eğer bir kodlarınızı obfuscate etmeyip, sadece minify edip bundle oluşturuyorsanız ve "ben bu listeyle ilgilenmiyorum, otomatik olarak bu fonksiyonlar eklensin" derseniz bu işi yapmak için AngularJs tarafından sizin bağımlılıklarını algılamak için kullanılan yönteminin benzerini kullanarak bunu yapabilmek mümkün. Aşağıdaki fonksiyon ile bir fonksiyonun bağımlılık listesi dizi olarak elde edilip son elementi olarak fonksiyonun kendisi bu diziye eklenerek AngularJs in dizi şeklinde beklediği değeri elde ediyoruz:

function getDependencyArray(functionName) {
    var fx = window[functionName];
    var dependencyConfiguration = getParamNames(fx);
    dependencyConfiguration.push(fx);
    return dependencyConfiguration;

    function getParamNames(fn) {
        var strip_Comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
        var argument_Names = /([^\s,]+)/g;

        var fnStr = fn.toString().replace(strip_Comments, '');
        var result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(argument_Names);
        if (result === null)
            result = [];
        return result;
    }
}

Bunu da şu şekilde kullanabiliriz:

angular('testModule')
    .controller('invoiceController', getDependencyArray(InvoiceController.name))
    .service('invoiceService', getDependencyArray(InvoiceService.name))
    .service('loggingService', getDependencyArray(LoggingService.name));

Bu yöntemi eğer kullandığınız obfuscate tool'unun fonksiyon parametreleri rename etmeden yapabilmesini sağlarsanız obfuscate ile de kullanabilirsiniz.

Hiç yorum yok:

Yorum Gönder