Backbone Form’da İlk Geçersiz Elemana Gitmek

Önyüzünde Backbone kullandığımız projeye eklenmesi gereken basit bir özellik var.

– Formda 12 tane alan vardır bunların *,*,* alanları zorunludur.
– Kullanıcı formu doldurur.
– Form geçerli ise listeleme sayfasına yönlendirilir.
– Geçersiz ise ilk hatalı input viewport’a gelir (scroll yapılır).
– Hem desktop hem mobilde aynı akış sağlanmalıdır.

Ne mobilde ne de masaüstü sürümde (responsive, esnek tasarım) form elemanının yerini tespit edip scroll yapamayacağımıza göre en güzel çözüm o elemanı aktif hale getirmek, yani “focus” event’i tetiklemek. Backbone size form yapısı sağlamıyor, fakat @powmedia’nın formu hemen hemen bütün ihtiyaçları karşılıyor.

Sistemde kullandığımız hiç bir kütüphaneyi abstract bir katman eklemeden kullanmıyoruz. Dolayısı ile kendi oluşturduğumuz Form, Backbone.Form u extend eden abstract katman. Yapımız AMD (Asynchronous Module Definition) ve dependency manager olarak RequireJS kullanıyoruz. Bunlarla birlikte Backbone bağımlılıkları olan jQuery, underscore gibi kütüphaneleri de sayfaya yüklememiz gerekiyor.

Çalıştığımız dizine RequireJS, Backbone ve BackboneForms u koyduktan sonra BackboneForms’dan kalıtım alan modulümüzü şöyle yazıyoruz:

Not: Ön bilgi vermekte fayda var, sayfamızda Backbone.Form github’daki powmedia’nın geliştirdiği formdur. Radiobox, selectbox gibi form elementleri editors namespace’i altında toplanmış. Siz de kendi form element türlerinizi geliştirebilirsiniz. Mesela yukarıdan gelen bir başka istekde bir sistemdeki tüm fiyat belirten inputların başına para cinsi, sonuna da “.00” şeklinde küsüratı sabitlememiz istenmişti, biz bunu Backbone.Form.editors.PriceField şeklinde tanımladık, formların şema tanımlamalarında da “type: ‘Text'” yerine “type: ‘PriceField'” yazarak basitçe çözdük.

Tüm form elementleri Backbone.Form.Field‘i extend ediyor. Backbone.Form.editors namespace’i altında da üst paragrafda bahsettiğim form eleman türleri var. Yani checkbox’ı override etmek için Backbone.Form.editors.Checkboxes, veya hidden input’u yeniden tanımlamak için Backbone.Form.editors.Hidden methodunu ezmeniz gerekiyor.

Bu blog yazısında bahsettiğimiz yer Backbone.Form.Field methodunu yeniden tanımlamak. Fakat ileriye dönük farklı ihtiyaçları da karşılayabilmek için biz burada Backbone.Forms’u da boş şekilde extend ettik.

/**
 * Author: Irfan Durmus 
 * Date:   Thu Apr 3 12:03:32 2014 +0400
 *
 * Customized Backbone Form Asynchronous Module in order 
 * to provide atuo-focus to first error field in Backbone Forms.
 */
define([
    'backbone',
    'backbone-forms'
], function(
    Backbone,
    BackboneForms
){
    // we'll need this variable to count how many element we have in our form.
    var counter,

    // extend Backbone.Form element to implement new features in the future
    Form = Backbone.Form.extend({});
    
    Form.Field = Backbone.Form.Field.extend({
        /**
         * Overwrite the setError method of Backbone.Form.Field
         * to focus first element when error occur
         */
        setError: function(msg) {
            Backbone.Form.Field.prototype.setError.call(this, msg);
            this.hasError = true;
            counter++;
            this.findError();
        },
         
        /**
         * Overwrite the clearError method of Backbone.Form.Field
         */
        clearError: function() {
            Backbone.Form.Field.prototype.clearError.call(this);
            this.hasError = false;
            counter++;
            this.findError();
        },
        
        findError: function() {
            var totalSize = _.size(this.form.schema),
                focusField = null;

            if (counter == totalSize) {
                // set counter 0 to able to manage next form action;
                counter = 0;
                _.each(this.form.fields, function(field, key, fields) {
                    if (!focusField && field.hasError) {
                        focusField = field;
                    }
                });
                if (focusField) {
                    focusField.focus();
                }
            }
        }

    });
    
    // return and use this extended form class instead of Backbone.Forms;
    return Form;
});

Form’u post ettiğimizde, form her bir elemanı için, clearError veya setError methodlarını çağırıyor. Biz yine formun sağladığı methodları static olarak Backbone.Form.Field.prototype.clearError.call ile çağırarak normal davranışını bozmadık. Sonra hata olup olmadığını anlamak için hasError’u set ettik (sanırım formda zaten var bu, satırı silip denemedim). Her bir form elemanı için her iki method’dan birisi çağrıldığına göre counter++ ile iki methodda da artırım yapmamızın bir saknıcası yok. Son olarak da kendi tanımladığımız findError methodunu çağırıyoruz.

Not: Diğerleri nasıl çözüm üretmiş diye araştırdığımda ingilizce kaynaklarda bile object orient bir çözüm olmaması çok garip gelmişti.

Kahrolsun Bağzı Shell’ler

Ba’ğ’zı şeyler vardır ki ayda yılda bir lazım olur, her seferinde manuelde gezip zaman kaybetmek can sıkıcı. Son 5 yıl içinde 3-4 kez git sunucu kurdum, her seferinde de manuel e ihtiyaç duydum. Unutmamak için aldığım ‘not’ yazılarından biri de bu olacak.

Güvenli Git Sunucu Kurulumu

apt-get install git git-sh ile paketleri kurduktan sonra ‘git’ kullanıcısını debian üzerinde şu şekilde ekliyoruz:
$ adduser --system --home /var/services/git --shell /usr/bin/git-shell --disabled-password git

Burada parametreler önemli.

--system ile kullanıcının “system user” olacağını belirledik ve /etc/skell altındaki .profile gibi dosyaların kopyalanmamasını sağladık.
--home ‘a /var/services/git parametresini geçerek ‘git’ kullanıcısının dizinini /var/services altında olmasını istedik, normal kullanıcılar ile karışmasın.
--shell parametresi güvenlik için önemli “kahrolsun diğer shell’ler”. git kullanıcısı hack’lense bile sadece git işlemleri yapılabilecek, sisteme ulaşılamayacak.
--disabled-password ile “şifre ile giriş yapılamaz” olarak ayarladık. SSH key ile hala giriş yapılabilir, bize de bu lazım. Tabii ki ssh ile normal shell işlemleri de yapılamaz, –shell /usr/bin/git-shell ile bunu da engellemiştik, ssh user@server şeklinde bir giriş mümkün değil.

Özetle, sadece git işlemlerine, sadece ssh key kullanarak yapılmasına izin verdik. Ev dizinini değiştirmemizin bir güzelliği de git@irfandurmus.com:/var/services/git/myblog gibi çirkin/kullanışsız bir yapıyı da engelledik. Geliştiricilerin sadece git@irfandurmus.com:project şeklinde kullanmaları yeterli olacak.

/var/services/git dizin git’in home dizini ve boş. Şimdi repository oluşturalım,
$ cd /var/services/git
$ mkdir myblog.git
$ cd myblog.git
$ git init --bare
$ ls
branches config description HEAD hooks info objects refs

/var/services/git/.ssh/authorized_keys dosyasını oluşturup, içerisine kullandığınız bilgisayardaki ~/.ssh/id_rsa.pub dosyamızı yazıyoruz, bildiğiniz standart ssh key login. (Dosya bilgisayarınızda yoksa ssh-keygen -t rsa ile oluşturabilirsiniz.) Takımdaki diğer arkadaşların id_rsa.pub dosyalarını da ekliyoruz buraya.

Tüm işlemleri root ile yaptığımız için /var/services/git/ dizini altında chown -R git:nogroup . komutunu çalıştırarak yetkileri düzenleyelim.

Hepsi bu kadar, bakalım çalışıyor mu;
$ git clone git@irfandurmus.com:myblog
Cloning into 'myblog'...
The authenticity of host 'irfandurmus.com (***.**.***.***)' can't be established.
RSA key fingerprint is **:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'irfandurmus.com' (RSA) to the list of known hosts.
warning: You appear to have cloned an empty repository.
$ cd myblog/
$ touch foo
$ git add .
$ git commit -am "foo"
[master (root-commit) 9b630ea] foo
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 foo
$ git push -u origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 201 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@irfandurmus.com:myblog
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
$ git remote -v
origin git@irfandurmus.com:myblog (fetch)
origin git@irfandurmus.com:myblog (push)

Happy Coding!

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:

Javascript UML

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:

JavaScript UML

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.

MacPorts ve diğerleri

Homebrew hakkında çok güzel olduğunu iddia eden yazılar gördüm fakat adamakıllı bir kanıt göremedim ben. Homebrew’i aylar önce denedim ve kurduğum ilk gün 3 tane bağımlı paketin olmadığını görünce kırk takla atarak ports a göre döndüm. Kullanacağınız paket yöneticisinde paket zenginliği çok önemli. Kuracağınız paketlerin bağımlılığının olması doğaldır.  Paket yöneticisi seçiminde paket zenginliği en önemli konulardan birisidir.

MacPorts çok yetenekli, neden macports un homebrew den daha üstün olduğunu gösterecek bir kaç örnek vereyim.

Zenginlik

Php 5.3 kullanıyorum, 5.4 ü de sistemime kurmak arada denemek istiyorum. Kullandığım bilgisayarda sudo port activate php54 ile version değiştirip, çalışmayan yer olup olmadığını test edip, herşey düzgün ise sunucuyu da 5.4 e yükseltebiliyorum. Eğer sorun varsa sudo port activate php53 ile önceki versionu aktif edebiliyorum. Aynı şey python veya diğer paketler için de geçerli.

Stabilite

Diyelim ki php5 paketini kurdum. php5 son stabil versiyona linklidir. Yani 5.3 stabil iken 5.3 e linklidir. 5.4 stabil olmuş ise 5.4 e otomatik yükseltilir ve 5.3 sisteminizden kaldırılmaz! Önemli nokta burası. Sistemi güncellediniz, sorun çıktı sudo port activate php53 ile geri dönebilirsiniz.

Ports /opt/local altına kurulur, MacOSX ile birlikte gelen hiç bir uygulamanıza karışmaz, dokunmaz. Kaldırmak istediğinizde de yardım bulması çok kolay.

Sistem Bakımı

Kolay sistem bakımı sağlar. port installed |grep -v “active” ile aktif olmayan paketlerin listesini alıyorsunuz. Baktınız hiç birisi işinize yaramıyor ve bir daha da kullanmayacaksınız. sudo port uninstall inactive ile hepsini kaldırıyorsunuz.

Upgrade / Downgrade

sudo port selfupdate ile sistem veritabanını günceller ve yeni versionları kurarsınız. Debian’daki apt-get update e eşittir. Sorun ile karşılaşırsanız zaten kurulmuş olan eski versionu güncellemek için sadece activate etmeniz yeterli.

Örneğin sunucunuzda php 4.4.9 gibi çok eski bir version var. Karşılaştığınız hatayı kullandığınız bilgisayarda görmek istiyorsunuz. “sudo port install php4 @4.4.9” yazıyorsunuz ve php4 ün 4.4.9 versionu u Macintosh’unuza MacPorts aracılığıyla kuruluyor. Hatayı kendi bilgisayarınızda tekrarlayıp, çözüp commit ediyorsunuz.

Dökümantasyon

En önemli noktalardan biridir dökümantasyon. port help yazıyorsunuz ve şöyle bir çıktı alıyorsunuz (sadece bir kısmı bu).

Supported actions
——————
activate, archive, archivefetch, build, cat, cd, checksum, clean, configure,
contents, deactivate, dependents, deps, destroot, dir, distcheck, distfiles,
dmg, dpkg, echo, edit, exit, extract, fetch, file, gohome, help, info,
install, installed, lint, list, livecheck, load, location, log, logfile,
mdmg, mirror, mpkg, notes, outdated, patch, pkg, platform, portpkg,
provides, quit, rdependents, rdeps, rev-upgrade, rpm, search, select,
selfupdate, setrequested, space, srpm, submit, sync, test, unarchive,
uninstall, unload, unsetrequested, upgrade, url, usage, variants, version,
work

Bunlar sadece action lar. Yani alt komutlar ile bir çok işlem yapabilirsiniz. Daha fazla detay isterseniz kocaman man sayfaları var. homebrew de yardım ı çalıştırınca websitesine yönlendiriyor, ne saçmalık! İnternetim yoksa dökümanım da mı olmayacak?

İnternette Macports’a çok haksızlık ediliyor. Bir sisteme kötü demek için yaşanan problemlerin diğer insanların da yaşayıp yaşamadığına bakmak lazım. Bazı sorunlar “kullanıcı” kaynaklıdır.

MacPorts kullanıyorum, mutluyum. Stabil, başağrısız, bol paketli, güzel dökümantasyonlu bir paket yöneticisi arıyorsanız homebrew, fink gibi atraksyonlara girmenizi hiç tavsiye etmem.

 

Boss ve Git ile nasıl deployment yapılır

Hala FTP ile live server’a dosya gönderenlerin oranı web üzerindeki domainlerin çoğunluğunu oluşturuyor. Revision Control kullananların bir kısmı da doğru sürümleme yapmıyor. Oyunu kurallarına göre oynamak, ürünün sürdürülebilirliği açısından önemlidir.

İşlerinizi otomatikleştirmek, en zayıf halka olan insan hata oranını olabildiğince aza düşürür, size zaman kazandırır. Yazılımınızın deployment stratejisi olması bu noktada önemli. Büyük bir ekip de olsanız, reklam ajansında küçük web siteleri yapıyor da olsanız düzeninizi kurduktan sonra sorunların çok daha hızlı çözüldüğünü göreceksiniz.

Boss Parametreleri

help Yardım dosyasını gösterir
test Local, stage veya live sunucu üzerinde verilen projenin testini tetikler.
deploy Verilen server üzerine verilen projenin verilen sürümünü deploy eder.
rollback Local, stage veya live sunucu üzerinde son yapılan deployment’i geri alır.
project Yeni proje eklemek, listelemek ve silmek için kullanılır.

Deployment

Web dünyasında deployment dediğimiz zaman akla gelen işlemler;

  • Code base update
  • Get pass frontend unit/integration tests
  • Pass backend tests
  • File permission updates
  • Database update (sql/no-sql)
  • Cache invalidation

Bu işlemleri önce kullandığınız bilgisayarda, sonra stage sunucuda son olarak da live sunucuda yaparsınız. Boss‘u ben bu işlemler için kullanıyorum. Farklı deployment yaklaşımları olabilir, size kalmış. Örneğin birden fazla application server’iniz var ise boss size yetersiz gelecektir, henüz çoklu live sunucu desteklenmiyor.

Bir kurulum ile birden fazla proje deploy edebilirsiniz. Sürüm için branch değil tag kullanır, kurulum çok basit;

$ git clone git@github.com/irfan/boss.git boss
$ cd boss
$ sh install.sh

Proje ismi, git repo, projenin bilgisayarınızda ki yolu, stage ve live sunucu bilgilerini girdikten sonra deployment için ilk proje config dosyanız ~/.boss/etc/<projeadi>.conf altında oluşturulacak.

NOT: Bu bilgileri eksiksiz girmelisiniz. sunucudaki proje dizininiz örneğin /var/www altında ise normal user ile burada değişiklik yapamayacağınız için root ssh login’in sunucuda açık olması ve root’u kullanmanız kolaylık sağlar. Eğer normal kullanıcı girerseniz, proje dizininiz /home/irfan altında olsa bile web sunucunuz www-data gibi bir kullanıcı ile çalıştığı için yine sıkıntı olacaktır. Fakat web sunucunuzu ssh ile aynı kullanıcıyla çalıştırıyorsanız sorun yaşamazsınız.

Versioning

Verisyon numaralarının anlamlı olması önemli. Bu konuda düşünceleriniz net değilse Wikipedia daki Software versioning başlığı sürümleme nedir ve nasıl olmalıdır konusunda size ön bilgili verebilir.

Boss sadece tag ile deployment yapabilir. Zaten branch ile yapılan sürümleme işlemi yanlıştır. Bu konu da ayrı bir yazının konusu.

Birinci sürüm deployment

Local, live ve stage sunucunuz da git repository’nizin olduğunu ve git pull yaptığınız zaman git sunucunuzdan güncellemeleri aldığınız bir sisteminizin olduğunu varsayarak devam ediyoruz. HEAD’inizi 1.0.0 sürümü oluşturarark sırasıyla local, stage ve live a deploy edelim.

NOT: Deployment a başlamadan önce .gitignore dosyanıza .boss/* satırını eklemelisiniz. .boss dizini altında deployment ve sürüm logları tutulmaktadır.

$ git status
# On branch master
nothing to commit (working directory clean)
$ git tag
$ git fetch --tags
$ git tag -a 1.0.0 -m "Version 1.0.0 releasing"
$ git push --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 172 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@happycoding.net:blog
* [new tag] 1.0.0 -> 1.0.0
$

Bulunduğumuz commit’i yani HEAD’ı 1.0.0 version olarak oluşturduk ve git server’a gönderdik. Şimdi bunu satge a deploy edip testlerimizi çalıştıralım.

$ boss deploy stage blog 1.0.0
==========================================
Deploying to: stage
Project: blog
Version: 1.0.0
==========================================
Pseudo-terminal will not be allocated because stdin is not a terminal.
--> Directory changing
--> creating log directory
fatal: No names found, cannot describe anything.
fatal: No names found, cannot describe anything.
--> Current version is
--> Checking uncommitted changes
--> Fetching tags
--> Pulling with flag
--> Getting new version
--> Setting permissions
Deployment succeed.
Current version is 1.0.0
$

İki tane fatal görünüyor, ilk kez deploy ettiğiniz için bunlar görünüyor. Bir sonraki sürümde görünmeyecek, bunları görmezden gelip devam edebiliriz. Şimdi sıra testlerimizi çalıştırmada.

NOT: Projenizi yapılandırırken testleri trigger edecek scriptin bulunduğu path i ve parametreleri girmiştiniz. Proje dizininizin altında bu scriptin bulunduğunu ve gerekli testleri tetikleyip sonucu verecek yeteneğe sahip olduğunu varsayıyoruz.

$ boss test stage blog
==========================================
Testing on: stage
Project: blog
Version: 1.0.0
==========================================
--> Running 74 frontend tests, please wait
--> 73 of 74 passed
--> 1 test failed:
- Test ID : 12
- Error : User ID could not found in user object
--> Running 138 backend tests, please wait
--> 138 of 138 test passed
Connection to irfandurmus.com closed.
$

Bir testimiz başarısız oldu. Gerekli değişikliği yapıp master branch’a commit edip, sunucuya push’luyoruz. Şimdi bir bugfix yapmış olduk ve bu ayrı bir sürüm olmalı. Sürümümüzün adı 1.0.0-1 olsun.
$ git tag -a 1.0.0-1 -m "12th failed test fixed"
$ git push --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 173 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@happycoding.net:blog
* [new tag] 1.0.0-1 -> 1.0.0-1
$

Şimdi tekrar stage e deploy edip testleri çalıştıralım.
$ boss deploy stage blog 1.0.0-1
==========================================
Deploying to: stage
Project: blog
Version: 1.0.0-1
==========================================
Pseudo-terminal will not be allocated because stdin is not a terminal.
--> Directory changing
--> Current version is 1.0.0
--> Checking uncommitted changes
--> Fetching tags
--> Pulling with flag
--> Getting new version
--> Setting permissions
Deployment succeed.
Current version is 1.0.0-1
$

Gördüğünüz gibi stage sunucu üzerindeki sürüm 1.0.0 dan 1.0.0-1 e yükselmiş oldu. Şimdi testleri çalıştıralım.
$ boss test stage blog
==========================================
Testing on: stage
Project: blog
Version: 1.0.0-1
==========================================
--> Running 74 frontend tests, please wait
--> 74 of 74 passed
--> Running 138 backend tests, please wait
--> 138 of 138 test passed
Connection to irfandurmus.com closed.
$

Tüm testlerimiz başarılı olduğuna göre artık live sunucuya bu sürümümüzü deploy edebiliriz.
$ boss deploy live blog 1.0.0-1
==========================================
Deploying to: live
Project: blog
Version: 1.0.0-1
==========================================
Pseudo-terminal will not be allocated because stdin is not a terminal.
--> Directory changing
--> Current version is 1.0.0-1
--> Checking uncommitted changes
--> Fetching tags
--> Pulling with flag
--> Getting new version
--> Setting permissions
Deployment succeed.
Current version is 1.0.0-1

İstersek testlerimizi live sunucu üzerinde de çalıştırıp bir hata varsa aynı şekilde fix edebiliriz fakat stage ile live aynı sonucu vermiyorsa sorun sistemler arasındaki farktadır.

Son olarak belirtmeliyim ki boss kesinlikle bir Jenkins veya Hudson alternatifi değildir, olamazda. Sadece işlerin nasıl olması gerektiğini gösteren basit bir script’dir.

15 Dakikada Frontend Yorumu

Ne kadar iyi olsanızda iş görüşmesinde kendinizi anlatabilmeniz işi ne kadar iyi bildiğinizden daha önemli kısımdır. Yazılım sektöründeki diğer uzmanlık alanlarına göre (embeded, desktop, elektronik vs.) bizim alanımızda daha zordur. Web’in kolay ulaşılıbilirliğinden kaynaklanan “msn hekleme maceraları”ndan yetişen, cms/blog kurup kendini yazılımcı olarak gören çok insan var. Dubai’de henüz masanın karşı tarafında bulunmadım fakat bir kaç arkadaştan duyduğuma göre Türkiye’den çok farklı değilmiş.

Sizin farkınızı ortaya koymanız gerekir. “Teknik eleman işe alım süreçleri”ne hakim birisiyle iş görüşmesi yapıyorsanız işiniz daha kolay, seviyenize göre daha mantıklı sorular yöneltilecek, sizi paniğe sürüklemeyecektir. Yaptığım onlarca iş görüşmesinden Senior PHP Developer olarak başvurduğum birinde bana çift tırnak ile tek tırnak farkı sorulmuştu. Yazılımcıda lazım olan bu bilgimidir yoksa hızlı ve doğru algoritma ile kodu spagetti ye götürmeden maintainable çözüm üretmesi midir? Diğer sorularda bundan farklı değildi. Seviyenin üzerinde soru soruluyorsa “bilmiyorum” dersiniz, bu bile bir çözümdür. Fakat seviyenizin altında gelen sorular nasıl bir takım içerisinde çalışacağınız hakkında sizde kötü izlenim bırakacaktır.

Dubai’de bir iş görüşmesinde önce genel müdür ile görüştüm, sonra backend ile ilgili teknik bir görüşme yaptık. Arkasından benden siteyi incelememi ve frontend kısımda neler yapabileceğimizi ertesi gün göndermemi istediler. Aslında en sevdiğim yöntem bana test-case göndermeleridir. Fakat bu yöntem de fena değil. Önünüze bir ürün koyuyorlar ve “hatası ne, neleri geliştirebiliriz” diyorlar. Büyük bir sistemde backend hakkında bir şeyler söyleyebilmeniz için bir süre çalışmanız gerekir. Frontend bu kadar zor değil. Gösterilen siteye 15 dakika göz attım ve aşağıdaki maddeleri çıkarttım. Bu maddelerin bazıları Türkiye’deki bir çok büyük sitede olan eksikler. Frontend kısımda neler/nasıl geliştirilmelidir hakkında belki size bir fikir verir.

Gönderdiğim e-mailden doğrudan copy/paste, çevirmiyorum. Şimdi düşündüğümde bu liste 25 e kadar uzar. Çift yazılanlar var fakat 15 dakika inceleyip, 5 dakikada yazma şartını bozmak istemediğim için listeyi değiştirmek, gramer hatalarını düzeltmek istemedim.

1- Reduce JS / CSS file calls
2- Move all of the inline css/javascript codes to css/js files
3- Get pass all YSlow tests
4- Reduce event binding (Use delegation)
5- Reduce traffic beetween javascript and layout engine.
6- Put all the script tags before closing body tag. (Bottom of the page)
7- Use nonblocking pattern to load javascript files. (asynchronous)
8- No “error tracking code” for frontend errors? How we’ll be notice if someone has an issue in different case? We cant test 10 different OS and 100 different browser.
9- 1 year expire date is too much, it should be determined depends to deployment strategy. It can be cause some problem in some users browser.
10- Write OOP Javascript in module pattern.
11- Prepare a deployment script that it will able automaticly obfuscate and minifiy all js and css files.
12- Use jslint or jshint to improve frontend code quality.
13- Dont use any js line of codes which is invisible by javascript garbage collector. (Firefox using too much memmory because of this)
14- Before start to write js codes, we should write test codes. Use BDD (Behaviour Driven Development)
15- Use module pattern for different part of web pages.
16- jQuery is good for newbie’s, but it is not good for extreme js development. Be careful when you need jQuery!
17- Cache all jQuery selectors.
18- Apply all of the advices in the slide: http://www.slideshare.net/irfandurmus/javascript-performance-optimisation

 

Siz 15 dakikada bir site hakkında neler söyleyebilirsiniz?
Happy Coding!

Bash ile temizlik

Sanırım ilk göz ağrım olmasından dolayı bash ile oynamayı seviyorum. Rapidshare’den 10.000 kadar dosyayı toplu olarak zinker ile indirdim.
fakat bazı dosyalar sunucuda bulunmadığı için dosya yerine sunucunun döndüğü hata kodunu kaydetmişiz. Bu bozuk dosyaları bulmak için dosyaların olduğu dizin de;

for file in $(ls); do notzip=`file "$file" | grep -v Zip`; if [ ${#notzip} -gt 1 ]; then echo $notzip; fi; done;

İçeriğine bakmak için;

for file in $(ls); do notzip=`file "$file" | grep -v Zip`; if [ ${#notzip} -gt 1 ]; then cat $file; fi; done;

Silmek için;

for file in $(ls); do notzip=`file "$file" | grep -v Zip`; if [ ${#notzip} -gt 1 ]; then rm $file; fi; done;

Hepsi bu..

Mac OS X Mountain Lion named servisi

Sadece internette hızlı gezmek değil, hosts dosyanıza tanımladığınız domainlere de daha hızlı erişmek için local dns server candır.

Mountain lion da “service” kalkmış, dolayısıyla şurada bahsettiğim yöntem çalışmıyor.

Bunun yerine terminalden

$ sudo rndc-confgen -a
$ sudo launchctl load -w /System/Library/LaunchDaemons/org.isc.named.plist

bu iki kurşunu verdikten sonra, System Preferences e gidip DNS ayarlarınızı 127.0.0.1 olarak değiştirip, ~20 ms daha hızlanmış oluyoruz.

Dediğimiz gibi, not almaktır hayatın anlamı.

ffmpeg ile en basit mp4 yapmak

İnternette konuyla ilgili bir sürü makale görürsünüz, sürüyle parametre, yok o codec bulunamadı, yok bunu kullanamazsın gibi hatalara sebep olabilir.
iPad için en basit ve yeterli yol;

ffmpeg -i myVideo.avi -s 1024x768 myVideoForiPad.mp4
800×600 ekrana sahip bir cihazınız varsa, 1024×768 i 800×600 olarak değiştirin.

Şöyle toplu dosya işlemi de yapabiliriz.

#!/bin/bash
counter=1;
addition=1;
for i in *.avi
do
    ffmpeg -i $i -s 1024x768 "episode-"$counter".mp4"
    counter=$(($counter + $addition));
    mv $i "converted/"$i
done;

Bazen unutmamak için not almaktır hayatın anlamı.