---
title: 'Yayımla–Abone Ol'
source: 'https://academia.sh/tr/kurslar/mimari-bicemler/yayimla-abone-ol'
course: 'Mimari Biçemler'
language: tr
updated: '2026-08-17T18:10:36+00:00'
license: 'CC BY-SA 4.0'
---

# Yayımla–Abone Ol

Ücret kesinleştiğinde koşan üç işin doğrudan çağrıyla ve olay veriyolu üzerinden bağlanmasının karşılaştırılması: yayımcının tanıdığı modül ve ad sayısının, ithal kapanışının, dördüncü bir tüketici eklendiğinde yayımcıda düzenlenen satır sayısının ve işleyicileri bulmak için okunması gereken dosya sayısının ölçülmesi.

Boru hattı sabit ve doğrusal bir zincirdi: her kayıt her adımdan geçiyor, her adımın çıktısı bir
sonrakinin girdisi oluyordu. Kitaplıkta bu kalıba uymayan bir iş kümesi var. Bir gönderinin ücreti
kesinleştiğinde teslimat operasyonu bağlamında kayıt açılıp rota atanmalı, sözleşmeli müşterinin
hacim sayacı artmalı, hangi tarife sürümüyle fiyatlandığı arşive geçmeli. Bu üç iş birbirini
beslemiyor, kaydı dönüştürmüyor ve aralarında bir sıra yok; sayıları da zamanla değişiyor. Ücreti
kesinleştiren modül bunları kendisi çağırırsa üçünü de ithal etmek zorunda kalır.

**Yayımla–abone ol** (publish–subscribe) düzeni Önbellekleme, Kuyruklar ve Eşzamansız İşleme
kursunun Mesajlaşma konusunda kurulmuş ve ölçülmüştü; konu, abonelik, üretici ve tüketici orada
tanımlandı, teslim anlamları ile yeniden deneme de oraya aittir ve burada yeniden ele alınmaz.
Tasarım Kalıpları kursundaki Gözlemci kalıbı aynı fikri nesne düzeyinde kurar: haber veren nesne
abone listesini kendisi taşır. Buradaki biçemin ayırt edici yüzü bir dosyada görünür — abone
listesi ne yayımcıda ne tüketicidedir. Araya adlandırılmış bir **konu** (topic) ile bir **olay
veriyolu** (event bus) girer; iki taraf birbirini değil veriyolunu tanır.

Bağlandığı **kalite niteliği** sürdürülebilirliktir ve nitelik sorusu şudur: kesinleşen ücrete
tepki veren dördüncü bir iş eklendiğinde ücreti kesinleştiren dosyada kaç satır düzenlenir. Aynı
sorunun ters yüzü de ölçülür: yayımcı işleyicileri tanımadığında, "bu olayı kim işliyor" sorusunu
yanıtlamak için kaç dosya okunur.

## Üç İş

Üç iş her iki düzende **birebir aynı** dosyalardır; değişen tek şey çağrılma yollarıdır. Her biri
kendi durumunu tutar ve dışarıya bir sayaç açar, böylece iki düzenin aynı etkiyi ürettiği
sayılabilir.

```sh
mkdir -p isler dogrudan veriyolu
```

```js
// isler/rota.mjs — teslimat operasyonu: teslimat kaydi acar ve rota atar
const AGAC = { yakin: ["34"], orta: ["34", "06"], uzak: ["34", "41", "65"] };
const acilan = [];
export const rotaAta = (olay) => { acilan.push({ kod: olay.kod, rota: AGAC[olay.bolge] }); };
export const acilanKayit = () => acilan.length;
```

```js
// isler/hacim.mjs — ucretlendirme: sozlesmeli musterinin hacim sayaci
const sayac = new Map();
export const hacimArtir = (olay) => { sayac.set(olay.musteri, (sayac.get(olay.musteri) ?? 0) + 1); };
export const hacim = (musteri) => sayac.get(musteri) ?? 0;
```

```js
// isler/arsiv.mjs — kesinlesen tutari tarife surumuyle birlikte arsive yazar
const satir = [];
export const arsivle = (olay) => { satir.push(`${olay.kod} ${olay.tarifeSurumu} ${olay.net}`); };
export const arsivSatiri = () => satir.length;
```

## Doğrudan Çağrı

Birinci düzende ücreti kesinleştiren modül üç işi sırayla çağırır. Üç ithal satırı ve üç çağrı,
yayımcının bilme yükümlülüğünün tamamıdır.

```js
// dogrudan/kesinlestir.mjs — ucreti kesinlestiren modul uc isi kendisi cagirir
import { rotaAta } from "../isler/rota.mjs";
import { hacimArtir } from "../isler/hacim.mjs";
import { arsivle } from "../isler/arsiv.mjs";

export function kesinlestir(olay) {
  rotaAta(olay);
  hacimArtir(olay);
  arsivle(olay);
  return olay.kod;
}
```

## Olay Veriyolu

İkinci düzende araya bir modül girer. Veriyolu iki işlem açar: `abone` bir konuya işleyici bağlar,
`yayimla` o konuya gelen olayı bağlı bütün işleyicilere verir. Her işleyici çağrısı yalıtılır ve
sayaçla kaydedilir. Bir tüketici hata verdiğinde ne olduğu ve olay sistem sınırını geçtiğinde
neyin değiştiği bu kursun Olay Güdümlü Mimari dersinde ölçülür; buradaki veriyolu tek süreçtedir
ve yayım eşzamanlıdır.

```js
// veriyolu/veriyolu.mjs — konu adiyla yayim: iki taraf birbirini degil veriyolunu tanir
const abonelik = new Map();
let yayim = 0, teslim = 0, hata = 0;

export function abone(konu, isleyici) {
  if (abonelik.has(konu) === false) abonelik.set(konu, []);
  abonelik.get(konu).push(isleyici);
}

export function yayimla(konu, olay) {
  yayim += 1;
  for (const isleyici of abonelik.get(konu) ?? []) {
    try { isleyici(olay); teslim += 1; } catch { hata += 1; }
  }
  return (abonelik.get(konu) ?? []).length;
}

export const sayac = () => ({ yayim, teslim, hata });
```

Yayımcı bu iki işlemden yalnız birini tanır. Tanıdığı öteki şey bir dizedir: konunun adı.

```js
// veriyolu/kesinlestir.mjs — yalnizca konu adini ve olay bicimini taniyor
import { yayimla } from "./veriyolu.mjs";

export function kesinlestir(olay) {
  yayimla("ucret-kesinlesti", olay);
  return olay.kod;
}
```

Abonelikler üçüncü bir dosyada kurulur. Bu dosya bileşim kökünün işini yapar: hem veriyolunu hem
üç işi tanır, ama kendisini kimse tanımaz — yayımcı onu ithal etmez.

```js
// veriyolu/kayit.mjs — abonelikler burada kurulur; yayimci bu dosyayi tanimaz
import { abone } from "./veriyolu.mjs";
import { rotaAta } from "../isler/rota.mjs";
import { hacimArtir } from "../isler/hacim.mjs";
import { arsivle } from "../isler/arsiv.mjs";

abone("ucret-kesinlesti", rotaAta);
abone("ucret-kesinlesti", hacimArtir);
abone("ucret-kesinlesti", arsivle);
```

## Ölçüm

Betik üç olayı iki düzende koşturur ve işlerin sayaçlarındaki artışı karşılaştırır. Sonra iki
yayımcı dosyasının ithal satırlarını okuyup bilinen modül ile ad sayısını çıkarır, ithal
kapanışını hesaplar ve son bölümde "bu olayı kim işliyor" sorusunu iki düzende yanıtlamaya
çalışır: doğrudan düzende yayımcı dosyası, veriyolu düzeninde kaynak ağacındaki bütün modüller
taranır.

```js
// bag-say.mjs — iki duzenin ayni etkiyi uretmesi, yayimcinin tanidigi ad ve isleyicileri bulmak icin okunan dosya
import { readdirSync, readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { kesinlestir as dogrudan } from "./dogrudan/kesinlestir.mjs";
import { kesinlestir as veriyolu } from "./veriyolu/kesinlestir.mjs";
import { sayac } from "./veriyolu/veriyolu.mjs";
import { acilanKayit } from "./isler/rota.mjs";
import { hacim } from "./isler/hacim.mjs";
import { arsivSatiri } from "./isler/arsiv.mjs";
import "./veriyolu/kayit.mjs";

const OLAY = [
  { kod: "G-1", bolge: "orta", musteri: "MUS-1", net: 89.25, tarifeSurumu: "T2" },
  { kod: "G-2", bolge: "uzak", musteri: "MUS-2", net: 119.7, tarifeSurumu: "T2" },
  { kod: "G-3", bolge: "yakin", musteri: "MUS-1", net: 27, tarifeSurumu: "T2" },
];
const durum = () => ({ rota: acilanKayit(), arsiv: arsivSatiri(), hacim: hacim("MUS-1") });
const fark = (a, b) => `rota kaydi +${b.rota - a.rota}, arsiv satiri +${b.arsiv - a.arsiv}, MUS-1 hacim +${b.hacim - a.hacim}`;

const d0 = durum();
for (const o of OLAY) dogrudan(o);
const d1 = durum();
for (const o of OLAY) veriyolu(o);
const d2 = durum();
console.log(`dogrudan duzen: ${fark(d0, d1)}`);
console.log(`veriyolu duzen: ${fark(d1, d2)}`);
console.log(`veriyolu sayaci: ${JSON.stringify(sayac())}`);

const ITHAL = /import\s*\{([^}]*)\}\s*from\s*"(\.[^"]+)"/g;
const kapanis = (kok) => {
  const gorulen = new Set([kok]), yigit = [kok];
  while (yigit.length > 0) {
    const d = yigit.pop();
    for (const [, , y] of readFileSync(d, "utf8").matchAll(ITHAL)) {
      const h = normalize(join(dirname(d), y));
      if (gorulen.has(h) === false) { gorulen.add(h); yigit.push(h); }
    }
  }
  return gorulen.size;
};

console.log("\nyayimci                    bilinen modul  bilinen ad  ithal kapanisi");
for (const yol of ["dogrudan/kesinlestir.mjs", "veriyolu/kesinlestir.mjs"]) {
  const bag = [...readFileSync(yol, "utf8").matchAll(ITHAL)];
  const ad = bag.flatMap((m) => m[1].split(",").map((s) => s.trim()));
  console.log(
    `${yol.padEnd(27)}${String(bag.length).padStart(13)}${String(ad.length).padStart(12)}` +
      `${String(kapanis(yol)).padStart(16)}  [${ad.join(" ")}]`,
  );
}

const mjs = readdirSync(".", { recursive: true }).filter((y) => y.endsWith(".mjs") && y.includes("/"));
const dogrudanIsleyici = (readFileSync("dogrudan/kesinlestir.mjs", "utf8").match(/^ {2}\w+\(olay\);$/gm) ?? []).length;
const aboneDosyasi = mjs.filter((y) => readFileSync(y, "utf8").includes('abone("ucret-kesinlesti"'));
const veriyoluIsleyici = aboneDosyasi.reduce(
  (t, y) => t + (readFileSync(y, "utf8").match(/abone\("ucret-kesinlesti", \w+\)/g) ?? []).length, 0);
console.log(`\n"ucret-kesinlesti olayini kim isliyor" sorusu`);
console.log(`  dogrudan: 1 dosya okunur, ${dogrudanIsleyici} isleyici gorunur`);
console.log(`  veriyolu: yayimcida 0 gorunur, ${mjs.length} dosya taranir, ${aboneDosyasi.length} dosyada ${veriyoluIsleyici} isleyici bulunur`);
```

```sh
node bag-say.mjs
```

```
dogrudan duzen: rota kaydi +3, arsiv satiri +3, MUS-1 hacim +2
veriyolu duzen: rota kaydi +3, arsiv satiri +3, MUS-1 hacim +2
veriyolu sayaci: {"yayim":3,"teslim":9,"hata":0}

yayimci                    bilinen modul  bilinen ad  ithal kapanisi
dogrudan/kesinlestir.mjs               3           3               4  [rotaAta hacimArtir arsivle]
veriyolu/kesinlestir.mjs               1           1               2  [yayimla]

"ucret-kesinlesti olayini kim isliyor" sorusu
  dogrudan: 1 dosya okunur, 3 isleyici gorunur
  veriyolu: yayimcida 0 gorunur, 7 dosya taranir, 1 dosyada 3 isleyici bulunur
```

## Sayıların Okunması

İlk iki satır ölçümün geçerlilik koşuludur: üç olay iki düzende de üç rota kaydı, üç arşiv satırı
ve `MUS-1` için iki hacim artışı üretti. İki düzen aynı işi yapıyor.

Üçüncü satır yayımın karşılığını verir. Üç yayım çağrısı dokuz işleyici çağrısına dönüştü;
yayımcının yaptığı çağrı sayısı 3, gerçekleşen iş sayısı 9. Aradaki çarpan yayımcının bilmediği
sayıdır ve `hata` alanı sıfır olduğu için dokuzunun dokuzu tamamlandı.

Tablo bilme yükümlülüğünü sayar. Doğrudan düzende yayımcı üç modül ve üç ad tanıyor, ithal
kapanışı 4 dosya: yayımcıyı yüklemek üç işi de yüklemektir. Veriyolu düzeninde bir modül ve bir ad
tanınıyor, kapanış 2 dosya. Üç iş, yayımcının ithal kapanışının dışına çıktı; kapanışın içinde
kalan tek yeni şey veriyolunun kendisidir. Konu adı `"ucret-kesinlesti"` bir ithal değil bir
dizedir, yani derleme anında denetlenmez — bu, bedelin ilk yarısıdır.

Son üç satır bedelin ikinci yarısıdır. Doğrudan düzende soruyu bir dosya yanıtlıyor: üç çağrı
orada, sırasıyla okunuyor. Veriyolu düzeninde yayımcı dosyası hiçbir işleyici göstermiyor;
yanıtı bulmak için ağaçtaki yedi modülün taranması ve abonelik satırlarının aranması gerekti.
Bulundu — bir dosyada üç işleyici — ama arama maliyeti dosya sayısıyla büyür. Gevşek bağlanmanın
ölçüsü budur: yayımcının bildiği ad sayısı 3'ten 1'e inerken, işleyicileri bulmak için okunan
dosya sayısı 1'den 7'ye çıktı.

## Dördüncü Tüketici

Nitelik sorusu dördüncü bir işle sınanır: kesinleşen ücret taşıyıcıya bildirilecek.

```js
// isler/tasiyici.mjs — dorduncu is: kesinlesen ucreti tasiyiciya bildirir
const bildirim = [];
export const tasiyiciBildir = (olay) => { bildirim.push(`${olay.kod} ${olay.net}`); };
export const bildirimSayisi = () => bildirim.length;
```

Betik iki düzenin kopyasını çıkarır, dördüncü işi ikisine de bağlar, hangi dosyada kaç satırın
değiştiğini sayar ve ayrıca yayımcı dosyasında değişen satırı ayrı bir sütunda gösterir. Sonra
iki düzeni yeni ağaçta koşturarak dördüncü işin gerçekten çalıştığını doğrular.

```js
// uzanti.mjs — dorduncu tuketici eklendiginde degisen satir, yayimcida degisen satir ve teslim sayisi
import { cpSync, readFileSync, writeFileSync } from "node:fs";

const duzenle = (yol, don) => writeFileSync(yol, don(readFileSync(yol, "utf8")));
const degisenSatir = (eski, yeni) => {
  const say = (m) => m.split("\n").reduce((h, s) => h.set(s, (h.get(s) ?? 0) + 1), new Map());
  const a = say(eski), b = say(yeni);
  const eksik = (x, y) => [...x].reduce((t, [s, n]) => t + Math.max(0, n - (y.get(s) ?? 0)), 0);
  return eksik(b, a) + eksik(a, b);
};
const ARSIV = 'import { arsivle } from "../isler/arsiv.mjs";';
const TASIYICI = 'import { tasiyiciBildir } from "../isler/tasiyici.mjs";';

for (const d of ["isler", "dogrudan", "veriyolu"]) cpSync(d, `yeni/${d}`, { recursive: true });
duzenle("yeni/dogrudan/kesinlestir.mjs", (m) =>
  m.replace(ARSIV, `${ARSIV}\n${TASIYICI}`).replace("  arsivle(olay);", "  arsivle(olay);\n  tasiyiciBildir(olay);"));
duzenle("yeni/veriyolu/kayit.mjs", (m) =>
  m.replace(ARSIV, `${ARSIV}\n${TASIYICI}`)
    .replace('abone("ucret-kesinlesti", arsivle);', 'abone("ucret-kesinlesti", arsivle);\nabone("ucret-kesinlesti", tasiyiciBildir);'));

const YAYIMCI = { dogrudan: "dogrudan/kesinlestir.mjs", veriyolu: "veriyolu/kesinlestir.mjs" };
const DOSYA = {
  dogrudan: ["dogrudan/kesinlestir.mjs"],
  veriyolu: ["veriyolu/kesinlestir.mjs", "veriyolu/veriyolu.mjs", "veriyolu/kayit.mjs"],
};
console.log("duzen     duzenlenen dosya          degisen satir  yayimcida degisen satir");
for (const [duzen, dosyalar] of Object.entries(DOSYA)) {
  const satirFarki = (y) => degisenSatir(readFileSync(y, "utf8"), readFileSync(`yeni/${y}`, "utf8"));
  const degisen = dosyalar.filter((y) => satirFarki(y) > 0);
  const toplam = degisen.reduce((t, y) => t + satirFarki(y), 0);
  const yayimcida = degisen.includes(YAYIMCI[duzen]) ? satirFarki(YAYIMCI[duzen]) : 0;
  console.log(`${duzen.padEnd(10)}${degisen.join(" ").padEnd(26)}${String(toplam).padStart(13)}${String(yayimcida).padStart(25)}`);
}

const OLAY = [
  { kod: "G-4", bolge: "orta", musteri: "MUS-1", net: 89.25, tarifeSurumu: "T2" },
  { kod: "G-5", bolge: "uzak", musteri: "MUS-2", net: 119.7, tarifeSurumu: "T2" },
];
const { bildirimSayisi } = await import("./yeni/isler/tasiyici.mjs");
const { kesinlestir: dogrudan } = await import("./yeni/dogrudan/kesinlestir.mjs");
const { kesinlestir: veriyolu } = await import("./yeni/veriyolu/kesinlestir.mjs");
const { sayac } = await import("./yeni/veriyolu/veriyolu.mjs");
await import("./yeni/veriyolu/kayit.mjs");

for (const o of OLAY) dogrudan(o);
const dogrudanBildirim = bildirimSayisi();
for (const o of OLAY) veriyolu(o);
console.log(`\ndorduncu is kostu mu: dogrudan ${dogrudanBildirim}/${OLAY.length}, veriyolu ${bildirimSayisi() - dogrudanBildirim}/${OLAY.length}`);
console.log(`veriyolu sayaci: ${JSON.stringify(sayac())}, olay basina teslim = ${sayac().teslim / sayac().yayim}`);
```

```sh
node uzanti.mjs
```

```
duzen     duzenlenen dosya          degisen satir  yayimcida degisen satir
dogrudan  dogrudan/kesinlestir.mjs              2                        2
veriyolu  veriyolu/kayit.mjs                    2                        0

dorduncu is kostu mu: dogrudan 2/2, veriyolu 2/2
veriyolu sayaci: {"yayim":2,"teslim":8,"hata":0}, olay basina teslim = 4
```

Değişen satır sayısı iki düzende de 2'dir: bir ithal satırı ve bir bağlama satırı. Fark son
sütunda: doğrudan düzende bu iki satır ücreti kesinleştiren dosyanın içindedir, veriyolu düzeninde
o dosyaya dokunulmadı. Yayımcı dosyası değişmeden olay başına teslim sayısı 3'ten 4'e çıktı.

Bu, kazancın nereye yerleştiğini gösterir: değişiklik miktarı aynı kaldı, değişikliğin **yeri**
değişti. Yayımcı ücretlendirme bağlamının içindedir; `veriyolu/kayit.mjs` ise hiçbir alan kuralı
taşımayan bir bağlama dosyasıdır. On tüketicili bir düzende her yeni iş yine iki satırdır, ama
ücret hesabının durduğu dosya hiç açılmaz.

## Özet

- Yayımla–abone ol biçeminde abone listesi ne yayımcıda ne tüketicidedir; araya bir konu adı ile
  bir olay veriyolu girer ve iki taraf birbirini değil veriyolunu tanır.
- Üç olay iki düzende de aynı etkiyi üretti; üç yayım çağrısı dokuz işleyici çağrısına dönüştü ve
  yayımcı bu çarpanı bilmiyor.
- Yayımcının bildiği modül sayısı 3'ten 1'e, ad sayısı 3'ten 1'e, ithal kapanışı 4 dosyadan 2
  dosyaya indi; konu adı bir ithal değil bir dize olduğu için derleme anında denetlenmez.
- Bedel izlenebilirliktir: "bu olayı kim işliyor" sorusu doğrudan düzende 1 dosya okunarak,
  veriyolu düzeninde 7 modül taranarak yanıtlandı.
- Dördüncü tüketici iki düzende de 2 satır değiştirdi, ama doğrudan düzende bu satırlar yayımcı
  dosyasına, veriyolu düzeninde bağlama dosyasına düştü; yayımcı dosyası değişmeden olay başına
  teslim 3'ten 4'e çıktı.

## Sonraki Adım

Bu veriyolunun ölçülmemiş bir özelliği var: `yayimla` çağrısı, bağlı bütün işleyiciler bitmeden
geri dönmüyor. Dört işleyici aynı çağrı yığıtında sırayla koşuyor, dolayısıyla ücreti
kesinleştiren modülün işi en yavaş işleyicinin işi kadar sürüyor. Arşiv yazımı yavaşlarsa ücret
kesinleştirme de yavaşlar; oysa arşivin ne zaman yazıldığı ücretlendirme için önemli değildir.
İşleyiciyi araya bir depo koyarak beklemekten çıkarmak, yani olayı bir yere bırakıp dönmek
mümkün. Sonraki ders bu depoyu kurar ve ölçer: yayımcı olayı bıraktığında kaç tüketici adımı
koşmuş oluyor, tüketici üreticiden yavaş olduğunda beklemede kaç iş birikiyor, iki tüketici aynı
deponun ucunda çalıştığında sıra korunuyor mu ve aynı ileti iki kez işlendiğinde teslimat kaydı
kaç kez açılıyor.
