---
title: 'İşlem Betiği ve Alan Modeli'
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/islem-betigi-ve-alan-modeli'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:39+00:00'
license: 'CC BY-SA 4.0'
---

# İşlem Betiği ve Alan Modeli

İş mantığının iki düzenlenişi: her senaryoyu baştan sona yürüten işlem betiği ile kuralı veriyi taşıyan nesnenin üzerine koyan alan modeli, aynı kuralın kaç yerde uygulandığının sayılması, iki düzenin aynı girdilerde eşitliğinin sınanması ve alan modelinin dosya, ad ve dolaylılık bedelinin tartılması.

Davranışsal kalıplar konusu, kalıp bedelinin kalıp kazancının altına düştüğü noktayı
gösterdi. Buraya kadarki bütün kalıplar **nesne düzeyinde** çalışıyordu: bir sorumluluk
alınıyor, ayrı bir nesneye veriliyor ve bedeli dolaylılık basamağıyla ödeniyordu.

Bu konu ölçeği bir basamak yukarı çıkarır. Soru artık bir sorumluluğun hangi nesneye
verileceği değil, bir uygulamanın **katman düzeninde** iş mantığının nereye yerleştiği ve
kalıcılıkla arasındaki sınırın hangi kalıplarla kurulduğudur. Bu kalıpların çoğu Veri Erişim
Katmanı kursunda kurulmuş ve orada başarım ölçüleriyle sınanmıştı. Burada aynı kalıplar
katalog düzeyinde ele alınır: her biri hangi problemi çözer, çözümü nedir, hangi sonuçları
doğurur ve **ne zaman uygulanmaz**.

## Problem: Kural Kaç Yerde Durur

Gönderi ücretlendirme kitaplığında üç senaryo var: yeni gönderi kaydı açmak, var olan bir
gönderiye indirim eklemek, iade ücreti hesaplamak. Üçü de aynı iki kuralı kullanır:
indirim oranlarının toplamı bir tavanı aşamaz, net ücret asgari ücretin altına inemez.

Kuralın nereye yazıldığı iki düzenleme doğurur. **İşlem betiği** (transaction script) her
senaryoyu baştan sona yürüten bir yordam yazar; kural senaryonun içinde durur. **Alan
modeli** (domain model) kuralı veriyi taşıyan nesnenin üzerine koyar; senaryo yalnız sırayı
kurar.

```sh
mkdir -p betik alan
```

## İşlem Betiği

```js
// betik/senaryolar.mjs — uc senaryo, her biri kurali kendi icinde uygular
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;
const INDIRIM_TAVANI = 0.4;

const taban = (agirlik) => KADEME.find(([ust]) => agirlik <= ust)[1];
const katsayi = (postaKodu) => KATSAYI[postaKodu.slice(0, 2)] ?? 1.8;
const toplamOran = (indirimler) => indirimler.reduce((t, i) => t + i.oran, 0);

export function yeniGonderi(girdi) {
  const ham = Math.round(taban(girdi.agirlik) * katsayi(girdi.postaKodu));
  const oran = Math.min(toplamOran(girdi.indirimler), INDIRIM_TAVANI);
  return { ...girdi, ham, net: Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET) };
}

export function indirimEkle(gonderi, indirim) {
  const indirimler = [...gonderi.indirimler, indirim];
  const oran = Math.min(toplamOran(indirimler), INDIRIM_TAVANI);
  return { ...gonderi, indirimler, net: Math.max(Math.round(gonderi.ham * (1 - oran)), ASGARI_UCRET) };
}

export function iadeUcreti(gonderi) {
  const oran = Math.min(toplamOran(gonderi.indirimler), INDIRIM_TAVANI);
  return Math.round(gonderi.ham * 0.3 * (1 - oran));
}
```

Düzenin kazancı doğrudan okunabilirliğidir: bir senaryoyu anlamak için tek bir yordamı
baştan sona okumak yeterlidir, başka dosyaya gitmek gerekmez. Kaybı, kuralın senaryo sayısı
kadar tekrarlanmasıdır.

## Alan Modeli

Aynı iş, kuralı nesnenin üzerine koyarak da yazılır.

```js
// alan/gonderi.mjs — kural nesnenin uzerinde, tek yerde
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;
const INDIRIM_TAVANI = 0.4;

export class Gonderi {
  constructor(girdi) {
    this.agirlik = girdi.agirlik;
    this.postaKodu = girdi.postaKodu;
    this.indirimler = [...girdi.indirimler];
    this.ham = Math.round(this.#taban() * this.#katsayi());
  }
  #taban() { return KADEME.find(([ust]) => this.agirlik <= ust)[1]; }
  #katsayi() { return KATSAYI[this.postaKodu.slice(0, 2)] ?? 1.8; }
  indirimOrani() {
    return Math.min(this.indirimler.reduce((t, i) => t + i.oran, 0), INDIRIM_TAVANI);
  }
  net() { return Math.max(Math.round(this.ham * (1 - this.indirimOrani())), ASGARI_UCRET); }
  indirimEkle(indirim) { this.indirimler.push(indirim); return this; }
  iadeUcreti() { return Math.round(this.ham * 0.3 * (1 - this.indirimOrani())); }
}
```

```js
// alan/senaryolar.mjs — senaryolar yalniz sirayi kurar, kural nesnede kalir
import { Gonderi } from "./gonderi.mjs";

export const yeniGonderi = (girdi) => new Gonderi(girdi);
export const indirimEkle = (gonderi, indirim) => gonderi.indirimEkle(indirim);
export const iadeUcreti = (gonderi) => gonderi.iadeUcreti();
```

Senaryo dosyası üç satıra indi. İki kural artık `indirimOrani` ve `net` yöntemlerinin içinde,
her biri bir kez yazılmış durumda.

## Kural Uygulama Noktasının Sayılması

İki düzenin farkı iddia edilmez, sayılır. Ölçü **kural uygulama noktası** sayısıdır: kuralın
adının, tanımlandığı satır dışında kaç ayrı satırda kullanıldığı.

```js
// kural-say.mjs — iki duzende kural uygulama noktasi, dosya ve disa acilan ad sayisi
import { readFileSync } from "node:fs";

const DUZEN = {
  "islem betigi": ["betik/senaryolar.mjs"],
  "alan modeli": ["alan/gonderi.mjs", "alan/senaryolar.mjs"],
};
const KURAL = { "indirim tavani": /INDIRIM_TAVANI/, "asgari ucret": /ASGARI_UCRET/ };

for (const [ad, dosyalar] of Object.entries(DUZEN)) {
  let satir = 0, disaAcilan = 0;
  const nokta = Object.fromEntries(Object.keys(KURAL).map((k) => [k, 0]));
  for (const dosya of dosyalar) {
    const satirlar = readFileSync(dosya, "utf8").split("\n");
    satir += satirlar.filter((s) => s.trim() !== "").length;
    for (const s of satirlar) {
      if (/^\s*export\b/.test(s)) disaAcilan += 1;
      if (/^\s*const [A-Z_]+ =/.test(s)) continue;
      for (const [k, kalip] of Object.entries(KURAL)) if (kalip.test(s)) nokta[k] += 1;
    }
  }
  console.log(`${ad}`);
  console.log(`  dosya = ${dosyalar.length}  satir = ${satir}  disa acilan ad = ${disaAcilan}`);
  for (const [k, n] of Object.entries(nokta)) console.log(`  ${k} uygulama noktasi = ${n}`);
}
```

```sh
node kural-say.mjs
```

```
islem betigi
  dosya = 1  satir = 22  disa acilan ad = 3
  indirim tavani uygulama noktasi = 3
  asgari ucret uygulama noktasi = 2
alan modeli
  dosya = 2  satir = 26  disa acilan ad = 4
  indirim tavani uygulama noktasi = 1
  asgari ucret uygulama noktasi = 1
```

Sayılar ödünleşimi tek satıra indiriyor. İndirim tavanı işlem betiğinde 3, alan modelinde 1
noktada uygulanıyor; asgari ücret 2 noktaya karşı 1 noktada. Kuralın biçimi değişirse —
tavan sabit bir oran olmaktan çıkıp müşteri türüne göre hesaplanan bir değere dönüşürse —
işlem betiğinde 3, alan modelinde 1 satır yeniden doğrulanmak zorundadır.

Bedel tarafı aynı çıktıda duruyor: alan modeli 1 dosya yerine 2 dosya, 22 satır yerine 26
satır ve 3 dışa açılan ad yerine 4 ad tutuyor. Kurala ulaşmak için okunması gereken dosya
sayısı da 1'den 2'ye çıkmış durumda.

## İki Düzen Aynı Sonucu Veriyor mu

Bir düzenlemenin ötekinin yerine geçebileceği iddia edilmez, sınanır.

```js
// esdeger-mi.mjs — iki duzen ayni girdilerde ayni sonucu veriyor mu
import * as betik from "./betik/senaryolar.mjs";
import * as alan from "./alan/senaryolar.mjs";

const GIRDILER = [
  { agirlik: 0.6, postaKodu: "34710", indirimler: [] },
  { agirlik: 3, postaKodu: "06800", indirimler: [{ ad: "sozlesme", oran: 0.15 }] },
  { agirlik: 12, postaKodu: "65100", indirimler: [{ ad: "sozlesme", oran: 0.25 }, { ad: "hacim", oran: 0.3 }] },
  { agirlik: 0.4, postaKodu: "81600", indirimler: [{ ad: "hacim", oran: 0.5 }] },
];
const EK = { ad: "kampanya", oran: 0.1 };
const say = (n) => String(n).padStart(5);

let ayrilan = 0;
for (const girdi of GIRDILER) {
  const b = betik.yeniGonderi(girdi), a = alan.yeniGonderi(girdi);
  const ilk = [b.net, a.net()];
  const b2 = betik.indirimEkle(b, EK), a2 = alan.indirimEkle(a, EK);
  const son = [b2.net, a2.net()];
  const iade = [betik.iadeUcreti(b2), alan.iadeUcreti(a2)];
  if (ilk[0] !== ilk[1] || son[0] !== son[1] || iade[0] !== iade[1]) ayrilan += 1;
  console.log(`${String(girdi.agirlik).padStart(4)} kg ${girdi.postaKodu}  net ${say(ilk[0])}/${say(ilk[1])}  ek indirimli ${say(son[0])}/${say(son[1])}  iade ${say(iade[0])}/${say(iade[1])}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GIRDILER.length}`);
```

```sh
node esdeger-mi.mjs
```

```
 0.6 kg 34710  net  3990/ 3990  ek indirimli  3990/ 3990  iade  1053/ 1053
   3 kg 06800  net  7344/ 7344  ek indirimli  6480/ 6480  iade  1944/ 1944
  12 kg 65100  net 12744/12744  ek indirimli 12744/12744  iade  3823/ 3823
 0.4 kg 81600  net  4212/ 4212  ek indirimli  4212/ 4212  iade  1264/ 1264
ayrilan sonuc = 0 / 4
```

Dört girdinin dördünde üç sonuç da aynı. İlk satırda asgari ücret, dördüncü satırda indirim
tavanı devreye giriyor; iki düzen de aynı sınırı aynı yerde uyguluyor. Seçim bir doğruluk
seçimi değil, bir değişim maliyeti seçimidir.

Sürümler arasında görünmeyen bir ayrım var: işlem betiği her adımda yeni bir kayıt
döndürüyor, alan modeli aynı nesneyi değiştiriyor. Bu ayrım, aynı gönderinin bellekte kaç
kopyası bulunduğu sorusunu doğurur ve kimlik haritası dersinin ölçtüğü şeydir.

## Ne Zaman Uygulanmaz

Alan modelinin kazancı tekrarı kesmesinden gelir, dolayısıyla kazanç tekrar sayısıyla
büyür. Yukarıdaki ölçümde kazanç, tavan kuralında 3 noktadan 1 noktaya inmektir; bedel 1 ek
dosya, 1 ek dışa açılan ad ve 4 ek satırdır.

Tek senaryolu bir kitaplıkta aynı hesap tersine döner: uygulama noktası zaten 1'dir, kazanç
0 olur, bedel değişmez. Bir kuralı üç yerde yazmak zorunda kalmayan bir uygulamada alan
modeli, ödediği dolaylılığın karşılığını alamaz. Ayrım ölçütü senaryo sayısı da değildir —
aynı kurala dokunan senaryo sayısıdır. Beş senaryodan biri kuralı kullanıyorsa işlem betiği
hâlâ ucuzdur.

İkinci karşı durum, kuralların veriden değil dış sistemlerden türediği işlerdir. Alan modeli
nesnenin kendi verisinden karar üretir; kararın girdisi her seferinde bir dış çağrıysa nesne
yalnız veri taşır, kural yine senaryoda kalır. Bu duruma **anemik alan modeli** (anemic
domain model) adı verilir: sınıf vardır, davranış yoktur, dolayısıyla bedel ödenir ama kazanç
alınmaz.

## Özet

- İşlem betiği her senaryoyu tek yordamda yürütür ve kuralı senaryonun içine yazar; alan
  modeli kuralı veriyi taşıyan nesneye taşır, senaryoya yalnız sırayı bırakır.
- Kural uygulama noktası sayıldığında indirim tavanı işlem betiğinde 3, alan modelinde 1
  noktada, asgari ücret 2 noktaya karşı 1 noktada uygulanıyordu.
- Alan modelinin bedeli aynı ölçümde göründü: 1 dosya yerine 2 dosya, 22 satır yerine 26
  satır, 3 dışa açılan ad yerine 4 ad ve kurala ulaşmak için 2 dosya okuma.
- Dört girdinin dördünde iki düzen aynı net ücreti ve aynı iade ücretini verdi; seçim
  doğruluk değil değişim maliyeti seçimidir.
- Kazanç aynı kurala dokunan senaryo sayısıyla büyür; tek senaryoda kazanç sıfırlanır, bedel
  kalır. Davranışı olmayan sınıflar anemik alan modeli üretir.

## Sonraki Adım

Bu dersteki iki düzen de gönderiyi bellekte tuttu; hiçbir yere yazmadı. Gönderi saklanmaya
başladığında nesne ile saklanan kayıt arasında bir çevrim doğar ve bu çevrimin nereye
konulacağı ikinci bir kalıp çiftini ortaya çıkarır: çevrimi nesnenin kendi üzerine koymak mı,
ayrı bir eşleyiciye vermek mi. Sonraki ders bu iki kalıbı aynı gönderi üzerinde kurar, alan
modülünün kalıcılık hakkında bilmek zorunda olduğu ad sayısını ve ithal kapanışının boyutunu
sayar, ikinci bir saklama biçimi eklendiğinde dokunulan dosya sayısını karşılaştırır.
