Kısım II · İnşaat · Bölüm 34

Arka Uç Nedir: Sunucu ve API

Veri tek merkezde durunca (What a backend is: servers and APIs)

Neden burada · Neye dayanıyor

Neden burada: Önceki bölümde arayüzün sunucudan veri çektiğini varsaydık: fetch bir yere gidiyor, oradan JSON dönüyor. O "bir yer"in ne olduğunu hiç açmadık. Leaderboard'u herkes aynı görecekse, puanlar hiçbirimizin telefonunda durmamalı; tek bir ortak merkezde durmalı. Bu merkezin adı sunucu, onu kuran yazılımın adı arka uç. Bu bölüm sunucunun gerçekte ne olduğunu, arayüzle arasındaki iş bölümünü (ve bu bölümün en önemli fikrini: neden puanı arayüze hesaplatmayız), iki programın birbirine nasıl söz verdiğini (API) ve sunucunun neden "hafızasız" tasarlandığını anlatır.

Neye dayanıyor: Bölüm 19.6: istemci–sunucu modelinin ilk tanımı; burada onu bir program olarak somutlaştırıyoruz. Bölüm 14.1: süreç kavramı; sunucu, sürekli çalışan bir süreçtir. Bölüm 22.4: her uygulama türünde istemci ve sunucu parçaları. Bölüm 33.1: sunucu durumu, yani sunucunun bize verdiği "ödünç kopya" fikri; şimdi o kopyanın asıl sahibine bakıyoruz.

  1. Kat 10Bizim uygulamamız: ön yüz ve arka uçUygulamanın iki yarısı. Ön yüz kullanıcının cihazında (Bölüm 23–33), arka uç ortak bir sunucuda. Bu bölüm ikinci yarıyı tanıtır.
  2. Kat 9Web ve tarayıcıÖn yüzün çalıştığı yer; isteği buradan gönderir.
  3. Kat 8Ağ ve internetİstek ile yanıtın taşındığı yol (IP, TCP, HTTP). Bölüm 19–20.
  4. Kat 7İşletim sistemiSunucu programı bir süreç olarak çalıştırır, ağ bağlantılarını ona iletir. Bölüm 14.
  5. Kat 5Programlama diliArka ucu yazdığımız dil: Go. Bölüm 36–37.
  6. Kat 0–4, 6Donanım, makine dili, gömülü yazılımSunucu makinesi de bir bilgisayar; aynı katlar onda da var.

Bu bölümün sonunda şunu anlatabileceksin: "Sunucu bir bilgisayar değil, bir program; bir portu dinleyip gelen istekleri bekleyen, hiç kapanmayan bir süreç." "Arka uç, kullanıcının dokunamayacağı ve değiştiremeyeceği kurallar katmanı; ön yüz ise kullanıcıya hizmet eden ama güvenilmeyen bir görünüm." "API, bu iki programın birbirine verdiği sözdür; bozulursa ikisi de bozulur." Ve: "sunucuyu kapatıp açınca bir şey kaybolmuyorsa, çünkü hafıza sunucuda değil, başka bir yerdedir." Bu bölümde kod azdır; kavram çoktur. Ama kavramları Bölüm 37'de yazacağımız gerçek bir Go sunucusunun gerçek çıktılarıyla sınayacağız.

34.1 Sunucu: sürekli çalışan ve istek bekleyen bir süreç

"Sunucu" kelimesi iki farklı şeye söylenir ve bu karışıklık, yeni başlayanın en sık tökezlediği yerdir. Birincisi makine: bir veri merkezindeki, kasalı, soğutmalı, diğerlerinden farkı olmayan bir bilgisayar. İkincisi program: o makinenin üzerinde çalışan, "istek bekleyen" yazılım. Bu bölümde ve kitabın geri kalanında, aksi söylenmedikçe ikinci anlamı kastediyoruz.

Bölüm 19.6'dan hatırla: istemci ve sunucu

Bölüm 19.6 istemci–sunucu modelini tanıtmıştı: iki taraftan biri (istemci) bir şey ister, diğeri (sunucu) cevap verir. MDN'in HTTP'ye genel bakış sayfası bunu şöyle koyar: HTTP bir istemci–sunucu protokolüdür, yani istekler alıcı tarafından, genellikle web tarayıcısı tarafından başlatılır.1 Belge ayrıca şunu da ekler: istemci, yani tarayıcı, isteği her zaman kendisi başlatır, sunucu asla başlatmaz (yıllar içinde sunucudan başlatılmış gibi görünen mekanizmalar eklenmiş olsa da).1 Bu basit kural önemli bir sonuç doğurur: sunucu, birisi sormadıkça konuşamaz. Dolayısıyla sunucunun yaşamı, tek bir döngüdür: bekle, isteği al, işle, yanıt ver, yeniden bekle.

Sunucu bir süreçtir

Bölüm 14.1'de bir programı (diskte duran dosya) bir süreçten (belleğe yüklenip işletim sistemi tarafından çalıştırılan hâli) ayırmıştık. Sunucu, normal bir programdan yalnızca bir yönden farklıdır: kendi kendine bitmez. Hesap makinesi bir işlem yapıp bekler, sen kapatınca biter. Sunucu bir işlem yapıp bir sonrakini beklemeye geçer ve kapanma emri gelene kadar bunu sürdürür. Sözde kodla (Bölüm 1.3'teki gibi):

bir portu dinlemeye başla                  // işletim sistemine: "bu porta gelen bağlantıları bana ver"
sonsuza kadar tekrarla:
    bir istek gelmesini bekle
    isteği oku                             // yöntem, adres, başlıklar, gövde
    cevabı hesapla                         // gerekirse veritabanına sor
    yanıtı geri yaz

"Portu dinlemek" ifadesi Bölüm 19.5'ten tanıdık: bir makinenin IP adresi tek bir "bina"yı gösterir, port ise o binadaki "kapı numarasıdır". Sunucu programı, işletim sistemine "8080 numaralı kapıya gelenleri bana yönlendir" der; işletim sistemi (Bölüm 13, Kat 7) ağ kartından gelen paketleri o kapıya karşılık gelen sürece teslim eder. Web'in varsayılan kapıları 80 (şifresiz HTTP) ve 443'tür (HTTPS); geliştirme sırasında genellikle 3000, 8000, 8080 gibi sayılar kullanılır, çünkü düşük numaralı kapılar çoğu işletim sisteminde özel izin ister.

Kendi gözünle gör: Go'da bir sunucuyu çalıştırmak

Bölüm 37'de adım adım yazacağımız Go programını bu bölümü yazarken gerçekten derleyip çalıştırdım. Başlatınca ekrana tek bir satır yazıyor ve komut satırı geri gelmiyor:

$ go build -o api . && ./api
2026/11/13 16:00:22 dinliyorum :8080
        (komut satırı geri gelmez: süreç çalışıyor, isteği bekliyor)

Başka bir terminal penceresinden (Bölüm 18'deki terminal) curl adlı bir araçla, yani tarayıcı yerine komut satırı bir istemciyle sunucuya soruyorum:

$ curl localhost:8080/leaderboard
[{"user_id":2,"name":"Emre","points":70},{"user_id":3,"name":"Ayşe","points":55},{"user_id":1,"name":"Kerem","points":40}]

Okuması: localhost, "bu bilgisayarın kendisi" demektir; istek ağa çıkmaz, kendi makinene döner. :8080 kapı numarasıdır. /leaderboard istenen yoldur (Bölüm 20.2'deki URL anatomisinden). Dönen şey bir web sayfası değil, düz veridir: bir sonraki bölümde göreceğin JSON. Aynı anda ilk terminal şunu yazdı (sunucunun kendi kaydı, "log"):

2026/11/13 16:00:24 GET /leaderboard -> 200 (10ms)

Yani "sunucu" dediğimiz şey burada: sırada bekleyen, istek gelince bir satır kayıt tutup cevap veren bir süreç. Tarayıcıdaki Expo uygulaman da aynı şeyi yapacak: curl'un yerine geçecek, aynı isteği atacak, aynı JSON'u alacak. Sunucu için fark yoktur; isteği kim gönderirse göndersin, ona yalnızca gelen istek görünür. Bu özelliğe ileride (34.2'de) çok ihtiyacımız olacak.

Veritabanı da bir sunucudur

Bölüm 38'de göreceğimiz PostgreSQL, aynı modelde çalışır. Postgres'in kendi belgesi mimarisini şöyle anlatır: bir PostgreSQL oturumu, iş birliği yapan iki süreçten oluşur; sunucu süreci veritabanı dosyalarını yönetir, istemci uygulamalardan gelen bağlantıları kabul eder ve onlar adına işlemleri yapar; istemci ise bir veritabanı işlemi yapmak isteyen kullanıcı uygulamasıdır. Belge istemci olarak "bir web sunucusunu" bile sayar: yani bir sunucu, başka bir sunucunun istemcisi olabilir.2 Bizim yığında Go sunucusu, Expo uygulamasına karşı sunucu, Postgres'e karşı istemcidir. Belge ayrıca iki tarafın farklı makinelerde olabileceğini ve bu durumda TCP/IP ağı üzerinden konuştuklarını da söyler; yani Kat 8, burada da çalışır.2

"Tek makine" bir yanılsama olabilir

MDN'in belirttiği bir nokta daha: sunucu dışarıdan tek bir makine gibi görünür, ama aslında yükü paylaşan bir sunucu topluluğu ya da onların üretimine katkıda bulunan başka yazılımlar (önbellekler, veritabanı sunucuları) olabilir; ayrıca aynı makinede birden çok sunucu yazılımı çalışabilir.1 Yani "sunucu" bir adres, yani bir arayüzdür (Bölüm 1.4: soyutlama); arkasında kaç makine olduğu istemciyi ilgilendirmez. Bu bölümün sonunda (34.4) bunun neden önemli olduğunu göreceksin.

34.2 Ön yüz ve arka uç: iş bölümü ve güven sınırı

Bir uygulamanın iki yarısı var ve her birinin kendi işi var.

Ön yüz (frontend), kullanıcının cihazında çalışan ve kullanıcıyla doğrudan konuşan yarıdır: ekranlar, düğmeler, animasyonlar, o an seçili şık. Bölüm 23–33'ün hepsi ön yüzdü. Arka uç (backend) ise ortak bir sunucuda çalışan, kullanıcının hiç görmediği yarıdır: veriyi saklar, kuralları uygular, birden çok kullanıcının ortak durumunu tutar. Bu bölümden sonrası arka uçtur.

Ön yüzArka uç
Nerede çalışır?Kullanıcının telefonu ya da tarayıcısıBizim kontrol ettiğimiz sunucu
Kim kontrol eder?Kullanıcı (kodu inceleyebilir, değiştirebilir)Biz
Ne tutar?Geçici, kişisel durum (istemci durumu)Ortak, kalıcı durum (veritabanı)
Asıl işiGöstermek, girdiyi toplamak, hızlı hissettirmekKuralları uygulamak, doğrulamak, saklamak
DiliTypeScript (Expo)Go (Bölüm 36–37)

Neden puan hesabı sunucuda yapılır?

İş bölümünün en kritik sorusuna geldik, çünkü cevabı bütün güvenlik ve oyunlaştırma tasarımına yön verir. Basit bir tasarım düşünelim: kullanıcı soruyu cevaplar, ön yüz doğru cevabı bilir (soru listesiyle birlikte indirmiştir), doğruysa puanı hesaplar ve sunucuya şunu yollar: { "points": 10 }. "Puan şu kadar arttı." Sunucu da toplar. Bu tasarım çalışır ve kodu çok kolay. Neden yanlış?

Çünkü ön yüz kullanıcının elinde çalışır. Kullanıcı tarayıcının geliştirici araçlarını açıp kodu okuyabilir, değiştirebilir; telefon uygulamasının kopyasını inceleyebilir; hiç uygulamayı kullanmadan, curl ile sunucuya doğrudan istek atabilir. Kullanıcının yerine gelen isteğin "gerçekten bizim uygulamamızdan geldiği" diye bir güvencesi yoktur. Bunu bu bölümü yazarken sınadım: yazdığım Go sunucusuna, uygulamayı hiç kullanmadan, doğrudan yalan bir istek attım:

$ curl -X POST localhost:8080/answers -H 'Authorization: ...' -d '{"points":999}'
{"error":"geçersiz JSON: json: unknown field \"points\""}      [HTTP 400]

Sunucu, istemcinin "ben şu kadar puan kazandım" demesine izin vermiyor: böyle bir alanı hiç tanımıyor ve isteği reddediyor (bunu Bölüm 37.2'de nasıl yaptığımızı göreceksin). Sunucu bir cevap seçeneği kabul ediyor, puanı kendisi belirliyor:

$ curl -X POST localhost:8080/answers -H 'Authorization: ...' -d '{"question_id":1,"choice":2}'
{"correct":true,"points_awarded":10,"total_points":50}          [HTTP 201]

$ curl -X POST localhost:8080/answers -H 'Authorization: ...' -d '{"question_id":1,"choice":1}'
{"correct":false,"points_awarded":0,"total_points":50}          [HTTP 201]

Burada kullanıcı yalnızca ne seçtiğini söylüyor ("1. soruda 2. şıkkı seçtim"); doğru cevabı sunucu biliyor (istemciye hiç göndermiyor), doğruluğu kendisi karşılaştırıyor ve puanı kendisi yazıyor. Kullanıcı sahte bir choice gönderebilir, ama bu onu en fazla yanlış cevaba götürür; sahte puana götürmez.

Güven sınırı

Bu ilke güvenlik literatüründe güven sınırı (trust boundary) adıyla anılır. Kodunun kontrol ettiğin kısmı ile kontrol etmediğin kısmı arasındaki çizgidir. Bizim durumumuzda çizgi, ağın tam ortasından geçer: sunucu çizginin bir yanında, kullanıcıdan gelen her şey (ön yüzün ürettiği her istek, her başlık, her JSON alanı) çizginin öbür yanında. Kural şu: sınırı geçen her şey güvenilmez kabul edilir ve doğrulanır. OWASP'ın girdi doğrulama rehberi bunu pratik bir cümleyle söyler: tarayıcı aynı alanları kontrol ediyor olsa bile sunucuda doğrula; istemci tarafı doğrulama atlatılabilir, onu yalnızca anlık geri bildirim için kullan.3 Aynı rehber, sınırı geçen verinin yalnızca kullanıcıdan değil, dahili sistemlerden, ortak servislerden, kuyruklardan ya da saklanmış kayıtlardan geliyorsa da doğrulanması gerektiğini söyler: "dahili bir taşıma", değerin alan bileşenin kurallarına uyduğunu kanıtlamaz.3

Hukuk eğitiminden tanıdığın bir şeye benziyor: bir belgeyi imzalamak ile imzanın sahibine ait olduğunun doğrulanması farklı şeylerdir; imzayı atan kendi imzasını "geçerli" ilan edemez, onu onaylayan ayrı bir merci gerekir. Ön yüz, kullanıcı adına "bu cevap doğru, bu kadar puan kazandım" diye beyanda bulunur; arka uç, o beyanın doğrusunu hesaplayıp onaylayan mercidir.

Sunucuda olması gerekenler

Güven sınırı bize sunucuya ne konması gerektiğini söyler:

Peki ön yüzde hiçbir kontrol olmaz mı?

Olur, ama farklı bir amaçla. Cevap boşsa "Gönder"i devre dışı bırakmak, şıkkı seçince anında yeşil/kırmızı efekt göstermek (iyimser güncelleme, Bölüm 33.2), kullanıcıyı ağa gitmeden uyarmak: bunlar deneyimi hızlandırır ama güvenliği sağlamaz. OWASP'ın ifadesiyle, istemci tarafı denetim "anlık geri bildirim içindir".3 İki katman da gerekli, ama rolleri farklı: ön yüz kibar, arka uç şüpheci.

Uygulamamızda

Kerem'in prototipinde (vanilla HTML/CSS/JS) puan hesabının nasıl yapıldığını görmedik; ama prototipler genellikle her şeyi tarayıcıda yapar: doğru cevap JavaScript dosyasındaki bir dizide durur, puan bir değişkende artar, seri localStorage'da tutulur (Bölüm 26.4). Prototip için bu mantıklı: sunucu yok, tek kullanıcı var. Ama Duolingo benzeri bir uygulamanın asıl değeri ortak şeylerde: leaderboard'daki sıran, arkadaşlarının serisi. Bu iki şey, ön yüzde hesaplanan puanlarla güvenilir olmaz: bir kullanıcı geliştirici araçlarında bir değişkeni değiştirip leaderboard'un başına geçebilir. Bu yüzden Expo sürümüne geçerken mantığın ağırlık merkezi ön yüzden arka uca taşınır: ön yüz "şu şıkkı seçti" der, arka uç "doğru, 10 puan" der. Prototipteki puan hesabı kodu, Go'ya taşınırken yeniden yazılacak (Bölüm 37) ve ön yüzdeki kopyası yalnızca anlık geri bildirim için kalacak.

34.3 API: iki program arasındaki sözleşme

Ön yüz ile arka uç farklı makinelerde, farklı dillerde (TypeScript ve Go), farklı kişiler tarafından, hatta farklı zamanlarda yazılıyor. Birbirlerinin kodunu görmeden nasıl anlaşıyorlar? Bir sözleşmeyle.

API (Application Programming Interface, uygulama programlama arayüzü), bir programın başka bir programa hangi isteklerde bulunabileceğini ve karşılığında ne alacağını tanımlayan, önceden üzerinde anlaşılmış kurallar bütünüdür. İçindeki "arayüz" kelimesi Bölüm 1.4'ten tanıdık: bir parçanın dışa açılan yüzü, içinin nasıl işlediğini gizleyerek. Bir API, bir programın arayüzüdür; kullanıcı arayüzü ise bir insanın arayüzüdür (kitapta bu ikisini hep ayırıyoruz).

API'nin üç katmanı: aynı fikir her yerde

API fikri yalnızca web'e özgü değil, kitap boyunca farklı kılıklarda gördün:

Üçünde de mantık aynı: çağıran, çağrılanın içini bilmez; yalnızca anlaşmayı bilir. Bu, kitabın baştan beri anlattığı soyutlama fikridir.

Sözleşmede ne yazar?

Bir web API'sinin sözleşmesi, bir hukuk sözleşmesinin maddeleri gibi, her iki tarafın yükümlülüklerini listeler:

  1. Hangi adresler var? (Hangi kapılar açık; bunlara uç nokta / endpoint denir, Bölüm 35.4.)
  2. Her adrese hangi türde istek atılabilir? (Okuma mı, yazma mı; Bölüm 35.1.)
  3. İstek neyi içermeli? (Hangi alanlar, hangi tipte, hangi sınırlar içinde.)
  4. Yanıt neyi içerir? (Başarılıysa şu şekil; hata varsa şu kod ve şu mesaj.)
  5. Kimlik gerekiyor mu? (Kim istediğini nasıl kanıtlayacak; Bölüm 41.)

Uygulamamız için küçük bir sözleşme taslağı (bu taslak ilk sürüm; ayrıntıları Bölüm 35 ve 37'de kuracağız):

İstekAnlamıBaşarılı yanıtOlası hata
GET /leaderboard?limit=10İlk 10'u getir200 + kullanıcı listesi (JSON)400 (limit geçersiz)
POST /answersBir soruya cevap gönder201 + doğru mu, kaç puan, toplam400, 401, 404, 422

(Bu iki uç nokta, bölümlerin sonundaki Go kodunda gerçekten çalışan iki uç noktadır; durum kodlarının anlamlarını Bölüm 35.2'de ayrı ayrı açacağız.)

Tip, sözleşmenin yalnızca bir kopyasıdır

Bölüm 33'te api.ts dosyasında şunu yazmıştık: type LeaderboardEntry = { user_id: number; name: string; points: number }. Bu tip, TypeScript derleyicisine sözleşmenin ön yüzdeki kopyasını tanıtır. Ama Bölüm 30.3'ten hatırla: tipler çalışma anında silinir (type erasure). Sunucu yarın bir alanın adını değiştirirse, TypeScript bunu derleme sırasında yakalayamaz, çünkü sunucuyu hiç görmez; uygulama çalışırken undefined değerlerle karşılaşır. Yani sözleşme iki yerde yazılıdır (Go'da ve TypeScript'te) ve ikisini birbiriyle tutarlı tutmak insanın ya da agent'ın sorumluluğundadır. Bu yüzden tasarım sırası şu olmalı: önce sözleşmeyi (uç noktalar, istek ve yanıt şekilleri) metin olarak yaz; sonra iki tarafı ona göre yaz. Agent'a iş verirken "önce uç nokta sözleşmesini yaz, ben onaylayayım, sonra kodu yaz" demek bu yüzden iyi bir alışkanlıktır (Bölüm 46.3).

Sözleşmeyi bozmak: sürümleme

Sözleşmenin bir özelliği daha önemli: bozulduğunda iki taraf da etkilenir. Bir web sitesini sunucuda güncellersen herkes yeni sürümü anında görür. Ama bir mobil uygulama kullanıcıların telefonlarında yüklüdür; kullanıcı güncellemeyi hemen yapmaz, bazıları haftalarca eski sürümü kullanır. Sen sunucuda points alanını score diye yeniden adlandırırsan, eski sürümü kullanan binlerce telefon bir anda bozulur. Bu yüzden API değişiklikleri genellikle geriye dönük uyumlu yapılır (yeni alan eklenir, eskisi kaldırılmaz) ya da API sürümlenir (/v1/leaderboard, /v2/leaderboard) ve eski sürüm bir süre yaşatılır. Bu, hukukta sözleşmenin tek taraflı değiştirilememesine benzer: bir tarafın değişiklik yapması için öbür tarafın hazır olması gerekir.

34.4 Durumsuz ve durumlu sunucu

Bir sunucu bir istemciyle ilgili bir şeyi bir istekten ötekine hatırlıyor mu? Bu sorunun cevabı tasarımı ikiye ayırır.

Durumlu sunucu: hatırlayan

Durumlu sunucu (stateful server), her istemci için kendi belleğinde bir şey tutan sunucudur: "Kerem şu anda 3. soruda, 2 canı kaldı." Kerem ikinci isteği gönderdiğinde sunucu, kim olduğunu ve neyi yarıda bıraktığını kendi hafızasında bulur. İlk bakışta doğal görünür: bir garsonun hangi masanın ne sipariş ettiğini hatırlaması gibi.

Durumsuz sunucu: her istek kendi başına yeter

Durumsuz sunucu (stateless server) ise istekler arasında istemciyle ilgili hiçbir şeyi kendi belleğinde tutmaz; her istek, o isteği anlamak için gereken bütün bilgiyi taşır. Roy Fielding'in REST tezi bunu açık bir kısıt olarak koyar: istemciden sunucuya her istek, isteği anlamak için gereken tüm bilgiyi içermeli ve sunucuda saklı bir bağlamdan yararlanamamalı; oturum durumu tamamen istemcide tutulur.4 Fielding bu kısıtın üç özellik sağladığını söyler: görünürlük (bir izleme sistemi tek bir isteğe bakarak onun ne olduğunu anlayabilir), güvenilirlik (kısmi arızalardan toparlanmak kolaylaşır) ve ölçeklenebilirlik (sunucu istekler arası durum tutmak zorunda olmadığı için kaynaklarını hızla serbest bırakabilir).4 Bedelini de açıkça yazar: aynı verinin her istekte tekrar gönderilmesi ağ performansını düşürebilir; ve uygulama durumunu istemcide tutmak, sunucunun tutarlı davranış üzerindeki kontrolünü azaltır.4

Garson benzetmesini sürdürelim: durumsuz bir garson hiçbir şey hatırlamaz; ama her sipariş fişinin üzerinde masa numarası, ne istendiği ve kim için olduğu yazılıdır. Garson değişse de, mutfak yeniden başlasa da, fiş sayesinde sipariş yürür.

HTTP'nin kendisi durumsuzdur, ama oturumsuz değildir

MDN bu inceliği çok iyi söyler: HTTP durumsuzdur, aynı bağlantıda art arda yapılan iki istek arasında hiçbir bağ yoktur; bu, alışveriş sepeti gibi tutarlı etkileşim isteyen sayfalar için sorundur. Ama HTTP'nin çekirdeği durumsuz olsa da, çerezler (cookies) sayesinde durumlu oturumlar mümkündür: üstbilgilerin genişletilebilirliğinden yararlanılarak her isteğin aynı bağlamı paylaşması sağlanır.1 Yani durumsuz sunucu, "kullanıcıyı tanımaz" demek değildir: her istek, kim olduğunu bir belirteçle (token, çerez ya da başlık) bildirir; sunucu belirteci doğrular ve işini görür. Çerez ve belirteç mekaniğini Bölüm 35.6'da ilk bakışla, Bölüm 41'de ayrıntıyla göreceğiz.

Neden durumsuz tercih edilir?

Üç somut sebep:

  1. Yeniden başlatma. Sunucu bir hata yüzünden çöker ya da yeni sürümü yüklemek için kapatılırsa, durumlu bir sunucu kullanıcıların oturumlarını kaybeder.
  2. Birden çok sunucu. Kullanıcı çoğaldığında bir değil, on sunucu çalıştırılır ve önüne bir yük dengeleyici (Bölüm 44.6) konur. Durumlu bir tasarımda Kerem'in 2. isteği 1. sunucuya, 3. isteği 7. sunucuya giderse, 7. sunucu onun kim olduğunu bilmez. Durumsuzda hangi sunucuya gittiği önemli değildir: hepsi aynı davranır.
  3. Test ve anlama kolaylığı. Her isteğin sonucu yalnızca kendi içeriğine ve veritabanına bağlıdır; "bu sunucu daha önce ne yaşadı" sorusu yoktur.

Sonuç: hafıza sunucuda değil, başka yerde

"Durumsuz" demek, hiçbir şeyin hatırlanmadığı anlamına gelmez. Puanlar, seri, ilerleme elbette kaydedilir. Fark şu: bunlar sunucu sürecinin belleğinde değil, ayrı, kalıcı bir yerde (veritabanı, Bölüm 38–39) ve sık erişilen kısa ömürlü şeyler için bellek-içi bir depoda (Redis, Bölüm 40) durur. Sunucu programı, her istekte ihtiyacını oradan okur ve sonucunu oraya yazar. Sunucu süreci bir "hesap makinesidir", hafızası dışarıdadır.

Bu bölümü yazarken bunun tersini de gördüm: Bölüm 37'deki örnek Go programı, basitlik için puanları kendi belleğinde tutuyor (bir Go haritasında). Çalıştırınca leaderboard'da Kerem 40 puanlı; doğru bir cevap gönderince 50 oluyor. Sunucuyu kapatıp yeniden açtığımda ne oldu?

$ curl -X POST localhost:8080/answers -H 'Authorization: token-kerem' -d '{"question_id":1,"choice":2}'
{"correct":true,"points_awarded":10,"total_points":50}
$ curl localhost:8080/leaderboard?limit=3
[{"user_id":2,"name":"Emre","points":70},{"user_id":3,"name":"Ayşe","points":55},{"user_id":1,"name":"Kerem","points":50}]

        (sunucuyu kapat, yeniden başlat)

$ curl localhost:8080/leaderboard?limit=3
[{"user_id":2,"name":"Emre","points":70},{"user_id":3,"name":"Ayşe","points":55},{"user_id":1,"name":"Kerem","points":40}]

Kerem yine 40. Sunucu belleğindeki puan, süreç bitince bitti (bellek uçucu, Bölüm 8.3). Bu bir kusur değil, bir öğretici hata: sunucuyu "hafızasız" yaptığımızda hafızanın başka bir yere taşınması gerektiğini gösteriyor. O yer, bir sonraki dört bölümün konusu: veritabanı.

Sık karışan nokta

"Durumsuz" ile "veri saklamaz" aynı şey değildir. Durumsuz olan, sunucu programıdır; sistem bütünü verileri elbette saklar. Ayrıca "HTTP durumsuz" cümlesiyle "web siteleri seni hatırlamaz" cümlesi çelişmez: web siteleri seni çerezler, belirteçler ve veritabanları aracılığıyla hatırlar; HTTP protokolünün kendisi hatırlamaz.

34.5 Uygulamamızın arka ucunun görevleri

Şimdi somut düşünelim: uygulamamız için arka uç tam olarak neler yapacak? Beş iş alanı sayabiliriz. Bu liste aynı zamanda önümüzdeki bölümlerin yol haritasıdır.

GörevNe yapar?Hangi bölümde kurulur?
KullanıcıKayıt, giriş, "bu istek kimden geliyor?" sorusunun cevabı; kullanıcı adı, parola özeti, oluşturulma tarihiVeri: 38, 39 · Kimlik: 41
İçerikBölümler, dersler, sorular, şıklar, doğru cevaplar. Doğru cevabı yalnızca sunucu bilirVeri: 38 · Uç nokta: 37
İlerlemeHangi kullanıcı hangi soruyu ne zaman, hangi şıkla cevapladı; hangi bölümü bitirdi38 (şema), 39 (işlem)
Puan, seri, canKuralları uygulayıp sayıları üretmek (güven sınırı: 34.2); serinin gün sınırını sunucu saatiyle belirlemek37 (kural kodu), 39 (işlem)
LeaderboardTüm kullanıcıların puanlarını sıralayıp ilk N'i vermek; sık sorulduğu için hızlı olmalıSQL sıralaması: 38 · Önbellek: 40

Bu beş alanın ortak bir kalıbı var: hepsi ya kalıcı bir veriyi okumak, ya kuralı uygulayıp yazmak, ya da ikisini birden yapmak. Bir kullanıcının tek bir eyleminin, bu beş alandan kaçını tetiklediğine bakalım: kullanıcı bir soruya cevap veriyor.

  Expo uygulaması                     Go arka ucu                          Veri
  ───────────────                     ───────────                          ────
  "1. soru, 2. şık"  ── POST ────▶   1. kimlik: bu kim?              (Kullanıcı)
                       /answers       2. soruyu bul, doğru şık ne?   (İçerik)
                                      3. doğru mu? kaç puan?         (Kural)
                                      4. cevabı ve puanı kaydet      (İlerleme)
                                      5. toplam puanı güncelle       (Puan)
  ekranda "+10"        ◀── JSON ──   6. sonucu yanıt olarak yaz
  leaderboard yenilenir ── GET ───▶   7. ilk 10'u sırala             (Leaderboard)
                       /leaderboard

Bu diyagramı Bölüm 45.1'de ("Bir cevabın yolculuğu") bütün katlarıyla yeniden çizeceğiz; şimdilik yalnızca arka ucun içindeki akışa dikkat et. Yedi adımın hiçbiri ön yüzde yapılamaz; ya da yapılırsa güvenilir olmaz.

Uygulamamızda

Tipik bir tasarım kararı: leaderboard'u her istekte baştan hesaplamak mı, yoksa hazır tutmak mı? Başlangıçta, 100 kullanıcıyla, ilki doğru ve yeterince hızlıdır: veritabanına "puanlara göre sırala, ilk 10'u getir" demek. Kullanıcı sayısı on binlere çıkıp herkes sürekli leaderboard'a baktığında bu hesap sık tekrarlanır; sonucu bir yerde hazır tutmak mantıklı olur: Bölüm 40'ın Redis'li çözümü tam bunun için. Sırası şu: önce doğru ve basit çalışsın (Bölüm 37–39), sonra gerekirse hızlansın (Bölüm 40). Agent "baştan Redis ekleyelim" önerirse, bu bölümdeki soruyu sorabilirsin: yavaş olan bir şey ölçtük mü?

Bölüm 22 ve 33 ile bağlantı

Bölüm 22.4'te her uygulama türünün iki parçası olduğunu söylemiştik: istemci ve sunucu. Bölüm 22.5'te bizimkinin türü "web + mobil (Expo) + Go arka uç" olarak kaydedilmişti. Bu bölümde o "Go arka uç"un kapsamını çizdik. Bölüm 33'te arayüz tarafı için sunucu durumu kavramını kurmuştuk ("asıl kopya uzakta"); bu bölümde o uzak asıl kopyanın sahibi olan sunucunun nasıl çalıştığını gördük. İki bölüm aynı hattın iki ucudur: biri veriyi isteyen taraf, öteki veriyi tutan taraf. Aradaki hattın dilini, yani HTTP ile JSON'u, bir sonraki bölümde ayrıntılı göreceğiz.

Bu bölümde neler kuruldu

Sunucu, bir portu dinleyip istek bekleyen, kendi kendine bitmeyen bir süreçtir; istemci isteği her zaman kendisi başlatır, sunucu yalnızca cevap verir. Uygulamanın iki yarısı vardır: kullanıcının elindeki ön yüz ve ortak sunucudaki arka uç. Aralarındaki çizgi güven sınırıdır: kullanıcıdan gelen her şey güvenilmez kabul edilir ve sunucuda doğrulanır; bu yüzden puan, seri ve kural hesapları sunucuda yapılır, ön yüzdeki kontroller yalnızca hızlı geri bildirim içindir. API, iki programın birbirine verdiği sözdür: hangi adresler, hangi istekler, hangi yanıtlar; sözleşme değişirse iki taraf da etkilenir, bu yüzden geriye uyumluluk ve sürümleme önemlidir. Durumsuz sunucu istekler arasında istemciyle ilgili bir şey hatırlamaz; her istek gereken bilgiyi taşır, kalıcı hafıza veritabanındadır. Arka ucun görevleri: kullanıcı, içerik, ilerleme, puan/seri/can ve leaderboard.

Terimler sözlükte, Bölüm 34 başlığı altında.

Birincil okuma

MDN, Overview of HTTP: şu başlıklar: Components of HTTP-based systems (istemci, sunucu, vekil), HTTP is stateless, but not sessionless. Derinleşmek için: OWASP, Input Validation Cheat Sheet (güven sınırının pratik yüzü) ve Roy Fielding'in REST tezinin 5. bölümü, yalnızca 5.1.3 Stateless alt başlığı (geri kalanını şimdilik atla).

Takıldığın her yeri yapay zekâ öğretmenine sor. Bir öneri: ondan, "bir mobil oyunda skor tablosu hile yapılmasın diye hangi hesap sunucuda yapılmalı?" sorusuna üç örnekle cevap vermesini iste; sonra aynısını bizim uygulamanın seri ve can kuralı için kendi cümlenle yaz.

Bölüm sonu soruları

Kitaba bakmadan cevapla. Kâğıt kalem serbest.

1. Bu kitapta "sunucu" dendiğinde aksi söylenmedikçe ne kastedilir?
"Sunucu" hem makineye hem programa söylenir; burada kastedilen, bir portu dinleyip istek bekleyen, sürekli çalışan süreçtir. Bkz. 34.1.
2. HTTP'de isteği her zaman başlatan taraf hangisidir?
İstekler istemci tarafından başlatılır; sunucu yalnızca sorulunca cevap verir (sunucudan başlatılmış gibi görünen mekanizmalar sonradan eklenmiştir). Bkz. 34.1.
3. İstemci {"points":999} gönderiyor. En doğru sunucu tasarımı hangisidir?
Ön yüz kullanıcının kontrolündedir; puan istemciden gelirse sahte olabilir. Sunucu yalnızca seçilen şıkkı alır, doğruluğu ve puanı kendisi belirler. Bkz. 34.2.
4. "Güven sınırı" neyi birbirinden ayırır?
Güven sınırı, kendi kontrol ettiğin kod ile kontrol etmediğin taraf arasındaki çizgidir; sınırı geçen her şey doğrulanır. Bkz. 34.2.
5. Ön yüzdeki doğrulama (örneğin boş cevap kontrolü) neden yine de yapılır?
İstemci tarafı doğrulama atlatılabilir; deneyimi hızlandırır ama güvenliği sağlamaz. Güvenlik için sunucuda da doğrulanır. Bkz. 34.2.
6. Bir API için en iyi tanım hangisidir?
API, bir programın başka bir programa hangi isteklerde bulunabileceğini ve ne alacağını tanımlayan önceden anlaşılmış kurallardır. Bkz. 34.3.
7. Sunucuda bir alanın adı değişince eski mobil sürümler neden bozulabilir?
Mobil uygulamalar kullanıcıların cihazlarında yüklüdür ve güncelleme gecikir; bu yüzden API değişiklikleri geriye uyumlu ya da sürümlü yapılır. Bkz. 34.3.
8. Durumsuz sunucuda her istek neyi taşımak zorundadır?
Fielding'e göre her istek, anlaşılması için gereken bütün bilgiyi içerir ve sunucudaki saklı bağlama yaslanmaz. Kalıcı hafıza veritabanındadır. Bkz. 34.4.
9. Puanlar sunucunun kendi belleğinde tutulursa yeniden başlatınca ne olur?
Bellek uçucudur; süreç bitince içindeki veri de biter. Bu yüzden kalıcı veri veritabanına yazılır (Bölüm 38). Bkz. 34.4.
10. Kendi cümlelerinle: seri (streak) hesabını neden telefona değil sunucuya bırakmalıyız? "Güven sınırı" kavramını kullan.
Örnek cevabı göster

Seri, "dün de çalıştın mı?" sorusuna dayanır, yani saate. Telefonun saati kullanıcının kontrolündedir; saati değiştirerek sahte seri kazanılabilir. Telefon güven sınırının kullanıcı tarafındadır, bu yüzden oradan gelen her bilgi (saat dahil) doğrulanmadan kabul edilmemeli. Gün sınırını sunucunun saati belirlerse sahte seri üretmek mümkün olmaz.

11. Durumlu ve durumsuz sunucu arasındaki farkı, on tane sunucunun yük dengeleyici arkasında çalıştığı bir senaryo üzerinden anlat.
Örnek cevabı göster

Durumlu sunucuda Kerem'in oturum bilgisi sunucu 1'in belleğindedir; sonraki isteği sunucu 7'ye giderse sunucu 7 onu tanımaz. Durumsuz tasarımda her istek kendini tanıtan bir belirteç taşır ve kalıcı veri ortak veritabanındadır; isteğin hangi sunucuya gittiği önemli değildir, hepsi aynı cevabı üretir. Bu sayede sunucular kolayca eklenip çıkarılabilir ve yeniden başlatılabilir.

Kaynaklar

  1. MDN, Overview of HTTP: istemci–sunucu protokolü, isteği tarayıcının başlatması, sunucunun birden çok makine olabilmesi, HTTP'nin durumsuz ama oturumsuz olmaması, çerezler.
  2. PostgreSQL belgeleri, 1.2. Architectural Fundamentals: sunucu süreci, istemci uygulama, ağ üzerinden konuşma, her bağlantı için süreç.
  3. OWASP, Input Validation Cheat Sheet: istemci tarafı doğrulamanın atlatılabilirliği, sunucuda doğrulama, güven sınırını geçen veri.
  4. Roy T. Fielding, Architectural Styles and the Design of Network-based Software Architectures (2000), Bölüm 5, 5.1.3 Stateless: durumsuzluk kısıtı, görünürlük/güvenilirlik/ölçeklenebilirlik, bedeli. Yalnızca atıf için kullanıldı.
  5. Bölüm içindeki curl çıktıları, bu kitabın yazımı sırasında Go 1.27.2 ile derlenip çalıştırılan Bölüm 37 örnek programından alınmıştır (kendi deneyimiz; yeniden üretilebilir).