Ders 16 / 19
Bağlantı Noktaları ve Bağdaştırıcılar
Altıgen mimarinin bir düzenleniş olarak sunulması: alanın dışa açıldığı yerlerin adlandırılmış bağlantı noktalarına dönüştürülmesi, alan modülünün ithal kapanışının ve dış dünyaya giden doğrudan bağ sayısının iki düzende sayılması, aynı alan dosyasının iki bağdaştırıcı takımıyla koşturulması ve karşılanmayan noktanın koşum anında bildirilmesi.
İçindekiler
Önceki dersin senaryosu kararları toplama köküne indirdi, ama adımlarından biri hâlâ bir dış dünya adı taşıyordu. Varış kaydının gerçek hâli tek bir depoyla da yetinmez: taşıyıcının kendi nokta kodu okunur, bir varış numarası üretilir, aktarma merkezine duyuru gönderilir. Bu üç ad alan modülünün içine doğrudan bir bağ olarak girerse alan modeli dışarıyı bilmeye başlar.
Bu dersin ölçtüğü şey o bağların sayısıdır. Ölçüyü kurmak için önce dışarının ne olduğunu adlandırmak gerekir. Bağlantı noktası (port) alanın dışa açıldığı bir yerin adı ve beklediği işlem kümesidir; bağdaştırıcı (adapter) o kümeyi karşılayan gerçekleştirimdir. Aynı noktayı birden fazla bağdaştırıcı karşılayabilir. Bu ad, Bilgisayar Ağları’ndaki taşıma katmanı bağlantı noktasıyla aynı sözcüktür ama akraba değildir: orada bir sayı, burada bir işlem kümesi sözleşmesidir.
Yeni Olan Ne
Sınırın nereye çizileceği, kararlı kural ile değişken ayrıntının ayrımı ve bağımlılık okunun yönü Tasarım İlkeleri kursundaki Politika ve Ayrıntı Ayrımı ile Sınırların Çizilmesi derslerinde, katmanların sorumlulukları Veri Erişim Katmanı ve İş Mantığı kursundaki Katman Sorumlulukları dersinde kurulmuş ve ölçülmüştü. Bağdaştırıcı kalıbının kendisi de Tasarım Kalıpları kursunda ele alındı. Burada hiçbiri yeniden kurulmuyor.
Yeni olan tek şey bir düzenlemedir: aynı ilkeler, alanı merkeze koyup dışa açılan her yeri adlandırılmış bir nokta hâline getirecek biçimde yerleştirildiğinde ortaya çıkan yerleşim altıgen mimari (hexagonal architecture) adını alır. Altı sayısının bir anlamı yoktur; şeklin yalnız iki yüzü olmadığını, yani girdi ve çıktı diye iki taraf bulunmadığını söylemek için seçilmiştir. Nokta sayısı üç de olabilir, dokuz da.
Dışarısı
Üç dış modül, alanın konuşmak zorunda olduğu üç ayrı dünyayı temsil eder.
mkdir -p dis kapali/alan kapali/uygulama
// dis/kayit-tablosu.mjs — teslimat kayitlarini tutan dis tablo const TABLO = new Map(); export const kayitTablosu = { oku: (id) => structuredClone(TABLO.get(id) ?? null), yaz: (kayit) => { TABLO.set(kayit.id, structuredClone(kayit)); }, tohum: (id) => { TABLO.set(id, { id, durum: "kabul-edildi", rota: ["34", "06", "35"], gecilen: [] }); }, };
// dis/takip-servisi.mjs — tasiyicinin nokta kodlarini ve varis numarasini veren dis sistem const KOD = { "34": "IST-A1", "06": "ANK-T2", "35": "IZM-D7" }; let sayac = 0; export const takipServisi = { nokta: (n) => KOD[n] ?? "BILINMEYEN", numara: () => `V${String((sayac += 1)).padStart(4, "0")}`, };
// dis/bildirim-kanali.mjs — aktarma merkezine duyuru gonderen dis kanal const GIDEN = []; export const bildirimKanali = { gonder: (m) => GIDEN.push(m), sayi: () => GIDEN.length };
Bağı Doğrudan Kuran Düzen
İlk düzende toplama kökü üç modülü kendisi ithal eder. Kararlar yine onun içindedir; değişen tek şey dışarıyla nasıl konuştuğudur.
// kapali/alan/teslimat.mjs — toplama koku uc dis modulu dogrudan ithal ediyor import { kayitTablosu } from "../../dis/kayit-tablosu.mjs"; import { takipServisi } from "../../dis/takip-servisi.mjs"; import { bildirimKanali } from "../../dis/bildirim-kanali.mjs"; export class Teslimat { #kayit; static yukle(id) { const kayit = kayitTablosu.oku(id); if (kayit === null) throw new RangeError(`teslimat yok: ${id}`); return new Teslimat(kayit); } constructor(kayit) { this.#kayit = kayit; } varisKaydet(nokta) { const k = this.#kayit; if (k.durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez"); if (!k.rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`); const sira = k.rota.indexOf(nokta); if (sira !== k.gecilen.length) throw new Error("rota sirasi bozuk"); k.gecilen.push(nokta); k.durum = sira === k.rota.length - 1 ? "dagitimda" : "aktarmada"; const numara = takipServisi.numara(), kod = takipServisi.nokta(nokta); kayitTablosu.yaz(k); bildirimKanali.gonder({ numara, id: k.id, kod, durum: k.durum }); return { kimlik: k.id, numara, kod, durum: k.durum, gecilen: k.gecilen.length }; } }
// kapali/uygulama/varis-kaydet.mjs — senaryonun disariya verecek bir seyi yok import { Teslimat } from "../alan/teslimat.mjs"; export const varisKaydet = (id, nokta) => Teslimat.yukle(id).varisKaydet(nokta);
Senaryo bir satıra indi, çünkü verecek bir şeyi kalmadı. Bağların hepsi bir basamak aşağıda kuruldu.
Noktaların Adlandırılması
İkinci düzende alan tarafı beklediği adları bir dosyada yazılı hâle getirir. Bu dosya alanın içindedir: sözleşmeyi kararlı taraf yazar, karşılayan taraf ona uyar.
mkdir -p acik/alan acik/uygulama acik/bagdastiricilar
// acik/alan/baglanti-noktalari.mjs — alanin disa acildigi uc nokta ve bekledigi bes ad export const BAGLANTI_NOKTALARI = { kayitlar: ["oku", "yaz"], takip: ["noktaKodu", "varisNumarasi"], duyurular: ["gonder"], }; export function cevreyiDogrula(cevre) { const eksik = []; for (const [nokta, adlar] of Object.entries(BAGLANTI_NOKTALARI)) { for (const ad of adlar) if (typeof cevre?.[nokta]?.[ad] !== "function") eksik.push(`${nokta}.${ad}`); } if (eksik.length > 0) throw new TypeError(`karsilanmayan baglanti noktasi: ${eksik.join(", ")}`); }
// acik/alan/teslimat.mjs — ayni bes karar; dis modul adi yok, cevre parametre olarak geliyor import { cevreyiDogrula } from "./baglanti-noktalari.mjs"; export class Teslimat { #kayit; #cevre; static yukle(id, cevre) { cevreyiDogrula(cevre); const kayit = cevre.kayitlar.oku(id); if (kayit === null) throw new RangeError(`teslimat yok: ${id}`); return new Teslimat(kayit, cevre); } constructor(kayit, cevre) { this.#kayit = kayit; this.#cevre = cevre; } varisKaydet(nokta) { const k = this.#kayit, c = this.#cevre; if (k.durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez"); if (!k.rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`); const sira = k.rota.indexOf(nokta); if (sira !== k.gecilen.length) throw new Error("rota sirasi bozuk"); k.gecilen.push(nokta); k.durum = sira === k.rota.length - 1 ? "dagitimda" : "aktarmada"; const numara = c.takip.varisNumarasi(), kod = c.takip.noktaKodu(nokta); c.kayitlar.yaz(k); c.duyurular.gonder({ numara, id: k.id, kod, durum: k.durum }); return { kimlik: k.id, numara, kod, durum: k.durum, gecilen: k.gecilen.length }; } }
// acik/uygulama/varis-kaydet.mjs — cevreyi disaridan alip alan modeline veren senaryo import { Teslimat } from "../alan/teslimat.mjs"; export const varisKaydet = (id, nokta, cevre) => Teslimat.yukle(id, cevre).varisKaydet(nokta);
İki Bağdaştırıcı Takımı
Aynı üç nokta iki ayrı biçimde karşılanır. Biri dış modüllere bağlanır, öbürü bellek içinde kalır.
// acik/bagdastiricilar/uretim.mjs — uc noktayi dis modullere baglayan bagdastiricilar import { kayitTablosu } from "../../dis/kayit-tablosu.mjs"; import { takipServisi } from "../../dis/takip-servisi.mjs"; import { bildirimKanali } from "../../dis/bildirim-kanali.mjs"; export const uretimCevresi = { kayitlar: { oku: (id) => kayitTablosu.oku(id), yaz: (k) => kayitTablosu.yaz(k) }, takip: { noktaKodu: (n) => takipServisi.nokta(n), varisNumarasi: () => takipServisi.numara() }, duyurular: { gonder: (m) => bildirimKanali.gonder(m) }, };
// acik/bagdastiricilar/sinama.mjs — ayni uc noktayi bellek icinde karsilayan bagdastiricilar const TABLO = new Map(); let sayac = 0; export const GIDEN = []; export const sinamaCevresi = { kayitlar: { oku: (id) => structuredClone(TABLO.get(id) ?? null), yaz: (k) => { TABLO.set(k.id, structuredClone(k)); }, }, takip: { noktaKodu: (n) => `SINAMA-${n}`, varisNumarasi: () => `S${String((sayac += 1)).padStart(4, "0")}` }, duyurular: { gonder: (m) => GIDEN.push(m) }, }; export const tohum = (id) => TABLO.set(id, { id, durum: "kabul-edildi", rota: ["34", "06", "35"], gecilen: [] });
Ölçüm
Ölçüm alan modülünün kökünden başlayıp ithal bağlarını yürür: kapanışa kaç dosya giriyor,
kaç bağ dis/ dizinine iniyor. Ardından aynı alan dosyası iki bağdaştırıcı takımıyla
koşturulur ve karşılanmayan bir nokta ile ne olduğuna bakılır.
// bag-say.mjs — alan modulunun ithal kapanisi, dogrudan bag sayisi ve iki bagdastirici takimiyla kosum import { readFileSync } from "node:fs"; import { dirname, join, normalize } from "node:path"; import { kayitTablosu } from "./dis/kayit-tablosu.mjs"; import { bildirimKanali } from "./dis/bildirim-kanali.mjs"; import { BAGLANTI_NOKTALARI } from "./acik/alan/baglanti-noktalari.mjs"; import { varisKaydet as kapaliVaris } from "./kapali/uygulama/varis-kaydet.mjs"; import { varisKaydet as acikVaris } from "./acik/uygulama/varis-kaydet.mjs"; import { uretimCevresi } from "./acik/bagdastiricilar/uretim.mjs"; import { sinamaCevresi, tohum, GIDEN } from "./acik/bagdastiricilar/sinama.mjs"; const ITHAL = /^import[^"']*["']([^"']+)["']/gm; function kapanis(kok) { const gorulen = new Set(), yigit = [kok], kenar = []; while (yigit.length > 0) { const dosya = yigit.pop(); if (gorulen.has(dosya)) continue; gorulen.add(dosya); for (const [, yol] of readFileSync(dosya, "utf8").matchAll(ITHAL)) { if (yol.startsWith(".") === false) continue; const hedef = normalize(join(dirname(dosya), yol)); kenar.push([dosya, hedef]); yigit.push(hedef); } } return { gorulen, kenar }; } for (const [ad, kok] of [["kapali", "kapali/alan/teslimat.mjs"], ["acik", "acik/alan/teslimat.mjs"]]) { const { gorulen, kenar } = kapanis(kok); const disa = kenar.filter(([, hedef]) => hedef.startsWith("dis/")); console.log(`${ad} alan modulu: ithal kapanisi = ${gorulen.size}, dis dunyaya dogrudan bag = ${disa.length}`); for (const [k, h] of kenar) console.log(` ${k} -> ${h}`); } const adSayisi = Object.values(BAGLANTI_NOKTALARI).flat().length; console.log(`baglanti noktasi = ${Object.keys(BAGLANTI_NOKTALARI).length}, sozlesmedeki ad = ${adSayisi}`); kayitTablosu.tohum("T-K"); kayitTablosu.tohum("T-U"); tohum("T-S"); const kosumlar = [ ["kapali + dis moduller ", kapaliVaris("T-K", "34")], ["acik + uretim takimi ", acikVaris("T-U", "34", uretimCevresi)], ["acik + sinama takimi ", acikVaris("T-S", "34", sinamaCevresi)], ]; for (const [ad, s] of kosumlar) console.log(`${ad} ${JSON.stringify(s)}`); const alan = kosumlar.map(([, s]) => `${s.durum}/${s.gecilen}`); console.log(`alan alanlari esit mi = ${new Set(alan).size === 1}, ayri deger = ${new Set(alan).size}`); console.log(`dis kanala giden duyuru = ${bildirimKanali.sayi()}, sinama kanalina giden = ${GIDEN.length}`); try { acikVaris("T-S", "06", { kayitlar: sinamaCevresi.kayitlar }); } catch (hata) { console.log(`eksik cevre: ${hata.message}`); }
node bag-say.mjs
kapali alan modulu: ithal kapanisi = 4, dis dunyaya dogrudan bag = 3
kapali/alan/teslimat.mjs -> dis/kayit-tablosu.mjs
kapali/alan/teslimat.mjs -> dis/takip-servisi.mjs
kapali/alan/teslimat.mjs -> dis/bildirim-kanali.mjs
acik alan modulu: ithal kapanisi = 2, dis dunyaya dogrudan bag = 0
acik/alan/teslimat.mjs -> acik/alan/baglanti-noktalari.mjs
baglanti noktasi = 3, sozlesmedeki ad = 5
kapali + dis moduller {"kimlik":"T-K","numara":"V0001","kod":"IST-A1","durum":"aktarmada","gecilen":1}
acik + uretim takimi {"kimlik":"T-U","numara":"V0002","kod":"IST-A1","durum":"aktarmada","gecilen":1}
acik + sinama takimi {"kimlik":"T-S","numara":"S0001","kod":"SINAMA-34","durum":"aktarmada","gecilen":1}
alan alanlari esit mi = true, ayri deger = 1
dis kanala giden duyuru = 2, sinama kanalina giden = 1
eksik cevre: karsilanmayan baglanti noktasi: takip.noktaKodu, takip.varisNumarasi, duyurular.gonder
Sayıların Okunması
Alan modülünün ithal kapanışı 4’ten 2’ye, dış dünyaya giden doğrudan bağ sayısı 3’ten 0’a indi. İkinci sayı belirleyici olanıdır: kapalı düzende toplama kökünü yüklemek üç dış modülü de yüklemek anlamına gelir, açık düzende alan modülü yalnız kendi sözleşme dosyasını tanır.
Üç bağ, üç bağlantı noktasına ve beş ada dönüştü. Nokta sayısı bağ sayısına eşit çıktı; bu rastlantı değildir, dönüşüm bağların adlandırılmasından oluşur. Beş ad ise dışarının alandan gördüğü yüzeydir: karşı taraf yalnız bu beş işlemi sağlamak zorundadır, kendi içinde kaç dosyası olduğu alanı ilgilendirmez.
Üç koşumun alan sonuçları eşit çıktı: aynı durum, aynı geçilen sayısı, ayrı değer sayısı 1. Farklı olan tek şey bağdaştırıcıdan gelen alanlardır — varış numarası ve nokta kodu takıma göre değişti, çünkü onları alan üretmiyor. Duyuru sayaçları da bunu doğruluyor: dış kanala 2 duyuru gitti, sınama kanalına 1. Aynı alan dosyası hem üretim takımıyla hem bellek içi takımla koştu ve dosyaya tek satır dokunulmadı.
Son satır sözleşme dosyasının koşum anındaki karşılığıdır. Eksik bir çevre verildiğinde karşılanmayan üç ad adıyla bildirildi. Kapalı düzende böyle bir hata iletisi olamaz, çünkü karşılanacak bir sözleşme yazılı değildir; eksik olan şey bir ithal hatası olarak görünür.
Bedelin Sayılması
Düzen ücretsiz değildir. Açık düzen 1 dosya (sözleşme) ve bağdaştırıcı takımı başına 1 dosya ekler; iki takımla toplam 3 dosya. Toplama kökünün her yaratımına bir parametre daha eklendi. Nokta sayısı arttıkça bu parametre büyür: dokuz noktalı bir alanda çevre nesnesi dokuz alan taşır ve her kullanım senaryosu onu baştan sona geçirir.
Kazanç iki koşula bağlıdır. Birincisi, bir noktanın gerçekten birden fazla bağdaştırıcısı olmalıdır; tek bağdaştırıcılı bir nokta yalnız dolaylılık ekler. İkincisi, alan modülünün kendi başına koşturulabilmesi bir işe yaramalıdır. Alanda karar yoksa — kararlar yine dışarıda duruyorsa — bağlantı noktası boş bir sözleşmedir; ölçüm bunu doğrudan bağ sayısının 0 çıkması ama karar noktası sayısının da 0 çıkması olarak gösterir.
Özet
- Bağlantı noktası alanın dışa açıldığı bir yerin adı ve beklediği işlem kümesidir; bağdaştırıcı o kümenin gerçekleştirimidir ve aynı nokta için birden fazla olabilir.
- Altıgen mimari yeni bir ilke getirmez; sınır çizimi, politika–ayrıntı ayrımı ve bağımlılık yönünü alanı merkeze alacak biçimde düzenler.
- Alan modülünün ithal kapanışı 4’ten 2’ye, dış dünyaya giden doğrudan bağ sayısı 3’ten 0’a indi; üç bağ üç noktaya ve beş ada dönüştü.
- Aynı alan dosyası iki bağdaştırıcı takımıyla koştu; üç koşumun alan sonuçları eşit çıktı, yalnız bağdaştırıcıdan gelen numara ve kod değişti.
- Bedel 3 dosya ve her yaratımda bir parametredir; kazanç bir noktanın birden fazla bağdaştırıcısı olduğunda ve alanda gerçekten karar bulunduğunda doğar.
Sonraki Adım
Buraya kadarki bütün ölçüler yazma yönündeydi: bir varış kaydının hangi kararlardan geçtiği, hangi noktalardan dışarı çıktığı. Operasyon masasının asıl işi ise okumaktır — hangi teslimatlar rotasının gerisinde, hangi aktarma merkezinde kaç paket bekliyor, hangi taşıyıcının gecikme oranı yüksek. Bu soruların yanıtı toplama kökleri üzerinden aranırsa her soru için onlarca nesne gezilir ve gezinti teslimat sayısıyla büyür. Sonraki ders aynı verinin okuma için ayrı bir modelde tutulmasını ele alır: sorgu için gezilen nesne sayısını iki düzende sayar, ayrı modelin kaç komut geriden geldiğini ölçer ve bunun karşılığında eklenen dosya ve eşleme sayısını bedel olarak yazar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.