Ders 12 / 19
Yozlaşma Önleyici Katman
Dış modelin sızmasının engellenmesi: iki taşıyıcı sağlayıcısının ad, durum kümesi ve birimlerinin kaç dosyada göründüğünün sayılması, iş kuralının tanıdığı dış durum değeri sayısının ölçülmesi, çeviri katmanı eklendiğinde çekirdek dosyalardaki sızmanın sıfıra inmesi ve katmanın dosya ile satır cinsinden bedelinin çıkarılması.
İçindekiler
Bir önceki dersin çeviri dosyaları elverişli bir durumda çalıştı: yukarı akıştaki sözleşme bağlamı aynı kod tabanının parçasıydı, adları düzenliydi, tipleri belliydi. Kitaplığın konuştuğu yukarı akışların bir kısmı böyle değil. Taşıyıcı sağlayıcılarının kendi modelleri, kendi durum kümeleri, kendi kimlik biçimleri ve kendi ağırlık birimleri var; hiçbiri kitaplığın alan diline uymak zorunda değil ve değiştiklerinde kimseye danışmıyorlar.
Bu dersin sorusu arayüz sorusu değil. İki sağlayıcının çağrı biçimlerinin farklı olması, Tasarım İlkeleri ve Tasarım Kalıpları kurslarında ölçülmüş bir sorundur ve çözümü bağdaştırıcıdır. Buradaki sorun bir düzey içeride: sağlayıcının kavramları içeri sızarsa iç model dışın diliyle konuşmaya başlar. Teslimat bağlamının durumları taşıyıcının hareket kodlarına dönüşür, gönderinin ağırlığı taşıyıcının birimine bağlanır, “kayıt bulunamadı” gibi bir aktarım hatası alanın bir durumu gibi davranır. Bu ders o sızmayı sayar.
Uyumsuzluğun İki Düzeyi
Bağdaştırıcı, iki tarafın imzalarını uzlaştırır: çağrının adı, parametre sırası, dönüş biçimi. Uzlaştırdığı şey biçimdir, kavram değildir. Bir bağdaştırıcı iki sağlayıcıyı tek bir işlev adı altında toplayabilir ve yine de her ikisinin durum kodlarını olduğu gibi yukarıya verebilir.
Yozlaşma önleyici katman (anti-corruption layer) ise iki tarafın modellerini uzlaştırır: dışın durum kümesi içinkine, dışın birimi içinkine, dışın hata biçimi içinkine çevrilir ve bu çeviri tek bir yerde durur. Adı bunu söylüyor: korunan şey iç modelin kendisidir. Ölçüsü de buradan çıkar — dışın adları ve değerleri iç modelin dosyalarında kaç kez görünüyor?
İki sağlayıcı, kitaplığın dışında duran iki dış sistem olarak yazılıyor. Kendi sözlükleri kasten birbirine benzemiyor.
mkdir -p saglayici sizan korumali
// saglayici/tasiyici-a.mjs — dis sistem A: kendi hareket kodlari, epok zamani, libre agirligi const KAYITLAR = { "AA-1041": { hareketKodu: "H25", sonHareketEpok: 1700000000, agirlikLibre: 5.3, hataKodu: 0 }, "AA-1042": { hareketKodu: "H30", sonHareketEpok: 1700050000, agirlikLibre: 1.5, hataKodu: 0 }, }; export function sevkDurumu(sevkKimligi) { const k = KAYITLAR[sevkKimligi]; return k === undefined ? { sevkKimligi, hareketKodu: "H99", sonHareketEpok: 0, agirlikLibre: 0, hataKodu: 404 } : { sevkKimligi, ...k }; }
// saglayici/tasiyici-b.mjs — dis sistem B: kendi durum etiketleri, ISO zamani, ons agirligi const KAYITLAR = { "BB/2210": { durumEtiketi: "tamamlandi", sonHareketZamani: "2023-11-14T22:13:20Z", agirlikOns: 84 }, "BB/2211": { durumEtiketi: "yolda", sonHareketZamani: "2023-11-15T20:00:00Z", agirlikOns: 12 }, }; export function takip(referans) { const k = KAYITLAR[referans]; return k === undefined ? { referans, durumEtiketi: "kayit-yok", sonHareketZamani: null, agirlikOns: 0 } : { referans, ...k }; }
Farklar sayılabilir: A durumu altı kodla, B beş etiketle veriyor; A zamanı epok saniyesi, B ISO metni olarak veriyor; A ağırlığı libre, B ons cinsinden veriyor; A bulunamayan kaydı hata koduyla, B bir durum etiketiyle bildiriyor. Alan tarafındaki iş kuralı ise tek bir soru soruyor: bu gönderi gecikmeli mi?
Sızan Düzen
Birinci düzende iş kuralı iki sağlayıcının sözlüğünü doğrudan tanıyor.
// sizan/gecikme.mjs — is kurali iki saglayicinin durum sozlugunu ve birimlerini dogrudan taniyor import { sevkDurumu } from "../saglayici/tasiyici-a.mjs"; import { takip } from "../saglayici/tasiyici-b.mjs"; export function gecikmeliMi(tasiyici, referans, pencereSaat, simdiEpok) { if (tasiyici === "A") { const y = sevkDurumu(referans); if (y.hataKodu !== 0 || y.hareketKodu === "H99") return null; if (y.hareketKodu === "H30" || y.hareketKodu === "H40") return false; return (simdiEpok - y.sonHareketEpok) / 3600 > pencereSaat; } const y = takip(referans); if (y.durumEtiketi === "kayit-yok") return null; if (y.durumEtiketi === "tamamlandi" || y.durumEtiketi === "iade") return false; return (simdiEpok - Math.floor(Date.parse(y.sonHareketZamani) / 1000)) / 3600 > pencereSaat; }
// sizan/rapor.mjs — rapor da iki saglayicinin birim ve durum sozluguyle yaziliyor import { sevkDurumu } from "../saglayici/tasiyici-a.mjs"; import { takip } from "../saglayici/tasiyici-b.mjs"; import { gecikmeliMi } from "./gecikme.mjs"; export function ozet(kayitlar, pencereSaat, simdiEpok) { let gecikmeli = 0, bilinmeyen = 0, gram = 0; for (const [tasiyici, referans] of kayitlar) { const g = gecikmeliMi(tasiyici, referans, pencereSaat, simdiEpok); if (g === null) bilinmeyen += 1; else if (g) gecikmeli += 1; gram += tasiyici === "A" ? Math.round(sevkDurumu(referans).agirlikLibre * 453.59237) : Math.round(takip(referans).agirlikOns * 28.349523125); } return { gecikmeli, bilinmeyen, gram }; }
Dosyalar çalışıyor ve kısa. Sorun, kuralın söylediği cümlededir. “Teslim edilmemiş bir gönderi son hareketinden bu yana servis penceresini aştıysa gecikmelidir” cümlesi alan uzmanının cümlesidir; dosyada duran cümle ise “hareket kodu H30 veya H40 değilse ve durum etiketi tamamlandı ya da iade değilse” biçimindedir. İkisi aynı şeyi söylüyor olabilir; ama ikincisi sağlayıcı sözleşmesi değiştiğinde yanlış olur ve yanlış olduğu yer iş kuralı dosyasıdır.
Korumalı Düzen
İkinci düzende her sağlayıcı için bir çeviri dosyası var. Çeviri dört işi birden yapıyor:
durum kümesini eşliyor, zamanı tek biçime getiriyor, ağırlığı grama çeviriyor ve bulunamayan
kaydı iç modelin bilinmiyor durumuna indiriyor.
// korumali/tasiyici-a-ceviri.mjs — A saglayicisinin modelini ic modele cevirir import { sevkDurumu } from "../saglayici/tasiyici-a.mjs"; const DURUM = { H10: "kabul", H20: "aktarmada", H25: "dagitimda", H30: "teslim-edildi", H40: "iade-edildi" }; export function gonderiGetir(referans) { const y = sevkDurumu(referans); if (y.hataKodu !== 0) return { gonderiNo: referans, sonDurum: "bilinmiyor", sonHareketAni: 0, agirlikGram: 0 }; return { gonderiNo: referans, sonDurum: DURUM[y.hareketKodu] ?? "bilinmiyor", sonHareketAni: y.sonHareketEpok, agirlikGram: Math.round(y.agirlikLibre * 453.59237), }; }
// korumali/tasiyici-b-ceviri.mjs — B saglayicisinin modelini ayni ic modele cevirir import { takip } from "../saglayici/tasiyici-b.mjs"; const DURUM = { alindi: "kabul", yolda: "aktarmada", "dagitima-verildi": "dagitimda", tamamlandi: "teslim-edildi", iade: "iade-edildi" }; export function gonderiGetir(referans) { const y = takip(referans); const sonDurum = DURUM[y.durumEtiketi] ?? "bilinmiyor"; return { gonderiNo: referans, sonDurum, sonHareketAni: sonDurum === "bilinmiyor" ? 0 : Math.floor(Date.parse(y.sonHareketZamani) / 1000), agirlikGram: Math.round(y.agirlikOns * 28.349523125), }; }
// korumali/gecikme.mjs — is kurali yalniz ic modelin durum kumesini taniyor const KAPANMIS = ["teslim-edildi", "iade-edildi"]; export function gecikmeliMi(gonderi, pencereSaat, simdiEpok) { if (gonderi.sonDurum === "bilinmiyor") return null; if (KAPANMIS.includes(gonderi.sonDurum)) return false; return (simdiEpok - gonderi.sonHareketAni) / 3600 > pencereSaat; }
// korumali/rapor.mjs — rapor da yalniz ic modeli okur import { gecikmeliMi } from "./gecikme.mjs"; export function ozet(gonderiler, pencereSaat, simdiEpok) { let gecikmeli = 0, bilinmeyen = 0, gram = 0; for (const g of gonderiler) { const s = gecikmeliMi(g, pencereSaat, simdiEpok); if (s === null) bilinmeyen += 1; else if (s) gecikmeli += 1; gram += g.agirlikGram; } return { gecikmeli, bilinmeyen, gram }; }
Kural dosyası artık hangi sağlayıcının konuştuğunu bilmiyor. Ağırlık alanının adı da
değişti: iç modelin zaman alanı sonHareketAni, A sağlayıcısının sonHareketEpok alanı
değil. Aynı sayıyı taşıyan iki adın ayrı tutulması gereksiz görünür; ama adın dışarıdan
alınması, o adın dışarıyla birlikte değişmesi demektir.
Sızmanın Sayılması
Ölçü üç sayıdan oluşuyor: dış adların ve dış durum değerlerinin geçtiği dosya sayısı, bunların kaçının çekirdek dosya olduğu ve iş kuralının tanıdığı dış durum değeri sayısı.
// sizma-say.mjs — dis modelin adlarinin ve durum degerlerinin kac dosyada gorundugunu sayar import { readFileSync } from "node:fs"; const DIS_ADLAR = ["hareketKodu", "sonHareketEpok", "agirlikLibre", "hataKodu", "durumEtiketi", "sonHareketZamani", "agirlikOns"]; const DIS_DEGERLER = ["H10", "H20", "H25", "H30", "H40", "H99", "alindi", "yolda", "dagitima-verildi", "tamamlandi", "iade", "kayit-yok"]; const IC_DEGERLER = ["kabul", "aktarmada", "dagitimda", "teslim-edildi", "iade-edildi", "bilinmiyor"]; const gecen = (metin, adlar) => adlar.filter((a) => new RegExp(`(?<![\\w-])${a}(?![\\w-])`).test(metin)); for (const [duzen, dosyalar, kural] of [ ["sizan", ["sizan/gecikme.mjs", "sizan/rapor.mjs"], "sizan/gecikme.mjs"], ["korumali", ["korumali/tasiyici-a-ceviri.mjs", "korumali/tasiyici-b-ceviri.mjs", "korumali/gecikme.mjs", "korumali/rapor.mjs"], "korumali/gecikme.mjs"], ]) { const satir = dosyalar.reduce((t, d) => t + readFileSync(d, "utf8").trimEnd().split("\n").length, 0); console.log(`${duzen}/ (${dosyalar.length} dosya, ${satir} satir)`); let sizan = 0, cekirdekSizan = 0, cekirdek = 0; for (const d of dosyalar) { const metin = readFileSync(d, "utf8"); const ad = gecen(metin, DIS_ADLAR), dd = gecen(metin, DIS_DEGERLER), id = gecen(metin, IC_DEGERLER); const sinir = d.includes("-ceviri.mjs"); if (!sinir) cekirdek += 1; if (ad.length > 0 || dd.length > 0) { sizan += 1; if (!sinir) cekirdekSizan += 1; } console.log(` ${d.split("/")[1].padEnd(22)} dis ad ${ad.length} dis deger ${String(dd.length).padStart(2)} ic deger ${id.length}`); } console.log(` dis ad veya deger gecen dosya = ${sizan} / ${dosyalar.length}, cekirdek dosyada = ${cekirdekSizan} / ${cekirdek}`); const k = readFileSync(kural, "utf8"); console.log(` is kuralinin tanidigi durum degeri: dis ${gecen(k, DIS_DEGERLER).length}, ic ${gecen(k, IC_DEGERLER).length}`); }
node sizma-say.mjs
sizan/ (2 dosya, 32 satir) gecikme.mjs dis ad 5 dis deger 6 ic deger 0 rapor.mjs dis ad 2 dis deger 0 ic deger 0 dis ad veya deger gecen dosya = 2 / 2, cekirdek dosyada = 2 / 2 is kuralinin tanidigi durum degeri: dis 6, ic 0 korumali/ (4 dosya, 51 satir) tasiyici-a-ceviri.mjs dis ad 4 dis deger 5 ic deger 6 tasiyici-b-ceviri.mjs dis ad 3 dis deger 5 ic deger 6 gecikme.mjs dis ad 0 dis deger 0 ic deger 3 rapor.mjs dis ad 0 dis deger 0 ic deger 0 dis ad veya deger gecen dosya = 2 / 4, cekirdek dosyada = 0 / 2 is kuralinin tanidigi durum degeri: dis 0, ic 3
Sızan düzende iki dosyanın ikisi de dışın sözlüğünü tanıyor ve ikisi de çekirdek dosya: 2/2. Korumalı düzende dışın sözlüğü hâlâ iki dosyada geçiyor — sayı aynı — ama ikisi de sınır dosyası; çekirdekte 0/2. Sızmanın toplam miktarı azalmıyor, yeri değişiyor. Yozlaşma önleyici katmanın iddiası bu kadardır: dışın modeli bir yerde görünmek zorundadır, o yer iş kuralı olmamalıdır.
İkinci satır çifti daha keskin. Sızan düzende iş kuralı altı dış durum değeri tanıyor ve hiçbir iç durum değeri tanımıyor; iç modelin durum kümesi kodda hiç yok, iki sağlayıcının kümelerinin birleşimi olarak dolaylı biçimde var. Korumalı düzende kural sıfır dış değer, üç iç değer tanıyor. Alan kavramı olarak “durum” ilk kez tek bir kümedir.
Davranış ve Bedel
Çevirinin doğru olduğu iddia edilmez, sınanır. Altı kayıt iki düzende de aynı kararı ve aynı özeti vermelidir.
// kosum.mjs — iki duzen ayni gecikme kararini ve ayni ozeti veriyor mu import { gecikmeliMi as sizanKarar } from "./sizan/gecikme.mjs"; import { ozet as sizanOzet } from "./sizan/rapor.mjs"; import { gonderiGetir as aCeviri } from "./korumali/tasiyici-a-ceviri.mjs"; import { gonderiGetir as bCeviri } from "./korumali/tasiyici-b-ceviri.mjs"; import { gecikmeliMi as korumaliKarar } from "./korumali/gecikme.mjs"; import { ozet as korumaliOzet } from "./korumali/rapor.mjs"; const KAYITLAR = [["A", "AA-1041"], ["A", "AA-1042"], ["A", "AA-9999"], ["B", "BB/2210"], ["B", "BB/2211"], ["B", "BB/9999"]]; const PENCERE = 24, SIMDI = 1700100000; const icModel = KAYITLAR.map(([t, r]) => (t === "A" ? aCeviri(r) : bCeviri(r))); let ayrilan = 0; for (let i = 0; i < KAYITLAR.length; i += 1) { const a = sizanKarar(KAYITLAR[i][0], KAYITLAR[i][1], PENCERE, SIMDI); const b = korumaliKarar(icModel[i], PENCERE, SIMDI); if (a !== b) ayrilan += 1; console.log(`${KAYITLAR[i][1].padEnd(9)} ${String(icModel[i].sonDurum).padEnd(13)} sizan ${String(a).padEnd(5)} korumali ${b}`); } console.log(`ayrilan karar = ${ayrilan} / ${KAYITLAR.length}`); console.log(`sizan ozet = ${JSON.stringify(sizanOzet(KAYITLAR, PENCERE, SIMDI))}`); console.log(`korumali ozet = ${JSON.stringify(korumaliOzet(icModel, PENCERE, SIMDI))}`);
node kosum.mjs
AA-1041 dagitimda sizan true korumali true
AA-1042 teslim-edildi sizan false korumali false
AA-9999 bilinmiyor sizan null korumali null
BB/2210 teslim-edildi sizan false korumali false
BB/2211 aktarmada sizan false korumali false
BB/9999 bilinmiyor sizan null korumali null
ayrilan karar = 0 / 6
sizan ozet = {"gecikmeli":1,"bilinmeyen":2,"gram":5805}
korumali ozet = {"gecikmeli":1,"bilinmeyen":2,"gram":5805}
Altı kayıtta karar aynı, özetin üç alanı da aynı. Bulunamayan iki kayıt ve iki farklı ağırlık birimi dâhil, çeviri sayısal bir fark üretmedi.
Bedel iki dosya ve on dokuz satırdır: 32 satırlık iki dosya yerine 51 satırlık dört dosya. İkinci bedel satırla ölçülmez: çeviri yalnız eşlediği kadarını geçirir. A sağlayıcısının yalnız kendisinde bulunan bir kavramı — örneğin gümrük beklemesi — iç modelin durum kümesinde karşılığı olmadığı sürece iş kuralına ulaşamaz. Bu bir kayıp gibi görünür, oysa kararın verildiği yeri belirler: yeni bir alan kavramı gerekip gerekmediğine iç model karar verir, sağlayıcı karar vermez.
Katmanın gerekçesi de buradan çıkar ve her dış sistem için geçerli değildir. Sözleşmesi kitaplığın denetiminde olan, modeli alanla aynı ayrımları taşıyan bir yukarı akış için katman yalnız bir dolaylılıktır. Gerekçe, dışın modelinin farklı ayrımlar yapması ve kitaplığa danışmadan değişmesidir.
Özet
- Bağdaştırıcı imzaları uzlaştırır, yozlaşma önleyici katman modelleri uzlaştırır; korunan şey iç modelin kendi kavramlarıdır.
- İki sağlayıcı durumu altı kod ile beş etiketle, zamanı epok ile ISO metniyle, ağırlığı libre ile ons cinsinden veriyordu; bulunamayan kaydı biri hata koduyla, öteki bir durum etiketiyle bildiriyordu.
- Sızan düzende dış sözlük iki dosyanın ikisinde de geçti ve ikisi de çekirdek dosyaydı (2/2); korumalı düzende yine iki dosyada geçti ama ikisi de sınır dosyasıydı, çekirdekte 0/2.
- İş kuralı sızan düzende altı dış durum değeri tanıyordu ve hiçbir iç değer tanımıyordu; korumalı düzende sıfır dış, üç iç değer tanıdı — durum ilk kez tek bir küme oldu.
- Altı kayıtta karar ve özetin üç alanı iki düzende de aynı çıktı; katmanın bedeli 2 dosya ve 19 satır, ayrıca çevirinin yalnız eşlediği kavramı geçirmesidir.
- Katmanın gerekçesi dışın modelinin farklı ayrımlar yapması ve kitaplığa danışmadan değişmesidir; sözleşmesi kitaplığın denetimindeki bir yukarı akış için katman yalnız bir dolaylılıktır.
Sonraki Adım
Buraya kadarki iki savunma da aynı varsayıma dayanıyordu: karşı taraf yabancıdır, arada bir çeviri durur. İki bağlamın iki ayrı takıma değil aynı takıma ait olduğu ya da aşağı akış takımının yukarı akışı değiştirme gücünün hiç bulunmadığı durumlarda bu varsayım tutmaz. Birinci durumda takımlar bir parçayı ortak tutmayı seçebilir; ikinci durumda aşağı akış çeviriden vazgeçip yukarı akışın modelini olduğu gibi benimseyebilir. İkisi de ölçülebilir kararlardır. Sonraki ders bu iki işbirliği kalıbını kurar, ortak tutulan parça büyüdüğünde iki takımın birlikte düzenlemek zorunda kaldığı dosya sayısını sayar ve çeviriden vazgeçmenin bedelini aynı ölçekte gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.