Gönderen H.Levent KARAGÖL Etiketler:

Bu makalemizde, Node.JS'in Express modülü ile JSON formatında cevap dönen Restful API'leri kolayca nasıl tasarlayabileceğimizi göreceğiz.

Bir önceki makalede, Node'un yaygın olarak kullanıldığı yerleri gruplamaya çalışmıştım. Orada da söylediğim gibi, fantezide sınır yok ancak kullanım alanlarının büyük çoğunluğu, dışarıdaki bir aktöre, hizmet vermek üzere bir uç birim sağlamak üzerine kurulu. Buradaki "aktör" kelimesinin karşılığı, kendimizin yazdığı programlar olabileceği gibi, başkalarının yazdıkları programlar hatta gerçek kişiler bile olabilir. "Hizmet" kelimesinin karşılığı ise, belirtilen kriterlere uygun verileri sağlamak, başkalarının gönderdikleri verileri kayıt altında tutmak, değiştirmek veya silmek gibi işlemler olabilir.

Bu tarz, iyi tanınan uç birimlere örnek vermek istersek;
  • Merkez bankasının günlük kur bilgilerini yayınladığı servisi
  • Google Maps API
  • Twitter API
  • Bankaların VPOS entegrasyon servisleri
  • Gelirler İdaresi E-Fatura servisi

şeklinde örnekler verebiliriz. Bunların her biri, farklı teknolojiler ile geliştirilmiş ve farklı amaçlara/aktörlere hizmet vermektedir. Bazıları tüm kullanıcılara açıkken, bazıları sadece belirli bir kısım kullanıcının erişimine açıktır. Yine bunlardan bazıları sadece kullanıcılara veri sağlarken, bazıları sadece kullanıcılardan veri almak için, bazıları da her iki yönlü de çalışmak üzere tasarlanmıştır. Ancak burada saydığım servislerin tamamının ortak özelliği, sahibi olduğu kurum ile servisi kullanacak üçüncü şahısların entegrasyon kurmasına imkan sağlamasıdır.

Bu tarz servislere genel olarak verilen isim API (Application Programming Interface)'dir. Bir kurum, kullanıcılar için API hazırladığı zaman, bu API'ye nasıl bağlanılacağını, hangi işlem için nasıl mesaj gönderileceğini ve alınan cevabın nasıl yorumlanması gerektiğini, örnek akış ve mesajlarla birlikte anlatan bir doküman yayınlar. Bu dokümana da ISD (Interface Specification Document) denilir.

Kullanılan teknolojiden yayınlanan dokümanın formatına kadar kurum sayısınca çeşitlilik gösterilebilse de, bir API ve ISD'nin aşağıdaki özellikleri göstermesi beklenir.
  • Kolay anlaşılabilir ve implemente edilebilir olması
  • Farklı platformlardan bağlanılabilmeye izin veriyor olması
  • Karşılaşılacak olası hataları, sebepleri ile birlikte anlaşılabilir şekilde kullanıcılara belirtiyor olması
  • Güvenlik açığının bulunmuyor olması (İhtiyaca göre Authentication, Authorization ve Validation)
  • Gelecekteki muhtemelen değişiklikler sebebiyle, geçmişte entegrasyon kurmuş kullanıcılara sıkıntı/ekstra iş yükü çıkartmıyor olması

Bu maddeler bir miktar daha uzatılabilir. Ancak endüstride bu ihtiyaçları karşılamak için çeşitli standartlar zaten oluşmuş durumda. Örneğin en çok kullanılan teknolojilerin başında XML Web Service uygulamaları geliyor. Bu servisler ile farklı platformlarda bulunan sistemleri karşılıklı olarak konuşturmak mümkün. Ancak bu servislerin aşağıdaki gibi bazı sıkıntıları var.
  • SOAP üzerine kurulu tasarlandığı için, eğer ki talepte bulunacak sistem, kütüphane seviyesinde Web Service implementasyonu yapmayı hazır şekilde desteklemiyorsa (Örneğin JavaScript ile hazırlanmış uygulamalar veya bazı POS cihazları), talep için gönderilmesi gereken mesajı hazırlamak bir miktar sıkıntılı olabiliyor.
  • Veri taşıma sırasında XML mesaj formatı kullanıldığı için, genelde talep edilen veriden 3-4 kat daha büyük bir veri akışının gerçekleştirilmesi gerekiyor ki orta ve yüksek boyutlu verilerde bu genelde sıkıntılara sebep oluyor.
  • Dönülen veriyi özellikle JavaScript gibi platformlarla Parse etmek genelde oldukça yavaş ve sistem bazında yorucu bir işlem oluyor.
  • Her ne kadar sistemin özü platform bağımsız tasarlansa da, örneğin Microsoft .Net ve Java ile yazılan Web Service'lerinde çeşitli farklılıklar olabiliyor ve bu iki sistemi birbirine entegre etmek için zaman zaman yazılımcıların bazı taklalar atmaları gerekebiliyor.

Tüm bu ve bunun gibi sebeplerden ötürü son zamanlarda XML Web Service'lerine alternatif bir API tasarımı yaygınlaşmaya başladı. Standardın genel kabul görmüş adı REST (Representational State Transfer).

İşin aslı REST yeni bir şey değil ancak bu standardı kullanarak "Restful API" denilen uç birimler geliştirilmeye başlandı. İşin özünde, basit HTTP talepleri ile kullanıcıların servislere isteklerini bildirmeleri yatıyor. Biraz örnekleme üzerinden gidelim.

Diyelim ki, fazlaca program ve modüllerden oluşan entegre sistemler bütünümüz var ve bunların tamamı için de dışarıya Restful API açtık. Güzel de bir ISD hazırladık. Kullanıcılara da dedik ki, http://www.bizimapi.com adresinden bu API'ye ulaşabilirsiniz.

Kullanıcının niyeti, muhasebe programındaki cari hesaplar modülünden ID'si 15 olan cari hesap bilgilerini çekmek. Buna göre ISD'de HTTP üzerinden göndermesi gereken URL aşağıdaki gibi belirtilmiş.

http://www.bizimapi.com/accounting/currentaccount/15

Burada base domainden sonra gelen ilk kısım program, ikinci kısım ise modül ismini gösteriyor. Sondaki değer ise, ulaşılmak istenen ID'yi belirtiyor (Bu bir standart değil, biz böyle tasarladık).

Yine benzer şekilde başka bir kullanıcı, muhasebe programındaki banka modülünden ID'si 7 olan banka bilgilerini çekmek istiyor. Buna göre ISD'de HTTP üzerinden göndermesi gereken URL aşağıdaki gibi belirtilmiş.

http://www.bizimapi.com/accounting/bank/7

Basit, değil mi? Hatta işin aslı bu bilgilere ulaşmak için bir programa bile gerek yok. Basit bir internet tarayıcısı üzerinden de bu adrese girilerek verilere ulaşılabilir.

Yukarıda gönderilen taleplerin tamamı HTTP GET ile yapıldı. Ancak bazı durumlarda, almak istediğimiz verilere basit bir ID yerine daha fazla sayıda parametre göndererek (örneğin filtreleme işlemi) ulaşmamız gerekebilir. Böyle durumlarda HTTP POST kullanımı daha uygundur. Örneğin adı "Ali" olup, baba adı "Veli" olan cari hesapları getirmek istediğimizde;

http://www.bizimapi.com/accounting/currentaccount/

adresine HTTP POST ile "Name" ve "FatherName" alanlarını POST edebiliriz. Buradaki POST işlemi, HTML'deki formlarda uygulanan POST işleminin aynısıdır.

Buraya kadar sadece verilere ulaşmaya baktık. Yeni bir kayıt oluşturmak için de yine POST işlemi kullanılabilir. Örneğin,

http://www.bizimapi.com/accounting/currentaccount/

adresi çağrılır ve bir cari hesap oluşturmak için gerekli olan parametreler HTTP POST ile gönderilebilir. Biz de sunucu tarafında gelen parametreleri sayarak, mesajın aslında filtrelenmiş veri çekmek için mi, yoksa yeni bir kayıt oluşturmak için mi gönderildiğini anlayabiliriz. Ya da daha güzeli, işlemin ne olduğunu anlatan bir parametre daha alabiliriz (Örneğin "Get" ve "New" gibi).

Bundan sonrası biraz ihtiyaçlara ve tasarımı yapana kalıyor. Sadece yukarıda anlattıklarım ile Update ve Delete işlemini yapmak da mümkün (Örneğin işlemin ne olduğunu anlatan parametreye "Update" ve "Delete" değerlerini geçerek). Yeter ki kolay anlaşılır ve uygulanabilir olsun. Ancak standartlara uymak açısından genellikle, güncelleme işlemleri için HTTP PUT, silme işlemleri için de HTTP DELETE çağrısı yapılır. Bu iki çağrı, GET ve POST'a göre çok daha az kullanılır ve bilinir. Ancak örneğin JQUERY ile JavaScript'ten bu çağrıları da kolayca gönderebiliriz.

Restful API tasarımında, çağrıda bulunan kullanıcıya dönülecek mesaj formatında herhangi bir standart yoktur. Örneğin sadece ilgili cari hesabın adını döneceksek, "Ali" gibi düz bir metin dönebiliriz. Ancak adı, soyadı ve yaşı gibi birden fazla bilgi döneceksek, kullanıcının bunu ayırt edebileceği bir yapı sunmak zorundayız. Bu durumda, örneğin ayrı verileri ";" ile ayırarak gönderebiliriz. Bu durumda döneceğimiz veri "Ali;Veli;45" gibi olacaktır. Gel gelelim, döneceğimiz veri bir değil de, birden fazla kişiyse ya da daha kompleks bir bilgi içeriyorsa, bu durumda daha iyi formatlanmış bir yapı kullanmak zorundayız.

XML burada bir alternatif olabilir. Dönülecek verinin yapısını ISD'de güzelce tariflersek, kullanıcılar XML'i Parse ederek okuyabilirler. Ancak makalenin başında XML'in sıkıntılarından bahsetmiştim. Bu durumda, son zamanlarda ismi fazlaca duyulan başka bir standart alternatife göz atmakta fayda var; JSON.

İşin ilginci, JSON'ın çıkış tarihi (2001), XML'in çıkış tarihinden (1998) çok da sonra değildi. Ama sanırım artan mesaj yükü ve veri boyutu ile insanların XML'den sıkılması biraz vakit aldı. Ayrıca JavaScript ile geliştirilen uygulamaların sayısının artması da JSON'a olan ilginin artmasına sebep oldu.

JSON'ın XML'e olan üstünlüklerini aşağıdaki şekilde sıralayabiliriz.
  • Toplam veri boyutu daha küçüktür
  • Özel karakterlerde CDATA benzeri bir yapıya ihtiyaç duymaz
  • Indentli durumda insan gözüyle anlaşılmaya daha uygundur
  • Parsing işlemi XML'e göre daha hızlıdır
  • JavaScript tarafında nesnel olarak direk kullanıma uygundur

Bu sebeple, istemcilere dönülen cevabın JSON formatında olması, son zamanlarda sık karşılaşılan bir durum oldu. Ancak işin bir de talep tarafı var. Az önce anlattığım gibi Restful bir API'de parametreleri tek tek alabileceğimiz gibi, JSON formatında tek bir parametre de alabiliriz. Böyle bir durumda, liste benzeri kompleks parametrelerin servise geçilmesi de daha kolay olacaktır. İstemcinin tek yapması gereken, ihtiyaç duyduğu fonksiyonun URL'ine, geçmesi gereken parametreleri uygun JSON formatında POST etmektir.

Not  : Burada girdi parametrelerini tek bir parametre ile JSON formatında HTTP POST ile almak, tercih edilebilecek yöntemlerden sadece biridir. Örneğin merkez bankasının, belirtilen para biriminin güncel kur bilgilerini dönecek bir servis yazacağını düşünürsek, bu durumda http://www.tcmb.gov.tr/currency/usd tarzında HTTP GET ile talepleri kabul eden bir servis yazması, hem istekte bulunan hem de karşılayan taraf için çok daha kolay olacaktır.

Demem o ki, ihtiyacı doğru anlayıp, soruna doğru çözümle yaklaşmak gerekir. Basit işlere karmaşık çözümlerle yaklaşmanın, kurumsal mimari gerektiren işlere Allah ne verdiyse girişmek kadar yanlış olduğunu unutmayalım.

Güzel, buraya kadar işin teorik kısmına değindik ama tek satır kod görmedik. Bu benim en sevmediğim anlatım türüdür. İşkenceye burada son verip, nefes aldırmaya başlıyorum inşallah.

Bu makaledeki örneğimizde, istemci olarak bir HTML sayfa kullanacağız. Sayfamızda dört adet butonumuz olacak. Bunlara tıklanınca, sunucu tarafında yazdığımız servise bağlanıp sırasıyla;
  • Var olan cari hesap listesini çekerek ekranda gösterecek (Bu arada filtreleme için parametreler geçecek)
  • Benzer şekilde var olan banka listesini çekerek ekranda gösterecek (Yine filtreleme parametreleri ile birlikte)
  • Yeni bir cari hesap ekleyecek
  • Yeni bir banka ekleyecek

Elbette henüz Node.JS ile veritabanı veya herhangi bir Persister kullanımını görmediğimiz için, sunucu tarafında sadece ilgili çağrıyı yakalayarak her seferinde aynı hardcoded cevabı döneceğiz. Burada önemli olan Restful JSON API'yi hazırlamak.

Normalde BDD yaklaşımı ile kod yazmayı savunan birisiyim ama makale Node üzerine olduğundan, öncelikle sunucu tarafını geliştirerek işe başlayacağız.

İlk olarak işlemler için gerekli REST adreslerini belirleyelim. Uygulamayı 2222 portundan yayınlamayı planlıyorum. Bu durumda her bir modül için adresimiz aşağıdaki gibi olsun.

Cari hesap modülü : http://localhost:2222/CurrentAccount
Banka modülü : http://localhost:2222/Bank

İstemciler hem sorgu, hem de yeni kayıt için bu adreslere gelecekler ancak gönderdikleri mesajda bulunan bir parametre sayesinde, mesajın tipini anlayacağız.

Şimdi de istekte gelecek ve cevap olarak dönülecek JSON mesaj formatlarını belirleyelim. Dört mesaj için kullanmayı düşündüğüm mesaj formatı örneklerini aşağıda belirtiyorum.

Cari Hesap Sorgusu

İstek Mesajı


{
    "MessageType": "Get", 
    "Filter": {
        "FatherName": "Ahmet", 
        "Gender": "Male"
    }
}

Cevap Mesajı

{
    "ResponseCode": 0, 
    "ResponseMessage": "Islem Basarili", 
    "CurrentAccountList": [
        {
            "ID": 10, 
            "Name": "Ali"
        }, 
        {
            "ID": 11, 
            "Name": "Veli"
        }
    ]
}


Yeni Cari Hesap Kaydı

İstek Mesajı

{
    "MessageType": "New", 
    "CurrentAccount": {
        "ID": 5, 
        "Name": "Ayşe", 
        "Gender": "Female", 
        "FatherName": "Mehmet"
    }
}

Cevap Mesajı

{
    "ResponseCode": 0, 
    "ResponseMessage": "Islem Basarili"
}


Banka Sorgusu

İstek Mesajı

{
    "MessageType": "Get", 
    "Filter": {
        "City": "Istanbul", 
        "Currency": "TL"
    }
}

Cevap Mesajı

{
    "ResponseCode": 0, 
    "ResponseMessage": "Islem Basarili", 
    "BankList": [
        {
            "ID": 3, 
            "Description": "C Bank"
        }, 
        {
            "ID": 4, 
            "Description": "D Bank"
        }
    ]
}


Yeni Banka Kaydı

İstek Mesajı

{
    "MessageType": "New", 
    "Bank": {
        "ID": 15, 
        "Description": "Z Bank", 
        "City": "Istanbul", 
        "Currency": "USD", 
        "AccountOwners": [
            {
                "Name": "Ali"
            }, 
            {
                "Name": "Veli"
            }
        ]
    }
}

Cevap Mesajı

{
    "ResponseCode": 0, 
    "ResponseMessage": "Islem Basarili"
}


Mesaj yapıları hakkında birkaç bilgi vereyim. İstek mesajlarındaki "MessageType" alanının değerinden, talebin sorgu amaçlı mı, yeni kayıt ekleme amaçlı mı olduğunu anlıyoruz. Yeni banka ekleme mesajını, örnek olması amacıyla bir kademe daha kompleks yaptım.

Dönüş cevaplarında da, "ResponseCode" alanı 0 olduğu takdirde işlem başarılı demektir. 0'dan başka bir değer gelmesi durumunda, hata yaşanmış demektir ve hatanın ne olduğu da "ResponseMessage" alanında gözükür (Bu durumda hangi ResponseCode'un ne anlama geldiğini de ISD'de yazmamız gerekecektir. ResponseMessage alanı sadece açıklama amaçlıdır). Yeni kayıt mesajlarında basit bir cevap dönerken, sorgu metotlarında bunun yanında liste şeklinde bir bilgi de dönmektedir.

Not  : Şimdi diyebilirsin ki, madem "MessageType" diye bir parametremiz var, neden iki modül için iki ayrı URL kullandık. Tüm talepler tek bir URL'e gelebilir, biz de MessageType'a dört farklı değer vererek işimizi görebilirdik.

Haklısın, ama bu durumda Interface'imiz Restful JSON API değil, JSON API olurdu ve makalede anlatmak istediklerimin yarısını anlatamazdım. Ayrıca modül bazında farklı URL'lere gitmenin farklı avantajları da var. Örneğin büyük bir sistem tasarladığımızı düşünürsek, her URL için ayrı sunucu kümeleri atayabiliriz ve bir modüldeki yoğunlaşmanın diğer modüldeki akışları etkilemesinin önüne geçmiş oluruz vs.

Aradaki mesajlaşma standardını belirledik. Geriye kodları yazmak kaldı. Ama artık işi az çok biliyoruz, Express'in bize hazır verdiği Template projeye ihtiyacımız yok, sıfırdan biz de yazabiliriz.

İlk olarak masaüstüne "RestfulJSONAPI" isimli bir klasör oluşturalım. Ardından içerisine aşağıdaki kodlarla "package.json" adında bir dosya oluşturalım.


Bu dosya, ihtiyacımız olan Express modülünü indirmeye yarayacak. View'a ihtiyacımız olmadığı için buraya herhangi bir Template Engine eklemedim.

Ardından aşağıdaki kodlarla "app.js" isminde bir dosya oluşturalım.


Devam etmeden önce bu kodu biraz inceleyelim. Aslında kodun büyük çoğunluğu, bir önceki makaledeki "app.js" dosyasına benziyor. Ancak fark olarak, aşağıdaki kodlarla, "routes" klasörü içerisindeki dosyalarımızı, nesnelere bağlıyoruz.

var currentAccount = require('./routes/currentaccount');
var bank = require('./routes/bank');

Ardından aşağıdaki kodlarla, bu dosyaları ilgili adreslere bağlıyoruz. Böylece bu adreslere gelen talepler, bu dosyalardaki "process" fonksiyonuna iletilecek.

app.get('/currentaccount', currentAccount.process);
app.get('/bank', bank.process);

Geriye kaldı "routes" klasörünü ve içerisindeki dosyaları oluşturmaya. İlk olarak "routes" isminde bir klasör oluşturalım. Ardından bunun içerisine girerek, aşağıdaki kodlarla "index.js" isminde bir dosya oluşturalım.


Bu dosyayı, ISD'yi okumadan direk root klasörüne dalan aceleci yazılım geliştirici kardeşlerimiz için hazırladık.

Sırada cari hesaplar modülü var. Aşağıdaki kodlarla "currentaccount.js" isminde bir dosya oluşturalım.


Burada gelen mesajdaki "MessageType" alanının değerine göre iki cevaptan birini dönüyoruz. Burada sabit bir cevap dönüyoruz ancak önemli olan gelen verileri alabilmemiz ve geriye cevap olarak uygun JSON mesajını dönebilmemiz.

Not  : Biliyorum, if-else yapısı burada çok yakışıksız duruyor. Ancak Design Pattern'lere girerek makalenin konusunu dağıtmak istemedim. İnşallah bunun daha düzgün halini, "Stok Programı Yapalım" isimli projede göreceğiz.

Şimdi aynı kodun banka modülü için olan hali için aşağıdaki kodlarla "bank.js" isminde bir dosya oluşturalım.


Muhtemelen daha fazla açıklamaya gerek yoktur, bir önceki dosya ile neredeyse aynı sayılır.

Sunucu tarafımızı bitirdik. Sıra Client tarafına geldi. Bunun için öncelikle "RestfulJSONAPI" klasörü içerisinde "public" isimli bir klasör oluşturalım ve bu klasör içerisinde, aşağıdaki kodlarla "test.html" isimli bir dosya oluşturalım.


Not  : İstemci olarak kullanacağımız HTML sayfasında AJAX talebinde bulunacağız ve bunun için çoğu tarayıcı güvenlik sebebiyle, sayfanın bir Web Server üzerinde host edilmesini istiyor. Bu yüzden "test.html" dosyasını masaüstü yerine "public" klasörü içerisine koyuyoruz ve Express'i Web Server olarak kullanıyoruz.

Client tarafındaki asıl kodlarımızı içerecek dosyayı da, aşağıdaki kodlarla "program.js" ismi ile oluşturalım.


Kodu incelersek, dört buton için birer adet Event Handler yazdığımızı ve bunlarda da, JQuery ile HTTP POST Ajax Call yaptığımızı, gelen cevabı da ekranda mesaj kutusu ile gösterdiğimizi görüyoruz.

Hepsi bu kadar. Eğlence zamanı. İlk olarak, terminal (Linux veya Mac OS) veya command prompt (Windows) ile "RestfulJSONAPI" klasörüne kadar gelip, sunucu tarafındaki kodumuzda ihtiyaç duyulan modülleri indirmek için,

npm install

komutunu, ardından projenin sunucu tarafını çalıştırmak için aşağıdaki komutu çalıştıralım.

node app.js

Bunun üzerine karşına aşağıdaki gibi bir ekranın gelmesi gerekiyor.



Sunucu tarafımız ayağa kalktı. Şimdi de masaüstündeki test.html dosyamızı açarak sırasıyla butonları deneyelim. Aşağıdaki gibi bir görüntüyle karşılaşman gerekiyor.



Böylece JSON formatında istek alan ve cevap veren, Resful bir API hazırlamış olduk. Yine hatırlatmak istiyorum. Burada yaptığımız örnek, her ihtiyaca uygun bir çözüm olmayabilir. HTTP GET ile Restful olarak çağırılacak ve JSON parametre girişine ihtiyaç duymayacak daha basit servisler de yazılabilir. Önemli olan ihtiyacı doğru anlayarak, soruna doğru çözümle yaklaşmaktır.

Makaleyi tamamlarken öğrendiklerimizi özetleyelim.
  • API, ISD, REST ve JSON kavramlarını öğrendik.
  • İyi bir API'nin ne gibi özellikler taşıması gerektiğinin üzerinden geçtik.
  • JSON'ın XML'e karşı olan üstünlüklerini saydık.
  • Express ile kolaylıkla Restful JSON API'ler hazırlayabileceğimizi gördük.
  • JavaScript ile istemci tarafında Restful JSON API'ye nasıl istekte bulunacağımızı ve gelen cevabı nasıl kullanabileceğimizi gördük.
  • Tek bir çözüm değil, en uygun çözüme odaklanmamız gerektiğini konuştuk.