---
title: 'Veri Aktarım Nesneleri'
source: 'https://academia.sh/tr/kurslar/veri-erisim-katmani/veri-aktarim-nesneleri'
course: 'Veri Erişim Katmanı ve İş Mantığı'
language: tr
updated: '2026-08-17T18:06:50+00:00'
license: 'CC BY-SA 4.0'
---

# Veri Aktarım Nesneleri

Dış sözleşmenin iç modelden ayrılması: aynı alan nesnesinden iki farklı görünüm üretilmesi, iç modele alan eklendiğinde dış sözleşmenin değişmediğinin sınanması, alan nesnesini doğrudan döndürmenin sızdırdığı alanların sayılması ve gelen yönde aşırı atama.

Önceki dersin düzeltilmiş sürümünde alan katmanı `{ sonuc, sebep }` biçiminde bir nesne
döndürdü ve sunum katmanı bunu HTTP gövdesine çevirdi. Çeviri üç alanlıydı, neredeyse
birebirdi; ara katman gereksiz bir el değiştirme gibi görünüyordu.

Kayıt büyüdüğünde soru keskinleşir. Ödünç kaydının dışarıya dönen biçimi ile alan modelinin
kendisi aynı şey midir? Alan modeline yeni bir alan eklendiğinde istemcinin gördüğü gövde
değişmeli midir? Bu dersin konusu, iki tarafın arasına konan **veri aktarım nesnesi** (data
transfer object): dışarıyla konuşmak için üretilen, alan davranışı taşımayan düz kayıt.

## Aynı Kayıt, İki Sözleşme

Ödünç kaydının alan modeli veriyi ve o veriden türeyen kararları taşır. Aşağıdaki sınıf on
bir alan tutar; ikisi türetilmiş bilgi üretir. Dersin dosyaları iki dizine yerleşir.

```sh
mkdir -p alan sunum
```

```js
// alan/odunc.mjs — odunc kaydinin alan modeli
export class Odunc {
  constructor(v) {
    this.oduncId = v.oduncId;
    this.kitapId = v.kitapId;
    this.kitapBaslik = v.kitapBaslik;
    this.uyeId = v.uyeId;
    this.uyeAd = v.uyeAd;
    this.uyeEposta = v.uyeEposta;
    this.alisTarihi = v.alisTarihi;
    this.sonTeslimTarihi = v.sonTeslimTarihi;
    this.iadeTarihi = v.iadeTarihi;
    this.personelNotu = v.personelNotu;
    this.kayitSurumu = v.kayitSurumu;
  }
  get acikMi() { return this.iadeTarihi === null; }
  gecikmeGunu(bugun) {
    const bitis = this.iadeTarihi ?? bugun;
    const fark = (Date.parse(bitis) - Date.parse(this.sonTeslimTarihi)) / 86400000;
    return Math.max(0, Math.round(fark));
  }
  durum(bugun) {
    if (!this.acikMi) return "iade_edildi";
    return this.gecikmeGunu(bugun) > 0 ? "gecikmis" : "acik";
  }
}
```

Üç alan dışarıya ait değildir. `uyeEposta` kişisel veridir ve yalnız belirli rollere
gösterilir; `personelNotu` iç yazışmadır; `kayitSurumu` İyimser ve Kötümser Kilitleme
dersindeki sürüm sütununun kendisidir, yani bir kalıcılık ayrıntısıdır.

Dışarısı ise tek bir biçim istemez. Liste ekranı satır başına dört alanla yetinir; ayrıntı
ekranı ilişkileri de görmek ister.

```js
// sunum/gorunumler.mjs — ayni alan nesnesinden iki farkli dis sozlesme
export const listeGorunumu = (o, bugun) => ({
  oduncId: o.oduncId,
  kitap: o.kitapBaslik,
  sonTeslimTarihi: o.sonTeslimTarihi,
  durum: o.durum(bugun),
});

export const ayrintiGorunumu = (o, bugun) => ({
  oduncId: o.oduncId,
  kitap: { kitapId: o.kitapId, baslik: o.kitapBaslik },
  uye: { uyeId: o.uyeId, ad: o.uyeAd },
  alisTarihi: o.alisTarihi,
  sonTeslimTarihi: o.sonTeslimTarihi,
  iadeTarihi: o.iadeTarihi,
  durum: o.durum(bugun),
  gecikmeGunu: o.gecikmeGunu(bugun),
});
```

Eşleme iki iş yapıyor: iç alanları dışarıda bırakıyor ve türetilmiş alanları ekliyor.
İkincisi çoğu zaman gözden kaçar. `durum` ile `gecikmeGunu` alan modelinde saklanan değerler
değil, hesaplanan değerlerdir; istemcinin bunları kendisi hesaplaması, iş kuralının
istemciye kopyalanması demektir.

```js
// gorunum.mjs — tek alan nesnesi, iki dis sozlesme
import { Odunc } from "./alan/odunc.mjs";
import { listeGorunumu, ayrintiGorunumu } from "./sunum/gorunumler.mjs";

const BUGUN = "2025-06-20";
const kayit = new Odunc({
  oduncId: 7, kitapId: 5, kitapBaslik: "Körlük", uyeId: 2, uyeAd: "Ayşe Demir",
  uyeEposta: "ayse@ornek.test", alisTarihi: "2025-06-01", sonTeslimTarihi: "2025-06-15",
  iadeTarihi: null, personelNotu: "telefonla arandi", kayitSurumu: 3,
});

console.log("liste  :", JSON.stringify(listeGorunumu(kayit, BUGUN)));
console.log("ayrinti:", JSON.stringify(ayrintiGorunumu(kayit, BUGUN)));
```

```sh
node gorunum.mjs
```

```
liste  : {"oduncId":7,"kitap":"Körlük","sonTeslimTarihi":"2025-06-15","durum":"gecikmis"}
ayrinti: {"oduncId":7,"kitap":{"kitapId":5,"baslik":"Körlük"},"uye":{"uyeId":2,"ad":"Ayşe Demir"},"alisTarihi":"2025-06-01","sonTeslimTarihi":"2025-06-15","iadeTarihi":null,"durum":"gecikmis","gecikmeGunu":5}
```

Aynı nesne, iki gövde. Liste gövdesinde ilişkiler düzleştirildi, ayrıntı gövdesinde
gruplandı. Bu bir biçim tercihi değil, iki farklı sözleşmedir: liste ekranı değişse ayrıntı
sözleşmesi etkilenmez.

## Sözleşmenin İç Değişimden Yalıtılması

Ayrımın asıl gerekçesi buradadır. Alan modeline yeni bir alan eklendiğinde ne olur?
Aşağıdaki dosya, aynı modelin iki iç alan eklendikten sonraki hâlidir.

```js
// alan/odunc-2.mjs — ayni modelin iki ic alan eklendikten sonraki hali
export class Odunc {
  constructor(v) {
    this.oduncId = v.oduncId;
    this.kitapId = v.kitapId;
    this.kitapBaslik = v.kitapBaslik;
    this.uyeId = v.uyeId;
    this.uyeAd = v.uyeAd;
    this.uyeEposta = v.uyeEposta;
    this.alisTarihi = v.alisTarihi;
    this.sonTeslimTarihi = v.sonTeslimTarihi;
    this.iadeTarihi = v.iadeTarihi;
    this.personelNotu = v.personelNotu;
    this.kayitSurumu = v.kayitSurumu;
    this.yenilemeSayisi = v.yenilemeSayisi ?? 0;        // yeni
    this.sonHatirlatmaTarihi = v.sonHatirlatmaTarihi ?? null;  // yeni
  }
  get acikMi() { return this.iadeTarihi === null; }
  gecikmeGunu(bugun) {
    const bitis = this.iadeTarihi ?? bugun;
    const fark = (Date.parse(bitis) - Date.parse(this.sonTeslimTarihi)) / 86400000;
    return Math.max(0, Math.round(fark));
  }
  durum(bugun) {
    if (!this.acikMi) return "iade_edildi";
    return this.gecikmeGunu(bugun) > 0 ? "gecikmis" : "acik";
  }
}
```

Sınama, iki sürümün ürettiği gövdeleri karşılaştırır.

```js
// gorunum.test.mjs — dis sozlesmenin ic model degisiminden etkilenmedigini sinar
import { test } from "node:test";
import assert from "node:assert/strict";
import { Odunc as Odunc1 } from "./alan/odunc.mjs";
import { Odunc as Odunc2 } from "./alan/odunc-2.mjs";
import { listeGorunumu, ayrintiGorunumu } from "./sunum/gorunumler.mjs";

const BUGUN = "2025-06-20";
const TEMEL = {
  oduncId: 7, kitapId: 5, kitapBaslik: "Körlük", uyeId: 2, uyeAd: "Ayşe Demir",
  uyeEposta: "ayse@ornek.test", alisTarihi: "2025-06-01", sonTeslimTarihi: "2025-06-15",
  iadeTarihi: null, personelNotu: "telefonla arandi", kayitSurumu: 3,
};
// Surum 2 ayni kayda iki ic alan ekler.
const TEMEL2 = { ...TEMEL, yenilemeSayisi: 2, sonHatirlatmaTarihi: "2025-06-18" };

const LISTE_SOZLESMESI = ["oduncId", "kitap", "sonTeslimTarihi", "durum"];
const AYRINTI_SOZLESMESI = ["oduncId", "kitap", "uye", "alisTarihi", "sonTeslimTarihi",
                            "iadeTarihi", "durum", "gecikmeGunu"];
const IC_ALANLAR = ["uyeEposta", "personelNotu", "kayitSurumu",
                    "yenilemeSayisi", "sonHatirlatmaTarihi"];

test("liste gorunumu sozlesmedeki alanlari uretir", () => {
  assert.deepEqual(Object.keys(listeGorunumu(new Odunc1(TEMEL), BUGUN)), LISTE_SOZLESMESI);
});

test("ayrinti gorunumu sozlesmedeki alanlari uretir", () => {
  assert.deepEqual(Object.keys(ayrintiGorunumu(new Odunc1(TEMEL), BUGUN)), AYRINTI_SOZLESMESI);
});

test("ic alanlar hicbir gorunumde gecmiyor", () => {
  const metin = JSON.stringify(listeGorunumu(new Odunc2(TEMEL2), BUGUN)) +
                JSON.stringify(ayrintiGorunumu(new Odunc2(TEMEL2), BUGUN));
  for (const alan of IC_ALANLAR) assert.equal(metin.includes(alan), false, alan);
});

test("ic modele alan eklenince liste gorunumu birebir ayni kalir", () => {
  assert.deepEqual(listeGorunumu(new Odunc2(TEMEL2), BUGUN),
                   listeGorunumu(new Odunc1(TEMEL), BUGUN));
});

test("ic modele alan eklenince ayrinti gorunumu birebir ayni kalir", () => {
  assert.deepEqual(ayrintiGorunumu(new Odunc2(TEMEL2), BUGUN),
                   ayrintiGorunumu(new Odunc1(TEMEL), BUGUN));
});
```

```sh
node --test gorunum.test.mjs
```

```
✔ liste gorunumu sozlesmedeki alanlari uretir (0.556ms)
✔ ayrinti gorunumu sozlesmedeki alanlari uretir (0.068083ms)
✔ ic alanlar hicbir gorunumde gecmiyor (1.237958ms)
✔ ic modele alan eklenince liste gorunumu birebir ayni kalir (0.062917ms)
✔ ic modele alan eklenince ayrinti gorunumu birebir ayni kalir (0.064041ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 35.5965
```

Parantez içindeki süreler her çalıştırmada değişir; anlamlı olan `pass 5` satırıdır. İki
sürüm arasında dış gövdeler `deepEqual` ile birebir eşit çıktı. İç model değişti, dış
sözleşme değişmedi. Bu, Web API Tasarımı kursundaki geriye dönük uyumluluk kuralının
uygulama içindeki karşılığıdır: sözleşmenin değişmesi bir karar olmalıdır, bir yan etki
değil.

## Doğrudan Dönmenin Bedeli

Ters yön ölçülebilir. Uç nokta alan nesnesini doğrudan döndürürse dış gövde ile sözleşme
arasında iki yönlü bir fark oluşur.

```js
// sizinti-say.mjs — alan nesnesi dogrudan donduruldugunde dis sozlesme ne oluyor
import { Odunc as Odunc1 } from "./alan/odunc.mjs";
import { Odunc as Odunc2 } from "./alan/odunc-2.mjs";
import { listeGorunumu } from "./sunum/gorunumler.mjs";

const BUGUN = "2025-06-20";
const TEMEL = {
  oduncId: 7, kitapId: 5, kitapBaslik: "Körlük", uyeId: 2, uyeAd: "Ayşe Demir",
  uyeEposta: "ayse@ornek.test", alisTarihi: "2025-06-01", sonTeslimTarihi: "2025-06-15",
  iadeTarihi: null, personelNotu: "telefonla arandi", kayitSurumu: 3,
};
const TEMEL2 = { ...TEMEL, yenilemeSayisi: 2, sonHatirlatmaTarihi: "2025-06-18" };

// Istemcinin gordugu gövde, nesnenin JSON'a cevrilmis halidir.
const disAlanlar = (nesne) => Object.keys(JSON.parse(JSON.stringify(nesne))).sort();
const fark = (a, b) => a.filter((x) => !b.includes(x));

for (const [ad, kayit] of [["surum 1", new Odunc1(TEMEL)], ["surum 2", new Odunc2(TEMEL2)]]) {
  const eslemeli = disAlanlar(listeGorunumu(kayit, BUGUN));
  const dogrudan = disAlanlar(kayit);
  console.log(`${ad}  eslemeli=${eslemeli.length} alan   dogrudan=${dogrudan.length} alan`);
  console.log(`  sizan  (${fark(dogrudan, eslemeli).length}): ${fark(dogrudan, eslemeli).join(", ")}`);
  console.log(`  eksilen (${fark(eslemeli, dogrudan).length}): ${fark(eslemeli, dogrudan).join(", ")}`);
}
```

```sh
node sizinti-say.mjs
```

```
surum 1  eslemeli=4 alan   dogrudan=11 alan
  sizan  (9): alisTarihi, iadeTarihi, kayitSurumu, kitapBaslik, kitapId, personelNotu, uyeAd, uyeEposta, uyeId
  eksilen (2): durum, kitap
surum 2  eslemeli=4 alan   dogrudan=13 alan
  sizan  (11): alisTarihi, iadeTarihi, kayitSurumu, kitapBaslik, kitapId, personelNotu, sonHatirlatmaTarihi, uyeAd, uyeEposta, uyeId, yenilemeSayisi
  eksilen (2): durum, kitap
```

Üç bulgu var. Birincisi sızıntının kendisi: liste sözleşmesinde dört alan varken doğrudan
dönen gövdede on bir alan çıktı, dokuzu sözleşmede yok. İçlerinden `uyeEposta` kişisel veri,
`personelNotu` iç yazışma, `kayitSurumu` kalıcılık ayrıntısıdır.

İkincisi büyümenin kendiliğinden olması: iki iç alan eklenince doğrudan dönen gövde on
üç alana çıktı, eşlemeli gövde dört alanda kaldı. Doğrudan dönen uçta her model değişikliği
bir sözleşme değişikliğidir ve bunun için kimse karar vermemiştir.

Üçüncüsü ters yöndeki kayıptır. Doğrudan dönen gövdede `durum` ve `kitap` alanları yok,
çünkü bunlar saklanan değil türetilen alanlardır. Aşırı Veri Çekme dersinde ölçülen fazla
bayt burada ikinci bir maliyet kazanıyor: gövde hem gereğinden büyük hem de istemcinin
ihtiyaç duyduğu bilgi bakımından eksik.

## Gelen Yön

Aynı sınır ters yönde de gereklidir. İstek gövdesi doğrudan iç modele atanırsa istemci
yazmaması gereken alanları belirler. Aşağıdaki karşılaştırma bunu sayıyor.

```js
// gelen-yon.mjs — istek govdesi ic modele dogrudan atandiginda istemci neyi belirliyor
const VARSAYILAN = {
  oduncId: null, kitapId: null, kitapBaslik: null, uyeId: null, uyeAd: null,
  uyeEposta: null, alisTarihi: null, sonTeslimTarihi: null, iadeTarihi: null,
  personelNotu: null, kayitSurumu: 1,
};

// Istemcinin yolladigi govde: uc mesru alan ve uc fazlalik.
const ISTEK = {
  kitapId: 5, uyeId: 2, alisTarihi: "2025-06-01",
  oduncId: 1, kayitSurumu: 99, personelNotu: "kaydi kapali gorun",
};

const korumasiz = (istek) => ({ ...VARSAYILAN, ...istek });
const esleyerek = (istek) => ({
  ...VARSAYILAN,
  kitapId: istek.kitapId, uyeId: istek.uyeId, alisTarihi: istek.alisTarihi,
});

const istemcininBelirledigi = (nesne) =>
  Object.keys(nesne).filter((a) => JSON.stringify(nesne[a]) !== JSON.stringify(VARSAYILAN[a]));

for (const [ad, kur] of [["korumasiz", korumasiz], ["esleyerek", esleyerek]]) {
  const alanlar = istemcininBelirledigi(kur(ISTEK));
  console.log(`${ad.padEnd(10)} istemcinin belirledigi alan = ${alanlar.length}: ${alanlar.join(", ")}`);
}
```

```sh
node gelen-yon.mjs
```

```
korumasiz  istemcinin belirledigi alan = 6: oduncId, kitapId, uyeId, alisTarihi, personelNotu, kayitSurumu
esleyerek  istemcinin belirledigi alan = 3: kitapId, uyeId, alisTarihi
```

Korumasız sürümde istemci kaydın kimliğini, sürüm sayacını ve personel notunu belirledi.
Sürüm sayacının istemciden gelmesi iyimser kilitlemeyi işlevsiz kılar: çatışma denetimi,
sunucunun bildiği sürüm numarasına dayanır. Bu davranışa **aşırı atama** (mass assignment)
denir ve gövdeye alan eklendikçe sessizce genişler.

Eşlemeli sürümde istek gövdesi bir veri aktarım nesnesidir; adı geçmeyen alan iç modele
ulaşamaz. Sorgu Nesneleri ve Belirtimler dersindeki tanıtıcı izin listesiyle aynı ilkedir:
dışarıdan gelen adın iç yapıya çevrilmesi sabit bir listeye dayanır.

## Eşlemenin Bedeli ve Sınırı

Ayrım bedelsiz değildir. Her görünüm ek kod, ek sınama ve model değiştiğinde ek bakım
demektir. Alan sayısı arttıkça eşleme işlevleri de uzar ve bir kısmı alan adını olduğu gibi
kopyalar.

Bedelin karşılığı üç durumda net olarak alınır: dış sözleşmenin iç modelden farklı bir
hızda değişmesi gerekiyorsa, iç modelde dışarıya gösterilmeyecek alan varsa ve aynı kayıttan
birden çok görünüm üretiliyorsa. Kimlik Doğrulama ve Yetkilendirme kursundaki rol ayrımı
üçüncü maddeyi doğrudan besler: aynı ödünç kaydı üyeye, personele ve rapor arayüzüne farklı
alan kümeleriyle görünür; bu farkı taşıyacak yer görünüm işlevleridir.

Karşı durum da vardır. İç modeli olmayan, tabloyu okuyup listeleyen bir yönetim ekranında
ayrı bir aktarım nesnesi katmanı yalnızca kopya üretir. Ölçüt şudur: **eşleme işlevi hiçbir
alanı gizlemiyor, hiçbir alan türetmiyor ve hiçbir adı değiştirmiyorsa** o eşleme henüz bir
sözleşme taşımıyordur.

## Özet

- Veri aktarım nesnesi dışarıyla konuşmak için üretilen düz kayıttır; alan modeli ise veriyi
  ve o veriden türeyen kararları taşır.
- Aynı alan nesnesinden liste ve ayrıntı olmak üzere iki ayrı sözleşme üretildi; eşleme hem
  iç alanları gizledi hem türetilmiş alanları ekledi.
- İç modele iki alan eklendiğinde iki görünüm de birebir aynı gövdeyi üretti; beş sınamanın
  tamamı geçti.
- Alan nesnesi doğrudan döndürüldüğünde liste sözleşmesine göre dokuz alan sızdı, iki
  türetilmiş alan eksildi; model büyüyünce sızan alan sayısı on bire çıktı.
- Gelen yönde istek gövdesinin iç modele doğrudan atanması istemcinin altı alanı
  belirlemesine yol açtı; adları sayılan bir eşlemede bu sayı üçe indi.

## Sonraki Adım

Bu dersin gelen yön örneğinde istek gövdesindeki alanlar hiç denetlenmeden kabul edildi:
`kitapId` bir sayı mı, `alisTarihi` geçerli bir tarih mi, üye gerçekten var mı, o üye şu
anda ödünç almaya uygun mu? Bu soruların hepsi "doğrulama" başlığı altında toplanır ama
aynı yere ait değildir. Biri gövdenin biçimiyle ilgilidir ve veriye bakmadan yanıtlanır;
öteki kurumun kuralıdır ve veritabanına bakmadan yanıtlanamaz. Sonraki ders aynı hatalı
girdiyi iki katmanda çalıştırır, ürettikleri yanıtların farkını gösterir ve alan kuralının
neden girdi doğrulamasına indirgenemediğini ortaya koyar.
