Frontend’e “pure” yaklaşım ve mimari üzerine
Web uygulamalarında frontend, uygulamanızın en kolay “çığrından çıkacağı” kısım. Uygulamanın veritabanı ve sunucu tarafını klasik yazılım geliştirme yöntemleri ile tamamlayabiliriz. Markup kalitesini yüksek tutmak, taşınabilirliği ve performansı yüksek uygulamalar yazmak farklı çözümler gerektiriyor.
Yakın zamanda tamamladığımız bir projenin önyüzü Symfony 1.4 üzerinde templateler ile oluşturulmuştu. Fakat geliştirmeler o kadar javascript bağımlı ki tarayıcı kısa sürede tıkanıyor, bir hatayı gidermek bazen işkenceye dönüşüyor, çoğunda da uzun zaman alıyordu. İş geliştirme’den gelen isteklerden biri “Facebook Wall”‘a bir diğeri de “Linkedin Profil” sayfasına benzer istekler. Tasarımlar ve işlevler farklı aslında fakat daha populer sitelerden daha benzer örnekler bulamadım. Özetle proje yüksek oranda javascript bağımlı sayfalardan oluşuyor.
Projenin sonuna yakın dahil oldum, ne kadar “yanlış yapılmış, düzeltmeliyiz” desem de anlatamadım derdimi, uzun bir süre “jquery eklentileri” ile çabaladıktan sonra “tamam, frontend mimarisini değiştirecez” kararını aldırdım.
İki parça “demo” yaptıktan sonra ekip üyelerinin ortak fikirleri: “Backend’deki bu böyle olmaz dediğimiz herşeyi sildik, frontend yazmıyoruz sanki backend’in kötü kısmını siliyoruz”.
Yanlış açıkca ortadaydı, “projenin JavaScript bağımlılık oranı çok yüksek” markup’ı javascript ile çizersek hem backend’i temizlemiş, hem de taşınabilir bir uygulama elde etmiş olacaz. Frontend’de oluşacak memory leakleri de büyük oranda engellemiş olacağız. Arama motoru derdimiz yok, içerik sadece üyelere açık olacak.
Yol Haritası ve Karar : Object Orient JavaScript (Pure)
Projeye başlamadan önce disiplini sağlayabilmemiz ve geliştiriciler arasında kod standartını sağlayabilmek için bazı kurallar koyduk. Projeye başladığımızda izleyeceğimiz yol:
– Kayıt, giriş gibi statik sayfalar geleneksel yolla oluşturulacak, markup, data ile birlikte sunucudan gelecek.
– DOM ile ilişiği olan her bir “obje”nin “.element”i olacak
– DOM elementleri (mümkün ise) tekrar kullanılabilir olacak.
– Her class/modul ayrı bir dosyada tutulacak.
– ‘new’ ile çağırılacak her bir class ‘B’üyük harf ile başlar
– Private methodlar ve değişkenler ‘_’ ile başlar
– Bize bir dependency manager lazım! (app.provide ve app.depends methodları)
– Basit bir araç kutusu lazım. DOM, ajax, extend, interface gibi.
– Uygulamanın her bir parçası ayrı derlenecek (Uygulamanın boyutu 5MB’ye ulaşırsa?).
– Her bir sayfaya tekil bir kimlik verilecek (userRegister, userWall, userProfile, userInbox gibi).
– Javascript kısımını 3 ayrı katmanda çözeceğiz. (Yazıda bu katmanları detaylandırmayacağım Core (çekirdek), Application (uygulama), Implementation (entegrasyon)).
– Tarayıcıya sunucudan HTML olarak sadece temel layout gelecek.
– Tüm sayfaların entegrasyon kodu sayfaya özel oluşturduğumuz isim ile aynı isimde olan ayrı bir dosyada olacak.
– Uygulamanın parçaları manalı namespace’ler altında toplanacak.
– Uygulamanın hiç bir parçası global scope’a çıkmayacak.
Şu an hatırladıklarım bunlar, bir kaç madde daha vardı sanırım. Yazıyı mümkün olduğunca kısa tutabilmek için oluşturduğum 2 demo’dan sadece birinden bahsedeceğim.
Diyagramlar (Wireframe)
İlk yaptığım demo aşağıdaki gibi bir menü.
Request, Notification ve bir tane daha menümüz var (üçüncüyü çizime gerek duymadım). İleride dördüncü/beşinciyi eklemeyeceğimizin, veya olanlardan birini çıkartmayacağımızın garantisi yok.
Menü’nün temel özellikleri:
– Kullanıcı menüye tıkladığında kırmızı daireler rakamlarıyla birlikte kaybolsun, yeni bir uyarı/istek var ise tekrar görünsün.
– “Notification” menüsünde uyarı yeni ise farklı arkaplan rengi ile belirtilsin
– Kullanıcı “action’lardan” birine tıkladığında farklı mesaj gösterebilelim.
– Zaman objeleri kendini güncelleyebilsin. (Bu yazıda bu modulden bahsetmeyeceğim)
UML’e geçmeden önce menüyü mantıksal parçalara ayırıp namespace’ler ile birlikte isimlendirelim.
Mavi renk app.menu namespace‘ini gösterir.
Sarı renk app.navigation namespace‘ini gösterir.
Gördüğünüz gibi tasarımımız iki farklı namespace altında. Bunun amacı, ileride “notifiable” fakat farklı görünen (facebook’da sol tarafda bulunan, groups, favorites, apps gibi bir menü düşünün) bir menü istediğimizde aynı yapıyı kullanabilmek istiyoruz. JavaScript “class less” olduğu için bundan sonra “class” yerine “constructor” olarak isimlendireceğim.
Constructor Diyagramı
Evet, henüz kod yazmıyoruz, biraz daha sabır. Şimdi constructor’ları UML diyagramı ile belirleyelim. Tabii ki abstract class veya parent class görevini üstlenen constructor’larımız da olacak. Interface implementasyonlarını, testleri burada pas geçiyorum. Yazının sonunda neleri atladığımızı bulabilirsiniz. Önce diyagramı görelim, sonra hangi pattern’leri nerelerde kullandığımızı açıklayalım.
notifiable.Container constructor’ın tam path’i app.menu.notifiable.Container, notifiable.Item da aynı şekilde app.menu.notifiable.Item olarak okuyun. Sadeleştirmek adına tam yol’u yazmadım. Diğer tüm constructor’lar app.navigation.* namespace’i altında bulunuyor. (UML’i hatırladığım kadarıyla çiziyorum, hata varsa bildirebilirsiniz)
Kullandığımız Tasarım Desenleri (Design Patterns)
Notifiable Menu
Composite ve Virtual Proxy
notifiable.Container ve alt objeleri olan notifiable.Item composite pattern ile oluşturduk. notifiable.Item yapılandırılırken ‘this.menuEL’e gerekli ‘click’ event’ini bağlıyor. Oluşturduğumuz objeyi Container’a argüment olarak geçiyoruz. Burada aynı zamanda virtual proxy kullandık. Kullanıcı tıklayıncaya kadar richMenu.Container çalıştırılmıyor. Aynı zamanda ‘this.notify’ (şu okunmamış mesaj numarası)’i güncellemek için sunucuyu dinliyoruz. Bu dinlemeyi de notifiable.Container’da yapıyoruz, her bir element için ayrı ayrı dinleme yapmıyoruz.
Rich Menu
Genel yapı için Composite ve Bridge, butonlar için Command
richMenu.Container diğer constructor’lar gibi sadece html elementden oluşmuyor, aslında manager bir constructor. Menü daha açık mı kapalı mı, daha önceden çalıştırılmış mı yoksa ilk defa mı çalıştırılıyor bu objede tutuyoruz. Bu class’ın open/close/update/destroy gibi method’ları da var. app.menu.richMenu.types.Notification menümüz bir “tür”, aynı constructor’dan kalıtım alan bir diğer constructor’da aynı namespace altındaki Request constructor’ı. İki menünün ortak özellikleri de var, farklı özellikleri de. Her ikisinin de “leaf” (yaprak, composite patterndeki en küçük birim)larını tutan “children” değerleri var. Bunun yanında RequestHeader class’ı sadece “Request” menüde çalıştırılıyor.
Command pattern’i butonlarda uyguladık. Her bir butonun “gotAction” method’u var. Karmaşık kısmı burada yapıp, click event’e bu methodu bağlıyoruz. Bu sayede yeni bir tür “action” oluşturduğumuzda “gotAction” yok ise Interface (diyagramda yok) aracılığıyla hata fırlatıp developer’i “gotAction” methodu oluşturması için uyarabiliyoruz.
Bridge pattern’i neredeyse bu tasarımın heryerinde kullandık. “this.init” methodları ile constructor’dan sonra oluşturulması gereken objelerin constructor’larını burada çağırdık.
Constructor’lar
Ana konumuz mimari olduğu için anlaşılabilir olması açısından detaylara girmeden sadece UML diyagramında göründüğü kadar kod yazacağız.
app.navigation.notifiable.Container
app.provide('app.navigation.notifiable.Container');
app.navigation.notifiable.Container = function(menus, parent) {
this.element = document.createElement('ul');
this.menus = menus;
this.parent = parent;
// burada daha fazla yapılandırıcı kod var
};
app.navigation.notifiable.Container.prototype = {
updateNotify: function(num){
// "notify" rakamı güncelliyoruz
// composite patten'e örnek görebilmeniz için sadece bu methodu implement ettim.
var i;
for (i in this.menus) {
if (this.menus[i].hasOwnProperty(i)) {
this.menus[i].updateNotify(num);
}
}
},
clearNotify: function(){
// "notify" deki rakamı kaldırıyoruz
},
setNotify: function(){
// notify alan ekliyoruz
}
// burada daha fazla prototype methodu var.
};
app.navigation.notifiable.Item
app.provide('app.navigation.notifiable.Item');
app.navigation.notifiable.Item = function(classNames) {
this.element = document.createElement('li');
this.element.className = classNames;
this.notifyNum = 0;
// burada daha fazla yapılandırıcı kod var
};
app.navigation.notifiable.Item.prototype = {
updateNotify: function(num){
this.notifyNum = num;
this.notify.innerHTML = num;
},
clearNotify: function(){
this.notify.style.display = 'none';
this.notifyNum = 0;
},
setNotify: function(){
this.notify = document.createElement('span');
this.element.appendChild(this.notify);
}
// burada daha fazla prototype methodu var.
};
app.menu.richMenu.* namespace’i altındaki yapılandırıcılara (class veya constructor) geçmeden önce yukarıdaki kodun entegrasyon kodunu verelim. Bu constructor entegrasyon katmanında olduğu için UML diyagramına koymadım.
Örneğin sayfamız kullanıcı profil sayfası olsun, sayfamızdaki body elementinin id’si “userProfile” olarak ele alalım. Bu durumda implementasyon kodumuz şu şekilde olacaktır:
app.pages.userProfile
app.provide('app.pages.userProfile');
app.depends('app.navigation.notifiable.Item', 'app.navigation.notifiable.Container');
app.pages.userProfile = function() {
var parent = $('header')[0];
new app.navigation.notifiable.Container([
new app.navigation.notifiable.Item('Request'),
new app.navigation.notifiable.Item('Notification')
], parent);
// burada daha fazla entegrasyon kodu var
}
Yukarıdaki gibi sayfaya özel kodları app.pages namespace’i altında tutuyoruz. Sayfa yüklendiğinde bu namespace ile body id’yi map eden bir objemiz var. O da bu yazıda yer almıyor maalesef. Ayrıca yukarıdaki örnek kodlarda gördüğünüz app.depends ve app.provide gibi oluşturduğumuz methodları da bu yazıda vermiyorum. app.provide verilen parametredeki namespace’i oluşturuyor. app.depends de verilen parametrelerdeki constructor’ları aynı namespace yapısı altındaki dosyaları sunucudan çekmekle yükümlü. Dosya organizasyonumuz da aslında bu namespace’ler ile aynı dizin yapısı içerisinde tutuluyor. Detaylara takılmadan “mimariye” odaklanmaya çalışın.
app.menu.richMenu.* namespace’i altında yazacağımız constructor’larımız da şu şekilde:
app.menu.richMenu.Container
app.provide('app.menu.richMenu.Container');
app.menu.richMenu.Container = function(parent) {
this.element = document.createElement('div');
this.parent = parent;
this.initialized = {}; // oluşturulmuş menüleri burada tutuyoruz
this.status = null; // burada da açık menü'nün tekil kimlik ID'sini tutuyoruz.
// burada çok daha fazla constructor kod var
};
app.menu.richMenu.Container.prototype = {
init: function(){
// oluşturulacak menü ye switch/case ile burada karar veriyoruz
},
open: function(){
// "rich menü" yü açacak kodlar burada
},
close: function(){
// daha iyi anlaşılabilmesi için bu methodu implement ettim
// "rich menü" yü kapatacak kodlar şöyle:
this.status = 'closed';
this.element.style.display = 'none';
return this;
},
destroy: function(){
// oluşmuş bir menü'nün tüm event'lerini ve elementlerini kaldır
}
};
app.menu.richMenu.types.Abstract
Bu constructor’ımız “parent” olduğu için this.element tanımlamıyoruz. Bu constructor’ı extend eden “child” de this.element tanımlayacağız. Burada parent olarak UL tanımladık. Bu constructor zaten küçük, tamamını veriyorum:
app.provide('app.menu.richMenu.types.Abstract');
app.menu.richMenu.types.Abstract = function(container) {
this.parent = document.createElement('ul');
this.parent.className = 'menuContainer';
this.menuItems = [];
container.appendChild(this.parent);
};
app.menu.richMenu.types.Abstract.prototype = {
init: function(){
// bu methodu her bir alt constructor'da tekrar tanımlamak gerekli,
// developer'i bir hata fırlatarak uyarıyoruz:
app.error('Parent class does not support this operation: Called: ',
arguments.callee, ' Caller: ', this.constructor);
}
};
Sıra geldi menü türlerimizi tanımlamaya. Şimdilik iki tür menümüz var, biri Request diğeri Notification.
Daha sonra RequestHeader yapılandırıcısını görüp menü elementlerini oluşturacak constructor’lara geçeceğiz.
app.menu.richMenu.types.Request
//namespace'i olutşturuyoruz
app.provide('app.menu.richMenu.types.Request');
// bağımlılıkları belirliyoruz
app.depends(
'app.menu.richMenu.items.Request',
'app.menu.richMenu.types.Abstract',
'app.menu.richMenu.types.RequestHeader'
);
app.menu.richMenu.types.Request = function(){
// RequestHeader'i henüz oluşturmadık Notification'dan sonra oluşturacağız.
this.header = new app.menu.richMenu.types.RequestHeader(this.element);
}
// extend methodu da kendi yazdığımız bir method, bu yazıya dahil değil.
app.extend('app.menu.richMenu.types.Request', 'app.menu.richMenu.types.Abstract');
app.menu.richMenu.types.Request.prototype.init = function(){
//ajax request yap ve dönen data için app.menu.richMenu.items.Request'i oluştur.
// bu method'u developer unutursa "parent"den hata mesajı gösteriyoruz.
};
app.menu.richMenu.types.Notification
Bu constructor’ımız da neredeyse Request ile aynı, şöyle ki:
app.provide('app.menu.richMenu.types.Notification');
// bağımlılıkları belirliyoruz
app.depends(
'app.menu.richMenu.items.Notification',
'app.menu.richMenu.types.Abstract'
);
app.menu.richMenu.types.Notification = function(){};
app.extend(
'app.menu.richMenu.types.Notification',
'app.menu.richMenu.types.Abstract'
);
app.menu.richMenu.types.Notification.prototype.init = function(){
//ajax request ve diğer işlemler burada
};
Notification menümüzde başlık yoktu fakat Request menümüzde bir başlık var. Şimdi bu constructor’ı yazalım:
app.menu.richMenu.types.RequestHeader
app.provide('app.menu.richMenu.types.RequestHeader');
app.menu.richMenu.types.RequestHeader = function(){
this.element = document.createElement('div');
this.element.className = 'requestMenuHeader';
// burada daha başka constructor işlemleri yapılıyor
};
app.menu.richMenu.types.Notification.prototype = {
// burada bazı prototype methodları var
};
Evet, iki farklı menümüzün farklı yapıları olduğu için farklı dosyalar da farklı constructor ile menümüzü neredeyse tamamladık. Sonraki adım olarak *.items.Abstract, *.items.Request, *.items.Notification constructor’larını oluşturuyoruz:
app.menu.richMenu.items.Abstract
app.provide('app.menu.richMenu.items.Abstract');
app.menu.richMenu.items.Abstract = function(){
// daha fazla detay kod ile kafa karışıklığına sebep olmayalım.
// buralar hep constructor kod
};
app.menu.richMenu.items.Abstract.prototype = {
// burada bazı prototype methodları var, setTime, setUserName, setActions gibi.
// bir tane örnek yazalım;
setAvatar: function(){
this.avatar = new Image();
this.avatar.src = this.data.user.avatar;
this.avatar.className = 'richUserMenuAvatar';
this.element.appendChild(this.avatar);
return this;
}
};
app.menu.richMenu.items.Request
app.provide('app.menu.richMenu.items.Request');
app.menu.richMenu.items.Request = function(){
this.setAvatar().setTime().setUsername();
// gibi parent constructor'dan gerekli methodları çağırıyoruz
// diğer constructor methodlarımız da burada.
};
app.extend(
'app.menu.richMenu.items.Request',
'app.menu.richMenu.items.Abstract'
);
// burada prototype'a ihtiyacımız yok.
app.menu.richMenu.items.Notification
app.provide('app.menu.richMenu.items.Notification');
app.menu.richMenu.items.Notification = function(){
// Bu menümüzde sadece tarih ve text objemiz var.
// Request constructor'da olduğu gibi burada sadece onları çağırıyoruz.
};
app.extend(
'app.menu.richMenu.items.Notification',
'app.menu.richMenu.items.Abstract'
);
// burada da ekstra prototype methoduna ihtiyacımız yok
Son kısıma geldik, action’lar. Hani şu “connection” isteği geldiği zaman kabul veya red ile cevap verebileceğiniz butonlar. Parent constructor’ımız şöyle:
app.menu.richMenu.actions.Abstract
app.provide('app.menu.richMenu.actions.Abstract');
app.menu.richMenu.actions.Abstract = function(){
this.element = document.createElement('a');
// burada event binding işlemlerini felan yapıyoruz ki,
// developer yeni buton oluşturduğunda onClick
// methodunu eklemesi gerektiğini söyleyelim.
};
app.menu.richMenu.actions.Abstract.prototype = {
onClick: function(){
app.error('Parent class does not support this operation: Called: ',
arguments.callee, ' Caller: ', this.constructor);
},
onSuccess: function(){
// göstermek istediğimiz ön tanımlı uyarı mesajını burada implement ediyoruz.
}
}
app.menu.richMenu.actions.Accept
app.provide('app.menu.richMenu.actions.Accept');
app.menu.richMenu.actions.Accept = function(){
this.element = document.createElement('a');
// burada event binding işlemlerini felan yapıyoruz ki,
// developer yeni buton oluşturduğunda onClick
// methodunu eklemesi gerektiğini söyleyelim.
}
app.extend(
'app.menu.richMenu.actions.Accept',
'app.menu.richMenu.actions.Abstract'
);
app.menu.richMenu.actions.Accept.prototype.onClick = function(){
// burada methodun implementasyonu var
}
app.menu.richMenu.actions.Accept.prototype.onSuccess = function(){
// Eğer bağlantı isteğini kabul etmişseniz, objemizin görünüşü,
// state'i değişiyordu. 'X artık arkadaşınız' gibi bir mesaj
// gösteriyoruz. Bu entegrasyonu burada yapıyoruz.
}
app.menu.richMenu.actions.Ignore
app.provide('app.menu.richMenu.actions.Ignore');
app.menu.richMenu.actions.Ignore = function(){
this.element = document.createElement('a');
// app.menu.richMenu.actions.Accept'deki aynı işlemleri Ignore için yapıyoruz
};
// Parent constructor'dan kalıtım alalım
app.extend(
'app.menu.richMenu.actions.Ignore',
'app.menu.richMenu.actions.Abstract'
);
app.menu.richMenu.actions.Ignore.prototype.onClick = function(){
// onClick de yapılacak işlemler
}
app.menu.richMenu.actions.Ignore.prototype.onSuccess = function(){
// ajax request sonrası neler yapacağız?
}
Bir gün yeni özellik gerekirse?
Yeni bir menü eklemek
Kullanıcı güvenlik ayarlarını değiştireceği yeni bir menümüz istendiğini varsayalım. Yazının başındaki şemalara tekrar göz atın, şimdi yanına bir menü daha eklemek istiyoruz. UML diyagramımızı şu şekilde değiştirdik:
Kırmızı renk ile gösterilmiş constructor’ları ekledik, şimdi bu iki constructor’ı yazalım:
app.menu.richMenu.types.Settings
app.provide('app.menu.richMenu.types.Settings');
// bağımlılıkları belirliyoruz
app.depends(
'app.menu.richMenu.items.Settings',
'app.menu.richMenu.types.Abstract'
);
app.menu.richMenu.types.Settings = function(){};
app.extend(
'app.menu.richMenu.types.Settings',
'app.menu.richMenu.types.Abstract'
);
app.menu.richMenu.types.Settings.prototype.init = function(){
//ajax request ve diğer işlemler burada
};
app.menu.richMenu.items.Settings
app.provide('app.menu.richMenu.items.Settings');
app.menu.richMenu.items.Settings = function(){
// constructor kodlar burada
};
app.extend(
'app.menu.richMenu.items.Settings',
'app.menu.richMenu.items.Abstract'
);
Bu yeni “settings” menüsünü kullanıma sunmak için implementasyon kodunda
new app.navigation.notifiable.Container([
new app.navigation.notifiable.Item('Request'),
new app.navigation.notifiable.Item('Notification')
], parent);
yerine
new app.navigation.notifiable.Container([
new app.navigation.notifiable.Item('Request'),
new app.navigation.notifiable.Item('Notification'),
new app.navigation.notifiable.Item('Settings')
], parent);
yazıyoruz. Evet sadece bir satır.
Yeni bir action eklemek
Bir süre sonra menü ile ilgili yeni bir geliştirme isteniyor. Kullanıcı isteklerinin gönderildiği “Request” menümüzdeki Accept, Ignore butonlarının yanına ek olarak Block seçeneği eklememiz gerekiyor. UML’i aşağıdaki şekilde güncelliyoruz:
Mavi renk ile gösterilen constructor’ımızı yazmamız yeterli olacaktır.
app.menu.richMenu.actions.Block
app.provide('app.menu.richMenu.actions.Block');
app.menu.richMenu.actions.Block = function(){
this.element = document.createElement('a');
// burada yine event binding işlemlerini felan yapıyoruz
}
app.extend(
'app.menu.richMenu.actions.Block',
'app.menu.richMenu.actions.Abstract'
);
app.menu.richMenu.actions.Block.prototype.onClick = function(){
// click methodu
}
app.menu.richMenu.actions.Block.prototype.onSuccess = function(){
// success de ne yapıyoruz?
}
Sonuç
Projede javascript bağımlılığı ajax/göster/gizle/slider gibi basit olsaydı bu tür bir mimariye hiç gerek kalmayacaktı. AngularJS veya EmberJS ile de bu kadar büyük bir projeyi tamamlayabilirdik. Fakat yine backend’de bir sürü markup oluşacak, ayrıca bu kadar “temiz” bir kod elde edemeyecektik. Performans ve taşınabilirlik konusunda da bu kadar yarar sağlayamayacaktık.
Ne elde ettik
Bakım: Bakımı yapılabilir, tekrar kullanılabilirliği yüksek, dökümantasyonu tam, sürdürülebilir bir ürün elde ettik. Yeni bir menü eklemek için bir constructor oluşturup, bağımlılığını singleton factory’e eklemek yeterli oldu. Yeni bir “rich menu” için ise aynı interface’leri kullanarak, diğerlerini bozma endişesi olmadan yine bir constructor ile çözebiliyoruz.
Taşınabilirlik: Bir süre sonra Symfony 1.4 den 2.0 a sadece 1 gün, evet şaka değil, sadece 1 gün de tüm frontend’i taşıdık. İleride bir gün çok bilen bir arkadaş “PHP kötü yeaaa hadi backend’i Java yapek!!1!” derse frontend de yine 1-2 günlük işimiz olacak.
Düşük library bağımlılığı: Kendi oluşturduğumuz “araç kutusu”nda bazı eksik yerleri gördük, eksikleri tamamlamak zaman alacaktı, tüm sistemi üç dört günde google closure a geçirdik. Biz sistemi oluştururken bilmiyorduk fakat closure’da bizim ile aynı yolları izliyormuş, bazı yerlerde oluşturduğumuz değişken, method isimleri, temel classların bile aynı olduğunu gördüğümüzde sadece bizim değil, google’ın da aynı metodolojiler ile çalıştığını farkettik, mutlu olduk.
Performans: Yaptığımız testlerde V8 ile (ajax request’ler hariç) hiç bir işlem 1 ms (yazıyla bir) den fazla sürmüyor. SpiderMonkey ile de 2 ms (yazıyla iki)’nin altında. (300-400 ms’e kadar kabul edelibelir olmasına rağmen).
Hata ayıklama: “Frontend çalışmıyoeaea” diyen testçimize, bir cookie set edip testleri tetiklemesini istedik. Eğer bizim maketlediğimiz json dosyaları ile tüm testlerden “pass” aıyorsak “backend’ci naber, hade iş başına” dedik. Hata giderme süreçlerimiz çok daha hızlandı.
Hızlı iş teslimi: “Facebook Wall”ı (sol menü ve header değil, ortadaki stream kısım) sadece 3 günde hemen hemen tüm özellikleriyle 1 developer yazdı (Aslında daha gelişmiş bir wall yazdık). Toplamda Wall’ın testi ve tamamlanması 1 developer ile sadece 5 gün sürdü. Post, comment, share, post ve comment için like buton, like edenleri göster, resim, video veya link önizleme, parent-child yorum sistemi vs. vs.
Çoklu dil desteği: Sunucudan sadece gerekli “json data objeleri” çekiyoruz, çoklu dil desteğini de frontend de tamamladık. (başka bir yazının konusu)
Nelerden bahsetmedik
Konu zaten uzun, daha da uzatmamak için hiç bahsetmediğim, eksik bıraktığım yerler var, izlediğimiz yol yazıda bahsettiğimden biraz daha detaylı;
– Buraya kadar FAZ1’di, FAZ2’den bahsetmedik.
– FAZ2’de facade pattern arkasına yerleştirdik ve addMenuItem(), bindMenuItem(‘ThirdMenu’) şeklinde menüleri oluşturduk.
– ajax, dom, extend ve interface için “light” bir library oluşturduk,
– app namespace’i altındaki “depends” methodu ile bağımlılıkları çözdük,
– Her bir constructor’un bir interface’i var aslında, interface’i implement etmiyor ise hata fırlatıyoruz.
– UML diyagramından hemen sonra testleri oluşturduk,
– Her bir ajax request için bir .json dosyası bıraktık sunucuya (mocked up!!),
– Backend’ci arkadaşlar sadece bu json’larda beklenen verileri sağladılar,
– Backend’e bıraktığımız .json dosyaları tüm “case”leri test edebilecek kadar detaylı,
– UML diyagramları çok daha detaylı, methodlar, değişkenler, bağlanacak ve dinlenecek event’leri belirledik,
– Deployment ve sürdürebilirlik için bir strateji geliştirdik,
– Dökümantasyonu JSDoc ile oluşturduk,
– Kaynak kodu web’den ulaşılamayacak bir yere koyduk, sadece derlenmiş javascript dosyalarına ulaşılabiliyor.
– Cache invalidate için sürüm numaralarını kullandık.
– JS yazarken constructor ve implementation katmanlarından değişik class’lar atadık, css hiç yazmadık.
– HTML ve JS tamamlandıktan, testlerden “pass” aldıktan sonra css uyguladık, js yazarken sitenin tasarımını görmemiştik bile.
– Sürümleme için <major>.<minor>.<bugfix> gibi bir yapı kullandık (1.0.0 gibi),
– Boss‘u özelleştirdik, projeye özel scriptler ekledik, boss ile deploy ettik.
Yazımıza son verirken esenlikler (şaka şaka, trt-1 miyiz biz esenlikler felan) “happy coding” diyoruz.
Not: Yazı çok uzun olduğu için çok iyi toparlayamadım, arada bug çıkabilir, kodlarda syntax hatası olabilir. UML çizimleri sıkış tıkış olmasının sebebi yazıya tam boyut ile sığdırmaya çalıştığım için, kusura bakmayın. Diğer çizimlerin çirkin olmasının sebebi ilkokuldaki resim öğretmenimin yüzünden.
Benzer Yazılar
15 Dakikada Frontend Yorumu
Firefox button taginda padding ve border sorunu
Opera ve Firefox’da body’e background bottom çalışmıyor mu ?
IE PNG problemi
2 Yorum Yapıldı
Hakan
Merhaba, Javascript kodlarında kullandığınız app.provide ve app.depends metodlarıyla ilgili internette hiçbir bilgi bulamadım. Bu metodları siz mi hazırladınız?
irfan
Evet, kendi hazırladığımız methodlar. Sistemi Google Closure'a "benzer" hazırlamışız, sonradan fark ettik. Google Closure'un goog.provide ve goog.require methodlarına bakarsanız deseni çözümlemenize yardımcı olabilir.
Yorum eklemek için çok geç, 04-08-2014 tarihinde yoruma kapatıldı.