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.

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ı.

Pratik ssh

Bazen sadece unutmamak için not almaktır hayatın anlamı;
$ vim ~/.ssh/config şeklinde dosyayı açıp aşağıdaki satırları yazıyoruz.

Host myserver
User irfan
Port 9281
HostName irfandurmus.com

SSH’dan makinemize bağlanmak için

$ ssh myserver yazıyoruz, bu komut ssh -p 9281 irfan@irfandurmus.com şeklinde overwrite oluyor.

SSH üzerinden git kullandığımızda da böyle ayarlara ihtiyacımız olmuştu.

Developer’s prompt

Prompt’un ne kadar önemli olduğunu (daha bir kaç yıl önce) farkettiğimde bi’ snippet yazmıştım, geçenlerde github’a push’lamıştım. “Unix like” bir sistemde prompt’da genelde hostname, username felan görürsünüz. Sistemci için “irfan@localhost:~$” gibi bir prompt iyi olabilir fakat bir developer için çok gereksiz veriler bunlar.

Yeterli olduğunu düşündüğüm için geliştirileceğini veya buna ihtiyaç duyulacağını düşünmemiştim, bu yüzden snippets repoma göndermiştim kodu. Bu gün Berker “Bunun sonuna svn de bulunduğumuz branch’i eklesek?” demesiyle bana bi aydınlanma geldi.
(daha&helliip;)

Mac OS X Developer Tools’u kaldırmak

$ sudo /Developer/Library/uninstall-devtools --mode=all
Password:
Start time: Sun Dec 12 01:03:08 EET 2010
Started uninstalling versioned non-relocatable developer tools content.
Shutting down distcc...
Analyzing devtools package: 'com.apple.pkg.VersionedDeveloperToolsSystemSupportLeo'...
Analyzing package: 'BSD.pkg'...
......

Söze gerek yok

Firefox button taginda padding ve border sorunu

Daha önce yaşadığım ve yaklaşık 30 dakikama mal olan bir problemdi bu. Aynı problemle bir başka projede tekrar karşılaştım, not almak gerekliymiş demekki -)

Button tag’ını severim kullanırım. Button tagı sayesinde;

  • input[type="submit"] gibi css atraksyonlarına gerek kalmaz.
  • button{} selector’ü input[type="submit"]{}
    selector’ünden daha hızlıdır.
  • Form’larda post/reset gibi event’lar için kullanılabilir.
  • Tag içerisinde html kullanılabilir.
  • Projenin tamamında kullanılacak buton görselleri tek etiket ile şekillendirilir.
  • Daha kolay bir regex ile tüm buton taglarını grep’leyebiliriz.
    • Button tag’ını grep’lemek için;
      $ grep -rHn '<button.*>' *
    • Input butonları grep’lemek için;
      $ grep -rHn '<input.*type=\"submit\".*' *

Firefox button tagına padding ve border -input button a da- uyguluyor. Chrome, Safari, Opera gibi tarayıcılarda bu tür bir problem ile karşılaşmadım.

Çözüm;

button::-moz-focus-inner {
border: 0;
padding: 0;
}

Şöyle ecnebice kaynaklarda var;

http://forum.userstyles.org/comments.php?DiscussionID=2773
http://www.aheadcreative.com/removing-unwanted-button-padding-in-firefox/
http://stackoverflow.com/questions/1433232/moz-focus-inner