---
title: 'Ön Yüz İçin Backend'
source: 'https://academia.sh/tr/kurslar/servis-mimarileri/on-yuz-icin-backend'
course: 'Servis Mimarileri'
language: tr
updated: '2026-08-17T18:06:53+00:00'
license: 'CC BY-SA 4.0'
---

# Ön Yüz İçin Backend

İstemciye özel toplama katmanının gerçekleştirimi: tek genel uçtan beslenen iki istemcinin taşıdığı fazla veri ile yaptığı istek sayısının ölçülmesi, ayrı toplama katmanlarının bu iki sayıya etkisi ve ödediği yinelenen mantık satırı ile sessiz ayrışma riski.

Önceki ders kenar sorumluluklarını tek bir yerde topladı: doğrulama, sınırlama ve yönlendirme
artık her servisin içinde değil, önündeki tek katmanda duruyor. O katmanın sessiz kabulü şudur:
sınırın arkasındaki servisler ne döndürüyorsa istemciye o gider. Oysa aynı uca bağlanan
istemciler aynı yanıtı istemiyor. Kütüphane ödünç sisteminin web listesi bir üyenin bütün ödünç
kayıtlarını kitap adı, yazar ve raf koduyla birlikte gösteriyor; mobil özet ekranı yalnız iki
sayı ve bir başlık gösteriyor. İkisi de aynı uçtan besleniyor.

Bu dersin işi, o durumu ölçmek ve **ön yüz için backend** (backend for frontend) yerleşimiyle
karşılaştırmaktır. Kalıbın tanımı Trafik Katmanı kursunda kuruldu ve orada trafik tarafından
ölçüldü; burada ölçülen şey uygulama tarafıdır: istemcinin taşıdığı bayt, yaptığı istek ve
toplama katmanının kod olarak ödettiği tekrar.

Düzeneğin varsayımları: **SS10** iki istemci vardır (web listesi, mobil özet) ve üçüncü bir
istemci yoktur. **SS11** katalog, üyelik ve ödünç üç ayrı süreçtir; katalog kaydı on alan
taşır. **SS12** ölçüm süre ölçmez; istemci ağındaki istek sayısını, taşınan baytı ve alan
sayısını sayar.

## Üç Aşağı Servis ve Tek Genel Uç

Üç servis tek dosyadan, üç ayrı süreç olarak koşuyor. Her biri kendi istek ve bayt sayacını
tutuyor; bu sayaçlar aşağı tarafın gördüğü yükü ölçmeye yarayacak.

```js
// bff/servis.mjs — uc asagi servis, tek dosya: node bff/servis.mjs katalog 8741
import { createServer } from "node:http";

const [ad, port] = [process.argv[2], Number(process.argv[3])];
const KITAP = {
  "978-0": { isbn: "978-0", ad: "Kayip Zamanin Izinde", yazar: "M. Proust", cevirmen: "R. Hakmen",
    yayinevi: "Yapi Kredi", basimYili: 1913, sayfa: 542, tur: "roman", dil: "tr", raf: "PQ-2631" },
  "978-1": { isbn: "978-1", ad: "Sessiz Ev", yazar: "O. Pamuk", cevirmen: null,
    yayinevi: "Iletisim", basimYili: 1983, sayfa: 320, tur: "roman", dil: "tr", raf: "PL-248" },
  "978-2": { isbn: "978-2", ad: "Algoritmalara Giris", yazar: "T. Cormen", cevirmen: "A. Yilmaz",
    yayinevi: "MIT", basimYili: 2009, sayfa: 1312, tur: "ders", dil: "tr", raf: "QA-76" },
  "978-3": { isbn: "978-3", ad: "Bozkirkurdu", yazar: "H. Hesse", cevirmen: "K. Sipal",
    yayinevi: "Can", basimYili: 1927, sayfa: 224, tur: "roman", dil: "tr", raf: "PT-2617" },
};
const UYE = { uyeNo: "U-41", ad: "Zeynep", soyad: "Aydin", eposta: "z.aydin@ornek.tr",
  telefon: "+90-555-0000", adres: "Kordon Cad. 12, Izmir", uyelikTarihi: "2019-04-02",
  uyelikTuru: "arastirmaci", cezaBakiyesi: 0, kartDurumu: "etkin" };
const ODUNC = [
  { isbn: "978-0", alinma: "2026-02-10", sonTeslim: "2026-03-10", uzatma: 1 },
  { isbn: "978-1", alinma: "2026-02-24", sonTeslim: "2026-03-24", uzatma: 0 },
  { isbn: "978-2", alinma: "2026-02-14", sonTeslim: "2026-03-14", uzatma: 2 },
  { isbn: "978-3", alinma: "2026-03-02", sonTeslim: "2026-04-01", uzatma: 0 },
];

let istek = 0, bayt = 0;                        // servis basina istek ve gonderilen govde bayti
const VER = { katalog: (u) => KITAP[u.split("/")[2]], uyelik: () => UYE, odunc: () => ODUNC };

createServer((q, y) => {
  if (q.url === "/sayac") {
    y.writeHead(200, { "Content-Type": "application/json" });
    return y.end(JSON.stringify({ ad, istek, bayt }));
  }
  const govde = JSON.stringify(VER[ad](q.url));
  istek += 1;
  bayt += Buffer.byteLength(govde);
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(port, "127.0.0.1", () => console.log(`${ad} 127.0.0.1:${port}`));
```

Genel uç, üyelik ile ödünç kayıtlarını birleştirip olduğu gibi döndürüyor. Katalog verisini
eklemiyor, çünkü katalog başka bir sınırın arkasında ve bu uç ondan önce yazıldı. Sonuç şu:
kitap adını göstermek isteyen her istemci, listeyi aldıktan sonra katalog servisine kendisi
gitmek zorunda.

```js
// bff/genel-uc.mjs — tek genel uc 8744: iki istemci de burayi cagirir, ayni govdeyi alir
import { createServer } from "node:http";

const AL = async (u) => (await fetch(u)).json();

createServer(async (q, y) => {
  const uyeNo = q.url.split("/")[2];
  const uye = await AL("http://127.0.0.1:8742/uye/" + uyeNo);
  const odunc = await AL(`http://127.0.0.1:8743/uye/${uyeNo}/odunc`);
  const govde = JSON.stringify({ uye, odunc });
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(8744, "127.0.0.1", () => console.log("genel-uc 127.0.0.1:8744"));
```

## İki İstemci, İki Toplama Katmanı

İkinci gerçekleştirimde her istemcinin kendi toplama katmanı var. Web katmanı listeyi kurup
gecikme gününü hesaplıyor; mobil katmanı aynı veriden iki sayı ve bir başlık çıkarıyor. İkisi
de ayrı bir dağıtım birimidir ve birbirinin kodunu kullanmıyor.

```js
// bff/bff-web.mjs — web listesi icin toplama katmani 8745
import { createServer } from "node:http";

const BUGUN = "2026-03-16";
const KATALOG = "http://127.0.0.1:8741/kitap/";
const UYELIK = "http://127.0.0.1:8742/uye/";
const ODUNC = "http://127.0.0.1:8743/uye/";
const AL = async (u) => (await fetch(u)).json();
const gunFarki = (a, b) => Math.round((Date.parse(a) - Date.parse(b)) / 86400000);

createServer(async (q, y) => {
  const uyeNo = q.url.split("/")[2];
  const uye = await AL(UYELIK + uyeNo);
  const odunc = await AL(`${ODUNC}${uyeNo}/odunc`);
  const kitap = await Promise.all(odunc.map((o) => AL(KATALOG + o.isbn)));
  const satir = odunc.map((o, i) => ({
    baslik: kitap[i].ad, yazar: kitap[i].yazar, raf: kitap[i].raf,
    teslim: o.sonTeslim, gecikmeGunu: Math.max(0, gunFarki(BUGUN, o.sonTeslim)),
  }));
  const govde = JSON.stringify({ okuyucu: `${uye.ad} ${uye.soyad}`, satir });
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(8745, "127.0.0.1", () => console.log("bff-web 127.0.0.1:8745"));
```

```js
// bff/bff-mobil.mjs — mobil ozet icin toplama katmani 8746
import { createServer } from "node:http";

const BUGUN = "2026-03-16";
const KATALOG = "http://127.0.0.1:8741/kitap/";
const UYELIK = "http://127.0.0.1:8742/uye/";
const ODUNC = "http://127.0.0.1:8743/uye/";
const AL = async (u) => (await fetch(u)).json();
const gunFarki = (a, b) => Math.round((Date.parse(a) - Date.parse(b)) / 86400000);

createServer(async (q, y) => {
  const uyeNo = q.url.split("/")[2];
  const uye = await AL(UYELIK + uyeNo);
  const odunc = await AL(`${ODUNC}${uyeNo}/odunc`);
  const gecikmis = odunc.filter((o) => gunFarki(BUGUN, o.sonTeslim) > 0).length;
  const yakin = odunc.slice().sort((a, b) => Date.parse(a.sonTeslim) - Date.parse(b.sonTeslim))[0];
  const kitap = await AL(KATALOG + yakin.isbn);
  const govde = JSON.stringify({ okuyucu: uye.ad, gecikmis,
    enYakin: { baslik: kitap.ad, kalanGun: gunFarki(yakin.sonTeslim, BUGUN) } });
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(8746, "127.0.0.1", () => console.log("bff-mobil 127.0.0.1:8746"));
```

## Ölçüm

Ölçüm betiği dört durumu koşuyor: iki istemci, iki gerçekleştirim. Her durumda istemcinin
yaptığı istek sayısını, aldığı baytı ve aldığı gövdedeki yaprak alan sayısını topluyor;
istemcinin gerçekten okuduğu adlara göre gövdeyi budayıp kullanılmayan baytı çıkarıyor. Son
sütun sızıntı ölçüsüdür: istemcinin okuduğu adlardan kaçı aşağı servislerin kendi alan adıdır.

```js
// bff/olc.mjs — iki istemci x iki gerceklestirme: istek, bayt, fazla veri, yinelenen satir
import { readFileSync } from "node:fs";

const al = async (u) => {
  const m = await (await fetch(u)).text();
  return { bayt: Buffer.byteLength(m), veri: JSON.parse(m) };
};
const SAYAC = [8741, 8742, 8743].map((p) => `http://127.0.0.1:${p}/sayac`);
const toplam = async () => (await Promise.all(SAYAC.map(al)))
  .reduce((s, x) => s + x.veri.istek, 0);

// Istemcilerin ekrana koymak icin gercekten okudugu alan adlari
const ALAN = {
  "web/genel uc": ["ad", "soyad", "isbn", "sonTeslim", "yazar", "raf"],
  "mobil/genel uc": ["ad", "isbn", "sonTeslim"],
  "web/bff": ["okuyucu", "satir"],
  "mobil/bff": ["okuyucu", "gecikmis", "enYakin"],
};
const SENARYO = {
  "web/genel uc": async () => {                    // genel uc + her kitap icin katalog cagrisi
    const g = await al("http://127.0.0.1:8744/uye/U-41/ozet");
    const k = await Promise.all(g.veri.odunc.map((o) => al(`http://127.0.0.1:8741/kitap/${o.isbn}`)));
    return { istek: 1 + k.length, bayt: g.bayt + k.reduce((s, x) => s + x.bayt, 0),
      govde: { ...g.veri, kitap: k.map((x) => x.veri) } };
  },
  "mobil/genel uc": async () => {                  // ayni genel uc + yalniz en yakin kitap
    const g = await al("http://127.0.0.1:8744/uye/U-41/ozet");
    const y = g.veri.odunc.slice().sort((a, b) => Date.parse(a.sonTeslim) - Date.parse(b.sonTeslim))[0];
    const k = await al(`http://127.0.0.1:8741/kitap/${y.isbn}`);
    return { istek: 2, bayt: g.bayt + k.bayt, govde: { ...g.veri, kitap: [k.veri] } };
  },
  "web/bff": async () => {
    const g = await al("http://127.0.0.1:8745/uye/U-41/liste");
    return { istek: 1, bayt: g.bayt, govde: g.veri };
  },
  "mobil/bff": async () => {
    const g = await al("http://127.0.0.1:8746/uye/U-41/ozet");
    return { istek: 1, bayt: g.bayt, govde: g.veri };
  },
};

const yaprak = (v) => Array.isArray(v) ? v.reduce((s, e) => s + yaprak(e), 0)
  : v && typeof v === "object" ? Object.values(v).reduce((s, e) => s + yaprak(e), 0) : 1;
const buda = (v, k) => {                           // yalniz istemcinin okudugu adlari birak
  if (Array.isArray(v)) return v.map((e) => buda(e, k));
  if (v && typeof v === "object") {
    const o = {};
    for (const [a, d] of Object.entries(v)) {
      if (k.has(a)) o[a] = d;
      else { const alt = buda(d, k); if (alt && Object.keys(alt).length) o[a] = alt; }
    }
    return o;
  }
  return null;
};
const anlamli = (dosya) => readFileSync(dosya, "utf8").split("\n")
  .map((s) => s.trim()).filter((s) => s && !s.startsWith("//") && /[A-Za-z]/.test(s));

// Asagi servislerin urettigi alan adlari: sizinti olcusunun tabani
const ham = await Promise.all(["8742/uye/U-41", "8743/uye/U-41/odunc", "8741/kitap/978-0"]
  .map((y) => al(`http://127.0.0.1:${y}`)));
const ASAGI = new Set(ham.flatMap((h) => Object.keys(Array.isArray(h.veri) ? h.veri[0] : h.veri)));

const satir = [];
for (const [ad, calistir] of Object.entries(SENARYO)) {
  const once = await toplam();
  const s = await calistir();
  const sonra = await toplam();
  const k = new Set(ALAN[ad]);
  satir.push({ ad, govde: s.govde, istek: s.istek, ic: sonra - once, bayt: s.bayt,
    gelen: yaprak(s.govde), kullanilan: yaprak(buda(s.govde, k)),
    fazla: s.bayt - Buffer.byteLength(JSON.stringify(buda(s.govde, k))),
    sizan: ALAN[ad].filter((a) => ASAGI.has(a)).length });
}

const B = ["istemci istegi", "asagi istek", "alinan B", "gelen yaprak", "kullanilan", "fazla B", "sizan ad"];
console.log(`${"istemci/gerceklestirme".padEnd(17)}${B.map((b) => b.padStart(15)).join("")}`);
for (const s of satir) {
  console.log(`${s.ad.padEnd(17)}${[s.istek, s.ic, s.bayt, s.gelen, s.kullanilan, s.fazla, s.sizan]
    .map((v) => String(v).padStart(15)).join("")}`);
}

console.log(`\nmobil/bff govdesi: ${JSON.stringify(satir.at(-1).govde)}`);
const w = anlamli("bff/bff-web.mjs"), m = anlamli("bff/bff-mobil.mjs");
const ortak = w.filter((s) => m.includes(s));
console.log(`\nbff-web anlamli satir = ${w.length}, bff-mobil = ${m.length}, ` +
  `ikisinde birebir ayni = ${ortak.length}`);
console.log(`genel uc dosyasi = ${anlamli("bff/genel-uc.mjs").length} satir, ` +
  `toplama katmani dosyasi = ${w.length + m.length} satir`);
console.log(`asagi servis alan adi = ${ASAGI.size}; toplama katmani = 2 dagitim birimi`);
```

```bash
for s in "servis.mjs katalog 8741" "servis.mjs uyelik 8742" "servis.mjs odunc 8743" \
         "genel-uc.mjs" "bff-web.mjs" "bff-mobil.mjs"; do
  node bff/$s > /dev/null &
done
for p in 8741 8742 8743 8744 8745 8746; do
  curl -s --retry 30 --retry-connrefused --retry-delay 0 -o /dev/null http://127.0.0.1:$p/sayac
done
node bff/olc.mjs
kill $(jobs -p)
```

```
istemci/gerceklestirme istemci istegi    asagi istek       alinan B   gelen yaprak     kullanilan        fazla B       sizan ad
web/genel uc                   5              6           1216             66             26            691              6
mobil/genel uc                 2              3            723             36             11            467              3
web/bff                        1              6            442             21             21              0              0
mobil/bff                      1              3             91              4              4              0              0

mobil/bff govdesi: {"okuyucu":"Zeynep","gecikmis":2,"enYakin":{"baslik":"Kayip Zamanin Izinde","kalanGun":-6}}

bff-web anlamli satir = 19, bff-mobil = 19, ikisinde birebir ayni = 13
genel uc dosyasi = 10 satir, toplama katmani dosyasi = 38 satir
asagi servis alan adi = 22; toplama katmani = 2 dagitim birimi
```

## Sayıların Okunması

İlk iki satır tek genel ucun bedelini veriyor. Web listesi ekranı kurmak için beş istek yapıyor
ve 1216 bayt alıyor; bu baytın 691'i, yani yüzde 57'si hiçbir zaman ekrana çıkmıyor. Mobil özet
ekranı iki sayı ve bir başlık göstermek için iki istek yapıyor, 723 bayt alıyor ve bunun 467'si
kullanılmıyor. Alan tarafında oran daha keskin: mobil istemci otuz altı yaprak değer alıyor ve
on birini okuyor.

Alt iki satır toplama katmanının etkisini gösteriyor. İstemci isteği 5'ten ve 2'den 1'e iniyor.
Alınan bayt web tarafında 1216'dan 442'ye, mobil tarafında 723'ten 91'e düşüyor — mobil ekran
için taşınan veri sekizde bire iniyor. Fazla bayt iki durumda da 0'dır, çünkü katman gövdeyi
ekranın istediği biçimde kuruyor.

Aşağı istek sütunu ise değişmiyor: web tarafında iki gerçekleştirimde de 6, mobil tarafında 3.
Bu, kalıbın en çok yanlış anlaşılan yeridir. Toplama katmanı aşağı servislerin yükünü
azaltmıyor; aynı işi yapıyor, yalnız **nerede** yaptığını değiştiriyor. Beş gidiş-dönüş
istemcinin ağından iç ağa taşınıyor. Kazanç istemcinin ağındadır ve orada gidiş-dönüş bir
sunucu içi çağrıdan çok daha pahalıdır.

Sızan ad sütunu bağlaşımı ölçüyor. Genel uç yerleşiminde web istemcisinin okuduğu altı adın
altısı da aşağı servislerin kendi alan adıdır; `sonTeslim` adı ödünç servisinin sözlüğünden
gelir ve istemci koduna kadar iner. Toplama katmanı yerleşiminde bu sayı 0'dır: istemci
`teslim`, `gecikmeGunu`, `enYakin` gibi ekranın sözlüğünü görür. Ödünç servisi alanını yeniden
adlandırdığında ilk yerleşimde istemci kodu düzeltilip yeniden yayınlanmak zorundadır; ikinci
yerleşimde düzeltme katmanda kalır.

## Yinelenen Mantık ve Sessiz Ayrışma

Bedel son üç satırdadır. Genel uç dosyası on anlamlı satır; iki toplama katmanı toplam otuz
sekiz satır. Bunların on üçü iki dosyada birebir aynıdır: üç servis adresi, `AL` yardımcısı,
`gunFarki`, `BUGUN` sabiti, üye ile ödünç çağrıları. Yani her istemci için bir katman kurmak,
aynı mantığı istemci sayısınca kopyalamak demektir. Üçüncü bir istemci geldiğinde on üç satır
üçüncü kez yazılacaktır.

Bu tekrarın doğurduğu arıza kipi, kopyanın kendisi değil, kopyalardan yalnız birinin
güncellenmesidir. Ödünç servisi `sonTeslim` alanını `teslimTarihi` diye yeniden adlandırıyor ve
yalnız web katmanı güncelleniyor.

```bash
sed 's/sonTeslim/teslimTarihi/g' bff/servis.mjs > bff/servis-v2.mjs   # odunc alani yeniden adlandirildi
sed 's/sonTeslim/teslimTarihi/g' bff/bff-web.mjs > bff/bff-web-v2.mjs # yalniz web katmani guncellendi
node bff/servis.mjs katalog 8741 > /dev/null &
node bff/servis.mjs uyelik 8742 > /dev/null &
node bff/servis-v2.mjs odunc 8743 > /dev/null &
node bff/bff-web-v2.mjs > /dev/null &
node bff/bff-mobil.mjs > /dev/null &
for p in 8741 8742 8743 8745 8746; do
  curl -s --retry 30 --retry-connrefused --retry-delay 0 -o /dev/null http://127.0.0.1:$p/sayac
done
echo "web  $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8745/uye/U-41/liste) $(curl -s http://127.0.0.1:8745/uye/U-41/liste | head -c 96)"
echo "mobil $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8746/uye/U-41/ozet) $(curl -s http://127.0.0.1:8746/uye/U-41/ozet)"
kill $(jobs -p)
```

```
web  200 {"okuyucu":"Zeynep Aydin","satir":[{"baslik":"Kayip Zamanin Izinde","yazar":"M. Proust","raf":"P
mobil 200 {"okuyucu":"Zeynep","gecikmis":0,"enYakin":{"baslik":"Kayip Zamanin Izinde","kalanGun":null}}
```

İki katman da 200 döndürüyor. Web listesi doğru; mobil özet ise `gecikmis` alanını 2 yerine 0,
`kalanGun` alanını -6 yerine `null` veriyor. Okunmayan alan `undefined` olduğu için tarih
karşılaştırması sessizce yanlış sonuç üretti ve hiçbir yerde hata kaydı oluşmadı. Tek genel uç
yerleşiminde aynı değişiklik iki istemciyi birden bozardı ve bu, fark edilmesi daha kolay bir
arızadır. Toplama katmanı yerleşiminde arıza **istemciler arasında ayrışma** biçiminde
görünür: bir ekran doğru, öteki sessizce yanlıştır.

Buradan çıkan kural, kalıbın sahipliğiyle ilgilidir. Toplama katmanı aşağı servisin değil,
ekranı yazan ekibin dağıtım birimidir; aşağı servisin sözleşmesi değiştiğinde katman sayısınca
düzeltme gerekir ve o düzeltmelerin hepsinin yapıldığını gösteren tek şey, katmanların
sözleşmeye karşı sınanmasıdır.

## Özet

- Tek genel uç yerleşiminde web listesi 5 istek ve 1216 bayt, mobil özet 2 istek ve 723 bayt
  taşıyor; bu baytların 691 ve 467'si ekrana çıkmıyor.
- İstemciye özel toplama katmanı istek sayısını 1'e, alınan baytı 442 ve 91'e indiriyor; fazla
  bayt iki istemcide de 0 oluyor.
- Aşağı servislere ulaşan istek değişmiyor (6 ve 3): katman yükü azaltmıyor, gidiş-dönüşü
  istemcinin ağından iç ağa taşıyor.
- Bağlaşım ölçüsü: genel uçta istemcinin okuduğu 6 addan 6'sı aşağı servisin alan adı; toplama
  katmanında 0.
- Ödenen kod: iki katman 38 anlamlı satır, bunların 13'ü birebir aynı; ayrıca 2 yeni dağıtım
  birimi.
- Doğan arıza kipi: aşağı alan adı değişip yalnız bir katman güncellendiğinde iki istemci
  sessizce ayrışıyor — web 200 ve doğru, mobil 200 ve `gecikmis: 0`.

## Sonraki Adım

Toplama katmanı da, önündeki ağ geçidi de aynı türden bir çözümdür: bir sorumluluğu servis
kodundan alıp ayrı bir yere koymak. İkisinin de aldığı sorumluluk **biçim** ile ilgiliydi —
hangi alan kime gider, hangi gövde nasıl kurulur. Oysa servis kodunun içinde biçimle ilgisi
olmayan başka bir yığın da duruyor: yeniden deneme döngüsü, zaman aşımı süresi, taşımanın
güvenliği ve her çağrının ölçütünü toplayan sayaçlar. Bu ders boyunca yazılan üç katmanın her
birinde `fetch` çağrısı çıplaktır; ne zaman aşımı vardır, ne yeniden deneme, ne de bir sayaç.
Sonraki ders bu işleri servis kodundan çıkarıp ayrı bir sürece taşıyan yerleşimi ele alır ve
ölçüyü kod tarafında kurar: uygulama kodundan kaç satır silindi, geriye hangi sorumluluk kaldı
ve ayrı süreçte koşmanın bedeli ne oldu.
