---
title: 'Alt Alanlar'
source: 'https://academia.sh/tr/kurslar/alan-odakli-tasarim/alt-alanlar'
course: 'Alan Odaklı Tasarım'
language: tr
updated: '2026-08-17T18:10:41+00:00'
license: 'CC BY-SA 4.0'
---

# Alt Alanlar

Çekirdek, destekleyici ve genel alt alanların ayrılması: alt alan ile alan katmanının farkının kurulması, alt alan başına satır ve değişmez sayısından emek payının çıkarılması, değişiklik günlüğünden değişim payının hesaplanması ve genel alt alan inceltildiğinde emek ile değişim dağılımının hizalanmasının ölçülmesi.

Önceki dört ders bağlamların sınırını çizdi, aralarındaki ilişkileri adlandırdı ve üç
işbirliği biçiminin bedelini saydı. Bu kararların hepsi bir soruyu yanıtsız bıraktı: hangi
parçaya ne kadar tasarım emeği verilecek? Ölçü biriminin tanımı ile ücret kuralı aynı özenle mi
modellenir? Posta kodu biçim denetimi ile sözleşmeli indirim kuralı aynı incelikle mi
yazılır?

Alan kendi içinde eşit değildir. Bir kısmı kurumun kazandığı işin kendisidir, bir kısmı onu
ayakta tutar, bir kısmı ise her kurumda aynıdır. Bu üç kısma **alt alan** (subdomain) denir ve
ayrım bir sınıflandırma egzersizi değil, emek dağıtma kararıdır. Bu ders emeği satır ve değişmez
sayısıyla, ihtiyacı değişiklik günlüğüyle ölçer ve ikisinin oranını çıkarır.

## Alt Alan, Alan Katmanı ve Sınırlı Bağlam

Üç kavram birbirine benzer adlar taşıyor; ayrımı baştan yapmak gerekiyor.

**Alan katmanı** (Veri Erişim Katmanı ve İş Mantığı kursunda kurulmuştu) kodun düzenlenişindeki
bir yerdir: iş
kurallarının durduğu, sunum ve altyapıya bakmayan katman. **Alt alan** kodda değil, işin
kendisindedir: kurumun yaptığı işin bir bölümü. Bir alt alanın kodu genellikle birkaç katmana
dağılır — ücretlendirmenin kuralı alan katmanında, tarife tablosunun okunması altyapı
katmanındadır — ve bir alan katmanı birden çok alt alanın modelini barındırabilir.

**Sınırlı bağlam** ise çözüm tarafındadır: bir modelin geçerli olduğu alan. Alt alan problemin
bölümü, sınırlı bağlam çözümün bölümüdür. İkisinin birebir eşlenmesi iyi bir hedeftir ama
zorunlu değildir; bir bağlam iki alt alanın modelini taşıyabilir, bir alt alan iki bağlama
yayılabilir. Bu dersin ölçtüğü şey bağlam sınırı değil, alt alan başına harcanan emektir.

## Üç Tür

**Çekirdek alt alan** (core subdomain), kurumun rakiplerinden ayrıldığı yerdir. Ücretlendirme
kitaplığında ücret kuralı ile sözleşmeli indirim kuralı budur: kurum işi bu iki kuralla kazanır,
ikisi de kurumun kendi kararıdır ve dışarıda karşılığı yoktur.

**Destekleyici alt alan** (supporting subdomain), iş için gerekli ama fark yaratmayan yerdir.
Servis seviyesinden teslim penceresinin türetilmesi böyledir: bulunmazsa iş yürümez, ama kimse
onun yüzünden bu kurumu seçmez.

**Genel alt alan** (generic subdomain), her kurumda aynı olan yerdir. Posta kodu biçiminin
doğrulanması ve kuruş aritmetiği böyledir. Ayırt edici ölçüt şudur: bir başkasının çözümü
olduğu gibi alınabiliyorsa o parça genel alt alandır.

Ayrımın kararı emek dağıtmaktır: çekirdek alt alana en fazla modelleme emeği verilir, genel alt
alana en az. Bu bir tercih değil, ölçülebilir bir hizadır — ve pratikte sık ters döner.

## Kalın Modellenmiş Genel Alt Alan

Aşağıdaki iki dosya, genel alt alanın özenle modellenmiş hâlini gösteriyor: posta kodu bir değer
nesnesi olarak kurulmuş, beş değişmez taşıyor, eşitlik karşılaştırması ve metne çevirme
yöntemleri var. Para da öyle: dört değişmez, para birimi alanı, aritmetik yöntemler.

```sh
mkdir -p alan genel-kalin genel-ince
```

```js
// genel-kalin/posta-kodu.mjs — genel alt alan, kalin modellenmis: posta kodu degeri
const IL_ONEKLERI = new Set(["01", "06", "34", "35", "65", "81"]);

export class PostaKodu {
  #metin;
  constructor(metin) {
    const ihlal = PostaKodu.dogrula(metin);
    if (ihlal.length > 0) throw new RangeError(`posta kodu gecersiz: ${ihlal.join(",")}`);
    this.#metin = String(metin).trim();
  }
  static dogrula(metin) {
    const ihlal = [];
    const d = typeof metin === "string" ? metin.trim() : "";
    if (d.length === 0) ihlal.push("G1-bos");
    if (d.length !== 5) ihlal.push("G2-uzunluk");
    if (/\D/.test(d)) ihlal.push("G3-rakam");
    if (d.startsWith("00")) ihlal.push("G4-sifir-onek");
    if (d.length === 5 && !/\D/.test(d) && !IL_ONEKLERI.has(d.slice(0, 2))) ihlal.push("G5-il");
    return ihlal;
  }
  get onEk() { return this.#metin.slice(0, 2); }
  toString() { return this.#metin; }
  esitMi(oteki) { return oteki instanceof PostaKodu && oteki.toString() === this.#metin; }
}

export const onEk = (metin) => (PostaKodu.dogrula(metin).length > 0 ? null : new PostaKodu(metin).onEk);
```

```js
// genel-kalin/para-birimi.mjs — genel alt alan, kalin modellenmis: para degeri
export class Para {
  #kurus; #birim;
  constructor(kurus, birim = "TRY") {
    const ihlal = Para.dogrula(kurus, birim);
    if (ihlal.length > 0) throw new RangeError(`para gecersiz: ${ihlal.join(",")}`);
    this.#kurus = kurus; this.#birim = birim;
  }
  static dogrula(kurus, birim) {
    const ihlal = [];
    if (!Number.isInteger(kurus)) ihlal.push("G1-tam-sayi");
    if (kurus < 0) ihlal.push("G2-negatif");
    if (kurus > 1e12) ihlal.push("G3-tasma");
    if (birim !== "TRY") ihlal.push("G4-birim");
    return ihlal;
  }
  carp(oran) { return new Para(Math.round(this.#kurus * oran), this.#birim); }
  yuvarla(adim) { return new Para(Math.round(this.#kurus / adim) * adim, this.#birim); }
  get kurus() { return this.#kurus; }
}

export const carp = (kurus, oran) => new Para(kurus).carp(oran).kurus;
export const yuvarla = (kurus, adim) => new Para(kurus).yuvarla(adim).kurus;
```

İki dosyanın da kötü yazılmış olduğunu söylemek yanlış olur. İkisi de düzgün değer
nesneleridir, değişmezleri açıktır, kimlik yerine değerle karşılaştırılırlar. Sorun kalitede
değil, yerdedir.

## Çekirdek ve Destekleyici

Çekirdek alt alanın iki dosyası genel alt alanı dışarıdan alıyor: `genel` adlı bir nesne
üzerinden ön ek çıkarma, çarpma ve yuvarlama işlemlerini çağırıyorlar. Bu, iki genel sürümün
aynı kuralla sınanabilmesini sağlıyor.

```js
// alan/ucret-kurali.mjs — cekirdek alt alan: kurumun kazandigi isin kurali
const KADEMELER = [
  { ustEsasGram: 1000, ucretKurus: 4990 },
  { ustEsasGram: 5000, ucretKurus: 8490 },
  { ustEsasGram: 30000, ucretKurus: 24990 },
];
const BOLGE_CARPANI = { "01": 1.35, "06": 1.35, "34": 1, "35": 1.35, "65": 1.8, "81": 1.8 };
const ASGARI_KURUS = 3990;

export function dogrula(gonderi) {
  const ihlal = [];
  if (!(Number.isInteger(gonderi.esasGram) && gonderi.esasGram > 0)) ihlal.push("C1-esas-agirlik");
  if (gonderi.esasGram > 30000) ihlal.push("C2-kademe-disi");
  if (typeof gonderi.postaKodu !== "string") ihlal.push("C3-adres");
  return ihlal;
}

export function ucretHesapla(gonderi, genel) {
  if (dogrula(gonderi).length > 0) return null;
  const onEk = genel.onEk(gonderi.postaKodu);
  if (onEk === null) return null;
  const kademe = KADEMELER.find((k) => gonderi.esasGram <= k.ustEsasGram);
  const ham = genel.carp(kademe.ucretKurus, BOLGE_CARPANI[onEk]);
  return Math.max(genel.yuvarla(ham, 50), ASGARI_KURUS);
}
```

```js
// alan/indirim-kurali.mjs — cekirdek alt alan: sozlesmeli indirim kurali
const HACIM_KADEMELERI = [
  { aylikGonderi: 500, oran: 0.05 },
  { aylikGonderi: 2000, oran: 0.12 },
  { aylikGonderi: Infinity, oran: 0.2 },
];
const UST_SINIR = 0.3;

export function dogrula(sozlesme) {
  const ihlal = [];
  if (!Number.isInteger(sozlesme.aylikGonderi) || sozlesme.aylikGonderi < 0) ihlal.push("C1-hacim");
  if (!(sozlesme.pazarlikOrani >= 0 && sozlesme.pazarlikOrani <= UST_SINIR)) ihlal.push("C2-pazarlik");
  if (sozlesme.pazarlikOrani > 0 && sozlesme.sozlesmeNo === null) ihlal.push("C3-sozlesmesiz-pazarlik");
  return ihlal;
}

export function indirimliUcret(brutKurus, sozlesme, genel) {
  if (dogrula(sozlesme).length > 0) return null;
  const kademe = HACIM_KADEMELERI.find((k) => sozlesme.aylikGonderi <= k.aylikGonderi);
  const oran = Math.min(kademe.oran + sozlesme.pazarlikOrani, UST_SINIR);
  return genel.carp(brutKurus, 1 - oran);
}
```

```js
// alan/teslim-penceresi.mjs — destekleyici alt alan: servis seviyesinden teslim penceresi
const PENCERE_SAAT = { SS1: 24, SS2: 48, SS3: 72 };

export function dogrula(servisSeviyeKodu) {
  const ihlal = [];
  if (typeof servisSeviyeKodu !== "string") ihlal.push("D1-tip");
  if (PENCERE_SAAT[servisSeviyeKodu] === undefined) ihlal.push("D2-taninmayan-seviye");
  return ihlal;
}

export const pencereSaat = (servisSeviyeKodu) =>
  (dogrula(servisSeviyeKodu).length > 0 ? 72 : PENCERE_SAAT[servisSeviyeKodu]);
```

## Genel Alt Alanın İnceltilmesi

İnce sürüm genel alt alandan yalnız çekirdeğin gerçekten ihtiyaç duyduğu kadarını bırakıyor: ön
ek çıkarma ve iki aritmetik işlem. Değer nesnesi, eşitlik karşılaştırması ve para birimi alanı
yok; çünkü kitaplık bunları hiç kullanmıyordu.

```js
// genel-ince/posta-kodu.mjs — genel alt alan, ince: yalniz alanin ihtiyaci kadar
const IL_ONEKLERI = new Set(["01", "06", "34", "35", "65", "81"]);

export function onEk(metin) {
  const d = typeof metin === "string" ? metin.trim() : "";
  if (!/^\d{5}$/.test(d) || d.startsWith("00") || !IL_ONEKLERI.has(d.slice(0, 2))) return null;
  return d.slice(0, 2);
}
```

```js
// genel-ince/para-birimi.mjs — genel alt alan, ince: yalniz alanin ihtiyaci kadar
const gecerli = (kurus) => Number.isInteger(kurus) && kurus >= 0 && kurus <= 1e12;

export const carp = (kurus, oran) => {
  if (!gecerli(kurus)) throw new RangeError("para gecersiz");
  return Math.round(kurus * oran);
};
export const yuvarla = (kurus, adim) => Math.round(kurus / adim) * adim;
```

## Emeğin ve Değişimin Dağılımı

Emek iki göstergeyle ölçülüyor: alt alan başına satır sayısı ve tanımlanmış değişmez sayısı.
İhtiyaç, yirmi model değişikliğinin alt alanlara dağılımıyla ölçülüyor. Üçüncü sayı ikisinin
oranıdır ve biri kesin bir eşiktir: emek payı değişim payına eşitse oran 1'dir.

```js
// emek-say.mjs — alt alan basina emek payi, degisim payi ve ikisinin orani
import { readFileSync } from "node:fs";

const ALT_ALAN = {
  "alan/ucret-kurali.mjs": "cekirdek", "alan/indirim-kurali.mjs": "cekirdek",
  "alan/teslim-penceresi.mjs": "destekleyici",
  "genel-kalin/posta-kodu.mjs": "genel", "genel-kalin/para-birimi.mjs": "genel",
  "genel-ince/posta-kodu.mjs": "genel", "genel-ince/para-birimi.mjs": "genel",
};

const GUNLUK = [
  ["ucret-kurali", 6], ["indirim-kurali", 5], ["teslim-penceresi", 4],
  ["posta-kodu", 3], ["para-birimi", 2],
];
const KAVRAM_ALT_ALAN = { "ucret-kurali": "cekirdek", "indirim-kurali": "cekirdek",
  "teslim-penceresi": "destekleyici", "posta-kodu": "genel", "para-birimi": "genel" };
const TOPLAM_DEGISIKLIK = GUNLUK.reduce((t, [, n]) => t + n, 0);

const TURLER = ["cekirdek", "destekleyici", "genel"];
const yuzde = (a, b) => `${((a / b) * 100).toFixed(1).padStart(5)}%`;

for (const [ad, dosyalar] of [
  ["kalin genel", ["alan/ucret-kurali.mjs", "alan/indirim-kurali.mjs", "alan/teslim-penceresi.mjs",
                   "genel-kalin/posta-kodu.mjs", "genel-kalin/para-birimi.mjs"]],
  ["ince genel", ["alan/ucret-kurali.mjs", "alan/indirim-kurali.mjs", "alan/teslim-penceresi.mjs",
                  "genel-ince/posta-kodu.mjs", "genel-ince/para-birimi.mjs"]],
]) {
  const satir = {}, degismez = {};
  for (const t of TURLER) { satir[t] = 0; degismez[t] = 0; }
  for (const d of dosyalar) {
    const metin = readFileSync(d, "utf8");
    const t = ALT_ALAN[d];
    satir[t] += metin.trimEnd().split("\n").length;
    degismez[t] += (metin.match(/ihlal\.push/g) ?? []).length;
  }
  const toplamSatir = TURLER.reduce((s, t) => s + satir[t], 0);
  const toplamDegismez = TURLER.reduce((s, t) => s + degismez[t], 0);
  console.log(`${ad}: ${toplamSatir} satir, ${toplamDegismez} degismez, ${TOPLAM_DEGISIKLIK} degisiklik`);
  for (const t of TURLER) {
    const dgs = GUNLUK.filter(([k]) => KAVRAM_ALT_ALAN[k] === t).reduce((s, [, n]) => s + n, 0);
    const emekPayi = satir[t] / toplamSatir, degisimPayi = dgs / TOPLAM_DEGISIKLIK;
    console.log(`  ${t.padEnd(12)} satir ${String(satir[t]).padStart(3)} (${yuzde(satir[t], toplamSatir)})`
      + `  degismez ${String(degismez[t]).padStart(2)} (${yuzde(degismez[t], toplamDegismez)})`
      + `  degisim ${String(dgs).padStart(2)} (${yuzde(dgs, TOPLAM_DEGISIKLIK)})`
      + `  emek/degisim ${(emekPayi / degisimPayi).toFixed(2)}`);
  }
}
```

```sh
node emek-say.mjs
```

```
kalin genel: 108 satir, 17 degismez, 20 degisiklik
  cekirdek     satir  47 ( 43.5%)  degismez  6 ( 35.3%)  degisim 11 ( 55.0%)  emek/degisim 0.79
  destekleyici satir  12 ( 11.1%)  degismez  2 ( 11.8%)  degisim  4 ( 20.0%)  emek/degisim 0.56
  genel        satir  49 ( 45.4%)  degismez  9 ( 52.9%)  degisim  5 ( 25.0%)  emek/degisim 1.81
ince genel: 75 satir, 8 degismez, 20 degisiklik
  cekirdek     satir  47 ( 62.7%)  degismez  6 ( 75.0%)  degisim 11 ( 55.0%)  emek/degisim 1.14
  destekleyici satir  12 ( 16.0%)  degismez  2 ( 25.0%)  degisim  4 ( 20.0%)  emek/degisim 0.80
  genel        satir  16 ( 21.3%)  degismez  0 (  0.0%)  degisim  5 ( 25.0%)  emek/degisim 0.85
```

Kalın düzenin ilk satırı hizasızlığı veriyor: kodun yüzde 45,4'ü genel alt alanda, oysa
değişikliklerin yalnız yüzde 25'i oraya dokunuyor. Çekirdek alt alan tersinden aynı durumda:
değişikliklerin yüzde 55'ini alıyor, kodun yüzde 43,5'ünü tutuyor. Oranlar 1,81 ve 0,79;
aradaki fark iki kattan fazla.

Değişmez sayısı daha keskin bir hizasızlık gösteriyor. Genel alt alan dokuz değişmez tanımlamış,
çekirdek alt alan altı. Kurumun işi kazandığı iki kural için altı değişmez yazılırken, her
kurumda aynı olan posta kodu ve para için dokuz değişmez yazılmış durumda.

İnce düzende üç oran 1,14 – 0,80 – 0,85 aralığına giriyor; genel alt alanın satır payı yüzde
45,4'ten yüzde 21,3'e düşüyor. Genel alt alanın tanımladığı alan değişmezi kalmıyor: ince
sürümdeki iki denetim birer koruma cümlesidir, alanın kuralı değil. Toplam kod 108 satırdan 75
satıra iniyor ve boşalan emek çekirdeğe kalıyor.

## Çekirdek Değişmedi mi

İnceltmenin çekirdeği bozmadığı iddia edilmez, sınanır. Aynı çekirdek kuralları iki genel sürümle
de koşuyor.

```js
// kosum.mjs — genel alt alan incelestirilince cekirdegin sonuclari degisti mi
import { ucretHesapla } from "./alan/ucret-kurali.mjs";
import { indirimliUcret } from "./alan/indirim-kurali.mjs";
import * as kalinPosta from "./genel-kalin/posta-kodu.mjs";
import * as kalinPara from "./genel-kalin/para-birimi.mjs";
import * as incePosta from "./genel-ince/posta-kodu.mjs";
import * as incePara from "./genel-ince/para-birimi.mjs";

const KALIN = { onEk: kalinPosta.onEk, carp: kalinPara.carp, yuvarla: kalinPara.yuvarla };
const INCE = { onEk: incePosta.onEk, carp: incePara.carp, yuvarla: incePara.yuvarla };

const GONDERILER = [
  { esasGram: 800, postaKodu: "34710" }, { esasGram: 4000, postaKodu: "06800" },
  { esasGram: 20000, postaKodu: "65100" }, { esasGram: 20000, postaKodu: "99999" },
  { esasGram: 0, postaKodu: "34710" }, { esasGram: 40000, postaKodu: "34710" },
];
const SOZLESMELER = [
  { aylikGonderi: 1500, pazarlikOrani: 0.05, sozlesmeNo: "S-7" },
  { aylikGonderi: 100, pazarlikOrani: 0, sozlesmeNo: null },
  { aylikGonderi: 100, pazarlikOrani: 0.1, sozlesmeNo: null },
];

let ayrilan = 0;
for (const g of GONDERILER) {
  const a = ucretHesapla(g, KALIN), b = ucretHesapla(g, INCE);
  if (a !== b) ayrilan += 1;
  console.log(`${String(g.esasGram).padStart(5)} g ${g.postaKodu}  kalin ${String(a).padStart(5)}  ince ${String(b).padStart(5)}`);
}
for (const s of SOZLESMELER) {
  const a = indirimliUcret(45000, s, KALIN), b = indirimliUcret(45000, s, INCE);
  if (a !== b) ayrilan += 1;
  console.log(`hacim ${String(s.aylikGonderi).padStart(4)} pazarlik ${s.pazarlikOrani}  kalin ${String(a).padStart(5)}  ince ${String(b).padStart(5)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GONDERILER.length + SOZLESMELER.length}`);
```

```sh
node kosum.mjs
```

```
  800 g 34710  kalin  5000  ince  5000
 4000 g 06800  kalin 11450  ince 11450
20000 g 65100  kalin 45000  ince 45000
20000 g 99999  kalin  null  ince  null
    0 g 34710  kalin  null  ince  null
40000 g 34710  kalin  null  ince  null
hacim 1500 pazarlik 0.05  kalin 37350  ince 37350
hacim  100 pazarlik 0  kalin 42750  ince 42750
hacim  100 pazarlik 0.1  kalin  null  ince  null
ayrilan sonuc = 0 / 9
```

Dokuz durumda da sonuç aynı: geçerli üç ücret, tanınmayan il ön eki, sıfır ağırlık, kademe dışı
ağırlık, iki indirim hesabı ve sözleşmesiz pazarlık denemesi dâhil. Kalın sürümün beş
değişmezinin dördü hiçbir çağrıda karşılığını vermedi, çünkü çekirdek onları hiç
sınamıyordu.

## Ayrımın Sınırı

Alt alan türü sabit bir etiket değildir. Bir kurum için genel olan, başka bir kurum için çekirdek
olabilir; adres çözümleme, teslimat işini adres kalitesiyle kazanan bir kurumda genel alt alan
değildir. Ayrım, alanın kendisinden değil kurumun rekabet ettiği yerden okunur ve zamanla
değişir.

İkinci sınır ölçünün kendisindedir. Satır sayısı emeğin kaba bir göstergesidir; bir kuralın
zorluğu satır sayısıyla artmaz. Bu yüzden ölçü tek başına bir karar vermez, bir soru üretir:
neden en çok değişen parça en az modelleme taşıyor?

## Özet

- Alt alan, işin bir bölümüdür; alan katmanı kodun düzenlenişindeki bir yer, sınırlı bağlam ise
  bir modelin geçerlilik alanıdır. Bir alt alanın kodu birkaç katmana dağılır.
- Üç tür ayırt edilir: çekirdek alt alan kurumun işi kazandığı yer, destekleyici alt alan gerekli
  ama fark yaratmayan yer, genel alt alan başkasının çözümü olduğu gibi alınabilen yerdir.
- Kalın düzende kodun yüzde 45,4'ü genel alt alandaydı ama değişikliklerin yüzde 25'i oraya
  dokunuyordu; çekirdek alt alan değişikliklerin yüzde 55'ini alıp kodun yüzde 43,5'ini
  tutuyordu. Oranlar 1,81 ile 0,79.
- Değişmez sayımı hizasızlığı doğruladı: genel alt alan dokuz, çekirdek alt alan altı değişmez
  tanımlamıştı.
- Genel alt alan inceltildiğinde üç oran 1,14 – 0,80 – 0,85 aralığına girdi, toplam kod 108
  satırdan 75 satıra indi ve dokuz durumda çekirdeğin sonuçları değişmedi.
- Alt alan türü kuruma göredir ve zamanla değişir; satır sayısı emeğin kaba göstergesidir, ölçü
  karar vermez, soru üretir.

## Sonraki Adım

Bu konu modelin **nerede** geçerli olduğunu kurdu: sınır bir adın anlamının değiştiği yerden
geçiyor, bağlamlar arası ilişkiler yön ve türle adlandırılıyor, dış modelin sızması bir katmanla
durduruluyor, alt alan ayrımı hangi parçaya ne kadar emek verileceğini söylüyor. Açıkta kalan
soru bir bağlamın **içindedir**: o bağlamdaki model bir uygulamanın içine nasıl yerleşir? Bir
istek nereden girer, hangi sırayla hangi nesneye iner, işlemin sınırını kim çizer, sonuç kime
döner? Buraya kadarki bütün örneklerde bu düzen örtük kaldı; kurallar doğrudan çağrıldı. Sonraki
konu uygulama katmanının sorumluluğunu tanımlar ve ilk ders kullanım senaryolarını bu katmanın
birimi olarak alır: alan modelini çağıran, işlemi başlatıp bitiren, ama iş kuralını kendisi
taşımayan ince bir kabuk.
