Node.js’in Back-end Geleceği

Kelimelerin tarihine özel bir ilgi ve alakam var. Twitter’dan beni takip ediyorsanız zaman zaman kelimelerin nerelerden geldiğine dair şeyler paylaşıyorum. Bunun sebebi, “şeylerin” altında yatan gerçek anlamını, onun tarihine bakarak çıkarmak istemem. Bir şeyin, nasıl geliştiğini izlerseniz, nasıl o hale geldiğini de daha iyi anlarsınız.

İlk öğrendiğim programlama dili JavaScript. Sonrasında öğrendiğim tüm diller “JavaScript’te böyle yaptığım şeyi bunda nasıl yapabilirim” methodolojisi ile yürüyerek gelişti. Bu yüzden JavaScript’in tüm gelişim süreçlerinde ben bir JavaScript geliştiricisi idim. JavaScript yazdığım dönemlerde “JavaScript’in geleceği yok, boş işlerle uğraşma” diyen adamlar da gördüm, “JavaScript’in geleceği Dart, boş işlerle uğraşma” diyenler de gördüm. Tüm bu tartışmalardan haklı çıkarak ayrıldım. Bu gündemdeki Node.js tartışmalarından haklı çıkacağımı göstermez tabii ki, fakat en azından kendimce sunduğum tez ve antitezlerin bir nebze daha kuvvetli olacağını gösterir.

Her yerde belirttiğim gibi, JavaScript en başında tarayıcılar için tasarlanmış ve direkt tarayıcıların öncelikli ihtiyaçlarını gideren bir programlama dili. Ve gerçekten tarayıcı (önyüz) tarafında çok daha iyi.

JavaScript’in gelişim süreçlerine birinci elden tanık oldum. document.getElementByIdden Prototype.js’e, $’a, oradan da Angular’a ve React’a geçişlerin tüm süreçlerini ilk test eden ve uygulayanlardandım. Node ilk çıktığında “vay, çok iyi abi” diyenler arasındayım yine. Hatta Node için Microsoft’un bile official olarak kullandığı omelette paketim var (gurur duyuyorum). Ama hayat şartları sizi daha pragmatik düşünmeye itiyor ve yazılımcılar da “bilgisayara istediklerini yaptırmayı amaçlayan meslek sahipleri” olarak pragmatik olmalılar.

Back-end’de Event Loop (Olay Döngüsü)

Tarayıcıların CLI programlarına kıyasla en büyük karmaşıklığı, kullanıcıdan gelen inputun karmaşıklığı ve bunun yönetimi. Çünkü, kullanıcıdan gelen girdi (input) sonsuz ihtimalle gelebilir. Bunu CLI tarafında yönetmek için bir loop kurarsınız ve input beklersiniz, gelen input’a göre kullanıcıyı bekletirsiniz (blocking), işlem bittikten sonra da kullanıcıya bir çıktı verirsiniz.

Eğer bu akış mevcut kullandığımız tarayıcılarda olsaydı, muhtemelen internet ciddi can sıkıcı bir yer olurdu. Bu yüzden akıllı geliştiriciler, JavaScript VM’leri içerisinde event loop denilen (V8'te thread pooling şeklinde single-threaded çalışan) şeyi icat etmişler. Bu loop sayesinde, kullanıcı inputları event loop’a girer, kanalize olur ve yapılan işlemlerin çıktısını beklerken arayüz kilitlenmez, kullanıcı siteye hala bir şeyler göndermeye devam eder (non-blocking).

“Arayüz” doğasında event-loop mükemmel. Ama işler back-end’e geldiğinde “event loop” dediğimiz şey, üniversite hocalarının “aman ha kullanmayın” dediği “goto”dan hiçbir farkı yok.

Back-end’de “arayüz”ün doğasına en uygun yapı, tabii ki WebSocket’ler. Çünkü bir WebSocket bağlantısı arayüzdeki kullanıcıya bir kapı açıyor, ve back-end kodunuzu direkt olarak kullanabilmesini sağlıyor. Bu açıdan yazdığınız WebSocket bağlantısı bir event-loop’a ihtiyaç duyuyor. Node da bu noktada göğsünü gere gere WebSocket konusunda atıp tutabiliyor. event loop sistemine sahip olmayan programlama dilleri önce bu tarz bir yapı kazanmaya çalışıyor, sonrasında bu yapı üzerinden çalışmaya çalışıyor ve yine de günün sonunda Node’un önüne geçemiyorlar, çünkü WebSocket bağlantısı bir arayüz ve Node’un event loop’u, karmaşık inputları yönetmek konusunda arayüzden gelen bir uzmanlık taşıyor.

Python’da event loop gerektiren durumlar için kullanılan Tornado Python’un bu ihtiyacını gideriyor, çünkü back-end’de non-blocking yapılar istisnai. Fakat Node varsayılan olarak non-blocking olduğu için önce blocking gibi davranmasını sağlamanız gerekiyor, daha sonra non-blocking kısımları olduğu gibi kullanıyorsunuz. Yani back-end için “non-blocking I/O” kullanışlı bir durum değil.

“WebSocket kullanacaksam Node kullanırım”ın temelinde yatan şey de bu.

Velakin, Node’un sahip olduğu bu event loop, beraberinde callback hell de getiriyor. Çünkü non-blocking bir yapıda işleri callback’ler ile yönetirsiniz. Bu da her işinizi callback’lere atamanızı gerektirir. Bunu çözmenin yöntemleri var tabii ki, ama zaten sorun da aslında bunun sorun olması.

“Ama bunun için Promise’ler var” değil mi? Evet. Ama zaten değinmek istediğim sorun da bu aslında. Node’un karşılaştığı sorunları çözme şekli kendi gelişiminin hatalı bir gelişim olduğunu gösteriyor. Promise’lerin de geldiği son nokta, en nihayetinde .NET’in yıllar önce çözdüğü async / await ise yıllardır tekerleği yeniden icat etmeye çalıştığının bir göstergesi.

Node Sezgisel Değil

Tarayıcıdaki JavaScript tamamen sezgisel (intutive):

button.onclick = function () {
alert(1);
}

Burada karmaşık hiçbir şey yok. Çünkü “kullanıcı butona tıklayınca” dediğiniz şey tam olarak yukarıda yazdığınız gibi. Arayüz dünyasında event loop bizim sezgilerimizi tam olarak karşılıyor.

Fakat işler back-end’e gelince sezgiler farklılaşıyor. Normal bir back-end programlama dilinden geçmiş birisine “dosyadan yazı al yazdır” gibi bir kod yazmasını söylerseniz, %90'ı şunun gibi bir kod yazacaktır:

var file = fs.readFile(“./a.txt”);
console.log(file) //=> beklediğiniz şey değil.

Ah, tabii, Node.js “non-blocking”. Bunun için “readFile” yerine “readFileSync” kullanması gerekecek. Yani “uyarlanmış” halini.

Haydi o zaman bir de sleep’e bakalım. Programlama dillerinin %90'ında işler,

puts 1
sleep 2
puts 2
puts 3
sleep 1
puts 4
# buradan devam edebilirsiniz.

kolaylığında iken (olağanüstü sezgisel), Node’da event loop’la anlaşmaya oturmanız gerekiyor:

console.log(1);
setTimeout(function () {
console.log(2);
console.log(3);
setTimeout(function () {
console.log(4);
// buradan devam etmeniz gerek :( Ya da promise.resolve :(
}, 1000);
}, 2000);

Bu koddan sonra yukarıda bahsettiğim “callback hell”i de örneklemiş oldum sanıyorum.

Kimse ikinci kod gibi bir kodu maintain etmek istemez. Ben de “maintain edilebilir kod” olayının tamamen yazılımcı ile alakalı olduğunu savunuyorum, ama Türkçe’de “göz var izan var” diye bir deyim var ve doğru. Kolaylık göreceli olsa da basitlik göreceli değildir.

Bir başka sorun: Konvensiyon

Bir çok Node web projesinde çalıştım, .NET, Python ve Ruby web projelerinde de çalıştım. Fakat Node projelerinin farkı hiçbirinin birbirine benzememesiydi. Node’da belli bir kod düzeni mevcut değil, global olarak edinilmiş bir kod konvensiyonu mevcut değil. Bunun temel sebeplerinden biri de maalesef transpiler’ların kontrolden çıkmış olması. Herkes JavaScript’i olmadığı bir şey yapmaya çalışıyor. ES2015, ES2016, ES2017 gibi spec’lerin sonu belirsiz. ECMA’nın tam olarak ne yapmaya çalıştığını tam olarak kestirebilmiş birileri var sanıyoruz, ama denk gelmedik. Babel.js gibi araçlar versiyonlar arasında uçurumlara sahip. Bir Node projesine girmeniz için önce kullandığı tüm tool’lara hakim olmak zorundasınız. Bir proje WebPack kullanırken diğeri Browserify kullanıyor. Bunu çok daha fazla örnekleyebilirim.

Geleceği: Desktop ve (belki) IoT. Ama back-end değil.

Geleceği kesinlikle muallak değil. Node çok sağlam şekilde yer etti ve geleceği muallak değil. Ama back-end dünyasında “daha az tercih edilen” bir yapıya geçiş olacak ve önemini tooling üzerine kuracak. Yine event loop iyi olduğu “arayüz geliştirme” tarafında tahtı kaptırmayacak. Zaten Electron, NW.js gibi projeler bunu yerine getiriyor. Ayrıca şahsi fikrim, IoT tamamen “karmaşık kullanıcı girdisi” tanımına uyduğu için event loop temelli Node IoT tarafında çok fazla sezgisel bir yapıyla öne geçebilir. Bu konuda Tessel’ın kodlarını inceleyebilirsiniz, son derece sezgisel olduğunu göreceksiniz.

Bir JavaScript geliştirici olarak yıllarca JavaScript’in tahtıyla ilgili zaman zaman tartışmalara şahit oluyorum. Ve diyebileceğim şu ki, bu noktada JavaScript’in bu tahtı kaptırma ihtimali yok. Ama Node’un “back-end” tarafındaki geleceğini parlak bulmuyorum.