---
title: 'Servis Katmanı'
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/servis-katmani'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:39+00:00'
license: 'CC BY-SA 4.0'
---

# Servis Katmanı

Uygulama sınırının tek bir yerde tanımlanması: senaryoyu kendi içinde yürüten iki istemci ile aynı senaryoyu bir kez yazan servis katmanının karşılaştırılması, istemci dosyasındaki altyapı adı ve tekrarlanan senaryo adımı sayısının sayılması, yalnız çağrıyı ileten yöntem sayısıyla ince servisin ölçülmesi.

Önceki dersin istemcileri eşleyicinin ürettiği kaydı hazır aldı; kaydı kimin ürettiği, hangi
adımların hangi sırayla yürüdüğü ve iş biriminin nerede açılıp kapandığı sorulmadı. Bu sorular
bir yerde yanıtlanmak zorundadır. Yanıt her istemcinin içinde tekrarlanırsa senaryo istemci
sayısı kadar yazılır.

**Servis katmanı** (service layer) bu yanıtı tek bir yere koyar: uygulamanın dışarıya açtığı
işlem kümesini tanımlar, her işlemin adım sırasını yürütür, iş biriminin sınırını belirler ve
dışarıya sözleşme kaydı döndürür. Katman sorumlulukları ve bağımlılık yönü Veri Erişim Katmanı
kursunda ölçülmüştü; burada ölçülen şey sınırın kendisidir: istemcinin bilmek zorunda olduğu
altyapı adı sayısı ve birden fazla dosyada tekrarlanan senaryo adımı sayısı.

Ölçüm için önceki derslerin parçaları derli bir dosyada toplanır.

```js
// altyapi.mjs — onceki derslerin parcalari derli bicimde: depo, is birimi, alan kurali, esleyici
const TABLO = new Map();
export const depo = {
  oku: (id) => structuredClone(TABLO.get(id) ?? null),
  yaz: (satir) => { TABLO.set(satir.id, structuredClone(satir)); },
};

export const gonderiDeposu = { bul: (id) => depo.oku(id), yeni: (satir) => depo.yaz(satir) };

export function isBirimi() {
  const kirli = new Map();
  return {
    kirlet: (satir) => kirli.set(satir.id, satir),
    tamamla() { for (const s of kirli.values()) depo.yaz(s); return kirli.size; },
  };
}

export const kural = {
  indirimEkle: (t, indirim) => ({ ...t, indirimler: [...t.indirimler, indirim] }),
  aktarmaEkle: (t, nokta) => ({ ...t, aktarma: [...t.aktarma, nokta] }),
  net: (t) => Math.round(t.taban * (1 - Math.min(t.indirimler.reduce((s, i) => s + i.oran, 0), 0.4))),
};

export const esleyici = { liste: (t) => ({ kimlik: t.id, net: kural.net(t), aktarma: t.aktarma.length }) };
```

## Problem: Senaryo Her İstemcide Yeniden Yazılırsa

Kitaplığın iki istemcisi var: gelen bir isteği karşılayan işleyici ve gece çalışan toplu
betik. İkisi de aynı işi ister — bir gönderiye indirim ve aktarma noktası eklemek — ve ikisi
de adımları kendi içinde yürütür.

```js
// dogrudan/istemci-a.mjs — istek isleyicisi senaryonun adimlarini kendi yurutur
import { gonderiDeposu, isBirimi, kural, esleyici } from "../altyapi.mjs";

export function indirimIste(id, indirim, nokta) {
  let t = gonderiDeposu.bul(id);
  if (t === null) throw new RangeError(`gonderi yok: ${id}`);
  t = kural.indirimEkle(t, indirim);
  t = kural.aktarmaEkle(t, nokta);
  const birim = isBirimi();
  birim.kirlet(t);
  birim.tamamla();
  return esleyici.liste(t);
}
```

```js
// dogrudan/istemci-b.mjs — toplu betik ayni adimlari yeniden yazar
import { gonderiDeposu, isBirimi, kural, esleyici } from "../altyapi.mjs";

export function topluIndirim(kimlikler, indirim, nokta) {
  return kimlikler.map((id) => {
    let t = gonderiDeposu.bul(id);
    if (t === null) throw new RangeError(`gonderi yok: ${id}`);
    t = kural.indirimEkle(t, indirim);
    t = kural.aktarmaEkle(t, nokta);
    const birim = isBirimi();
    birim.kirlet(t);
    birim.tamamla();
    return esleyici.liste(t);
  });
}
```

İkinci dosya birincisinin gövdesini bir döngünün içine almış hâlidir. Tekrarlanan şey kod
satırı değil, **karar**: adımların sırası, doğrulamanın nerede yapıldığı, işlem sınırının
nereye çizildiği.

## Çözüm: Sınırı Bir Kez Yazmak

```js
// servis/gonderi-servisi.mjs — senaryo bir kez burada: sira, islem siniri ve donen sozlesme
import { gonderiDeposu, isBirimi, kural, esleyici } from "../altyapi.mjs";

export const gonderiServisi = {
  indirimVeAktarmaEkle(id, indirim, nokta) {
    let t = gonderiDeposu.bul(id);
    if (t === null) throw new RangeError(`gonderi yok: ${id}`);
    t = kural.indirimEkle(t, indirim);
    t = kural.aktarmaEkle(t, nokta);
    const birim = isBirimi();
    birim.kirlet(t);
    birim.tamamla();
    return esleyici.liste(t);
  },
};
```

```js
// servis/istemciler.mjs — iki istemci ayni uygulama sinirini cagirir
import { gonderiServisi } from "./gonderi-servisi.mjs";

export const indirimIste = (id, indirim, nokta) => gonderiServisi.indirimVeAktarmaEkle(id, indirim, nokta);
export const topluIndirim = (kimlikler, indirim, nokta) =>
  kimlikler.map((id) => gonderiServisi.indirimVeAktarmaEkle(id, indirim, nokta));
```

Toplu betiğin tek işi kaldı: döngü. Tekil ile toplu arasındaki gerçek fark buydu; kalan her
şey ortaktı.

## Sınırın Sayılması

Ölçüm iki tür sayı çıkarır. Birincisi statik: her dosyanın kaç altyapı adı bildiği ve kaç
senaryo adımı taşıdığı. İkincisi koşumdan: iki düzenin aynı sözleşmeyi üretip üretmediği.

```js
// sinir-say.mjs — istemci basina ithal edilen altyapi adi, tekrar eden senaryo adimi ve cikti esitligi
import { readFileSync } from "node:fs";
import { gonderiDeposu } from "./altyapi.mjs";
import * as dogrudanA from "./dogrudan/istemci-a.mjs";
import * as dogrudanB from "./dogrudan/istemci-b.mjs";
import * as servis from "./servis/istemciler.mjs";

const ADIM = {
  arama: /gonderiDeposu\.bul\(/, dogrulama: /throw new RangeError/, indirim: /kural\.indirimEkle\(/,
  aktarma: /kural\.aktarmaEkle\(/, birim: /isBirimi\(\)/, tamamla: /\.tamamla\(\)/, sozlesme: /esleyici\.liste\(/,
};
const ALTYAPI = /\b(gonderiDeposu|isBirimi|kural|esleyici)\b/g;

function tara(dosyalar) {
  const adimlar = dosyalar.map((d) => {
    const m = readFileSync(d, "utf8");
    const bulunan = Object.keys(ADIM).filter((k) => ADIM[k].test(m));
    const ithal = new Set([...m.matchAll(ALTYAPI)].map(([a]) => a));
    console.log(`  ${d.padEnd(26)} altyapi adi = ${ithal.size}, senaryo adimi = ${bulunan.length}`);
    return bulunan;
  });
  const tumu = adimlar.flat();
  const tekrar = new Set(tumu.filter((a, i) => tumu.indexOf(a) !== i));
  console.log(`  toplam adim yazimi = ${tumu.length}, birden fazla dosyada tekrarlanan adim = ${tekrar.size}`);
}

for (const id of ["G1", "G2"]) gonderiDeposu.yeni({ id, taban: 8640, indirimler: [], aktarma: ["34"] });
const INDIRIM = { ad: "sozlesme", oran: 0.15 };

console.log("servis katmani yok");
tara(["dogrudan/istemci-a.mjs", "dogrudan/istemci-b.mjs"]);
console.log("servis katmani var");
tara(["servis/istemciler.mjs", "servis/gonderi-servisi.mjs"]);

const a = dogrudanA.indirimIste("G1", INDIRIM, "06");
const b = servis.indirimIste("G2", INDIRIM, "06");
const c = dogrudanB.topluIndirim(["G1"], INDIRIM, "35")[0];
const d = servis.topluIndirim(["G2"], INDIRIM, "35")[0];
const es = (x, y) => x.net === y.net && x.aktarma === y.aktarma;
console.log(`tek istek ciktisi esit mi = ${es(a, b)}, toplu cikti esit mi = ${es(c, d)}`);
console.log(`ornek sozlesme = ${JSON.stringify(b)}`);
```

```sh
node sinir-say.mjs
```

```
servis katmani yok
  dogrudan/istemci-a.mjs     altyapi adi = 4, senaryo adimi = 7
  dogrudan/istemci-b.mjs     altyapi adi = 4, senaryo adimi = 7
  toplam adim yazimi = 14, birden fazla dosyada tekrarlanan adim = 7
servis katmani var
  servis/istemciler.mjs      altyapi adi = 0, senaryo adimi = 0
  servis/gonderi-servisi.mjs altyapi adi = 4, senaryo adimi = 7
  toplam adim yazimi = 7, birden fazla dosyada tekrarlanan adim = 0
tek istek ciktisi esit mi = true, toplu cikti esit mi = true
ornek sozlesme = {"kimlik":"G2","net":7344,"aktarma":2}
```

Üç sayı değişti. İstemci dosyasının bildiği altyapı adı 4'ten 0'a indi: sunum tarafı artık
deponun, iş biriminin, alan kuralının ve eşleyicinin adını taşımıyor, tek bir servis adı
taşıyor. Toplam adım yazımı 14'ten 7'ye indi. Birden fazla dosyada tekrarlanan adım sayısı
7'den 0'a indi; yedi kararın hepsi tek dosyada toplandı.

Son iki satır davranışın korunduğunu gösteriyor: tek istek ve toplu koşum aynı sözleşmeyi
üretti. Sayıların büyüme biçimi de önemlidir. İstemci sayısı `n` iken servis katmanı olmayan
düzende adım yazımı `7n`, servis katmanlı düzende sabit 7'dir; kazanç istemci sayısıyla
doğrusal büyür.

## Bedel: İnce Servis

Servis katmanı 1 dosya ekler ve istemciden alan kuralına giden yolu 1 basamak uzatır. Bu bedel
karşılığını yalnız servis yönteminin **kendine ait bir kararı** olduğunda alır. Karar yoksa
yöntem çağrıyı iletmekten başka iş yapmaz.

```js
// servis/ince-servis.mjs — her yontemi tek bir cagriyi ileten servis
import { gonderiDeposu, kural } from "../altyapi.mjs";

export const inceServis = {
  bul: (id) => gonderiDeposu.bul(id),
  net: (t) => kural.net(t),
  indirimEkle: (t, i) => kural.indirimEkle(t, i),
};
```

```js
// ileten-say.mjs — servis yontemlerinin kaci yalniz cagriyi iletiyor
import { readFileSync } from "node:fs";

const YONTEM = /^\s{2}(\w+)[:(]/;
const ILETEN = /^\s{2}\w+:\s*\([^)]*\)\s*=>\s*[\w.]+\([^;]*\),$/;

for (const dosya of ["servis/gonderi-servisi.mjs", "servis/ince-servis.mjs"]) {
  const satirlar = readFileSync(dosya, "utf8").split("\n");
  const yontem = satirlar.filter((s) => YONTEM.test(s)).length;
  const ileten = satirlar.filter((s) => ILETEN.test(s)).length;
  console.log(`${dosya.padEnd(26)} yontem = ${yontem}, yalniz ileten = ${ileten}`);
}
```

```sh
node ileten-say.mjs
```

```
servis/gonderi-servisi.mjs yontem = 1, yalniz ileten = 0
servis/ince-servis.mjs     yontem = 3, yalniz ileten = 3
```

İnce serviste üç yöntemin üçü yalnız çağrıyı iletiyor. Bu düzen 1 dosya ve 3 ad ekler,
karşılığında 0 karar toplar; istemcinin bilmek zorunda olduğu ad sayısını da düşürmez, yalnız
adları değiştirir.

## Ne Zaman Uygulanmaz

Ölçüdeki kazanç iki koşula bağlıdır: istemci sayısının birden fazla olması ve senaryonun birden
çok adım içermesi.

Tek istemcili bir kitaplıkta tekrarlanan adım sayısı zaten 0'dır; servis katmanı 7'yi 7'de
bırakır ve 1 dosya ile 1 dolaylılık basamağı ödetir. Tek adımlı işlemlerde de kazanç yoktur:
bir gönderiyi kimliğiyle okuyup döndüren bir işlem için servis yöntemi ileten-yöntem sayacına
takılır.

Üçüncü sınır kalınlık kararıdır. Servis yöntemi iş kuralını kendi içine yazmaya başlarsa
kuralın uygulama noktası sayısı yeniden çoğalır — bu, İşlem Betiği ve Alan Modeli dersinde
sayılan şeydir. Servis katmanının işi sıra, sınır ve sözleşmedir; karar alan modelinde kalır.

## Özet

- Servis katmanı uygulamanın dışarıya açtığı işlem kümesini tanımlar; adım sırasını, iş
  biriminin sınırını ve dönen sözleşmeyi tek dosyada toplar.
- İki istemcili ölçümde istemci dosyasının bildiği altyapı adı 4'ten 0'a, toplam adım yazımı
  14'ten 7'ye, tekrarlanan adım sayısı 7'den 0'a indi.
- Tek istek ve toplu koşum aynı sözleşmeyi üretti; kalıp davranışı değiştirmedi, kararların
  hangi dosyada durduğunu değiştirdi.
- Kazanç istemci sayısıyla doğrusal büyür: adım yazımı servis olmadan `7n`, servisle sabit 7.
- Kendine ait kararı olmayan servis ince kalır; üç yöntemin üçü yalnız çağrıyı iletti, 1 dosya
  ve 3 ad karşılığında 0 karar toplandı.

## Sonraki Adım

Buraya kadarki altı kalıp gönderi türünün tek olduğunu varsaydı: her gönderinin ağırlığı,
tabanı, indirimleri ve rotası var. Kitaplığa ikinci bir hizmet türü girdiğinde — soğuk zincir
gönderisi, değerli eşya gönderisi, belge gönderisi — her türün kendi nitelikleri ve kendi
ücret kuralı olur. Her tür için yeni bir sınıf yazmak, kaç dosyanın düzenlendiğini artıran bir
karardır; türler bir veri kaynağından geliyorsa sınıf yazmak zaten olanaksızdır. Sonraki ders
türü çalışma zamanında bir nesne olarak modelleyen tip nesnesini ve niteliklerin türe göre
değişmesini yöneten nitelik varyantlarını kurar, yeni tür eklendiğinde düzenlenen dosya ve
eklenen satır sayısını sayar.
