İçeriğe geç
academia.sh

Ders 06 / 19

Alan Servisleri

Nesneye ait olmayan davranışın yerleştirilmesi: en uygun tarife seçiminin gönderiye yüklendiği ve ayrı bir alan servisine verildiği iki modelde gönderi modülünün ithal ettiği modül sayısının, bir gönderiden erişilebilen nesne sayısının ve serileştirme uzunluğunun ölçülmesi, alan servisini uygulama servisinden ayıran kalıcılık izinin sayılması.

İçindekiler

Toplama kökünün yöntemleri kendi sınırının içindeki veriyle karar veriyor. Kitaplıkta bu kalıba uymayan bir davranış var: bir gönderiye uygulanacak en uygun tarifenin seçilmesi. Alan uzmanı kuralı şöyle söylüyor — uygulanabilir tarifeler arasından en düşük net ücreti veren seçilir, sözleşme gerektiren tarifeler yalnız o tarifeye yetkili sözleşmesi olan müşteriye uygulanır.

Karar üç şeyi birlikte gerektiriyor: gönderi, tarife kataloğu ve sözleşme. Üçünden hiçbiri ötekilerin sahibi değildir. Kendi verisiyle karar veremeyen, birden çok alan nesnesini birlikte gerektiren ve hiçbirine ait olmayan davranışın konulduğu yere alan servisi (domain service) denir.

Ölçüt: Ortak Çalışan Sayısı

Bir davranışın nereye ait olduğu iki soruyla belirlenir. Kararı vermek için kaç alan nesnesi gerekiyor? Bu nesnelerden biri ötekilerin sahibi mi?

Gönderinin hacimli sayılıp sayılmadığı kararı yalnız gönderinin ağırlığını gerektirir; ortak çalışan sayısı sıfırdır ve karar gönderinin yöntemidir. Tarife seçimi üç nesne gerektirir ve sahip yoktur; karar bir alan servisine aittir. Tavan kuralı iki nesne gerektirir — gönderi ve indirimleri — ama biri ötekinin sahibidir, dolayısıyla önceki dersteki gibi kökün yöntemi olarak kalır.

Davranışı Nesneye Yükleyen Model

İki sürüm de aynı katalogu ve aynı yetki kuralını kullanır.

mkdir -p ortak yuklu servis
// ortak/tarife-katalogu.mjs — tarife katalogu ve bir tarifenin ucret hesabi
export const TARIFELER = [
  { ad: "standart", kademe: [[1, 3900], [5, 6400], [Infinity, 11800]], asgari: 3990, sozlesmeli: false },
  { ad: "yogun-hacim", kademe: [[1, 4200], [5, 5900], [Infinity, 9800]], asgari: 4500, sozlesmeli: true },
  { ad: "bolgesel", kademe: [[1, 3600], [5, 6900], [Infinity, 13200]], asgari: 3600, sozlesmeli: false },
];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };

export function tarifeUcreti(agirlik, bolge, tarife) {
  const taban = tarife.kademe.find(([ust]) => agirlik <= ust)[1];
  return Math.max(Math.round(taban * (KATSAYI[bolge] ?? 1.8)), tarife.asgari);
}
// ortak/sozlesme-kurallari.mjs — sozlesme gerektiren tarifeye yetki kurali
export const yetkiliMi = (sozlesme, tarife) =>
  !tarife.sozlesmeli || (sozlesme?.yetkiliTarifeler ?? []).includes(tarife.ad);

Birinci sürüm seçimi gönderinin yöntemi yapar. Yöntem katalogu ve yetki kuralını kendi modülünde ithal eder, sözleşmeyi de gönderinin içinde tutar.

// yuklu/gonderi.mjs — secim gonderinin yontemi; katalog ve sozlesme gonderinin icinde
import { TARIFELER, tarifeUcreti } from "../ortak/tarife-katalogu.mjs";
import { yetkiliMi } from "../ortak/sozlesme-kurallari.mjs";

export class Gonderi {
  constructor(gonderiNo, agirlik, bolge, sozlesme) {
    this.gonderiNo = gonderiNo;
    this.agirlik = agirlik;
    this.bolge = bolge;
    this.sozlesme = sozlesme;
  }
  enUygunTarife() {
    const uygun = TARIFELER.filter((t) => yetkiliMi(this.sozlesme, t));
    return uygun.reduce((en, t) =>
      tarifeUcreti(this.agirlik, this.bolge, t) < tarifeUcreti(this.agirlik, this.bolge, en)
        ? t : en);
  }
  netUcret() { return tarifeUcreti(this.agirlik, this.bolge, this.enUygunTarife()); }
}

Bu sürüm önceki dersin kuralını da bozuyor: gönderi, kendi tutarlılık sınırının dışındaki bir toplamayı — sözleşmeyi — kimlikle değil nesneyle taşıyor.

Davranışı Servise Veren Model

İkinci sürümde gönderi yalnız kendi verisini tutar, sözleşmeye numarasıyla atıf yapar. Seçim ayrı bir modülde, üç parametreli bir işlev olur.

// servis/gonderi.mjs — gonderi yalniz kendi verisini tasir, sozlesmeye kimlikle atif yapar
export class Gonderi {
  constructor(gonderiNo, agirlik, bolge, sozlesmeNo) {
    this.gonderiNo = gonderiNo;
    this.agirlik = agirlik;
    this.bolge = bolge;
    this.sozlesmeNo = sozlesmeNo;
  }
  hacimliMi() { return this.agirlik >= 5; }
}
// servis/tarife-secimi.mjs — alan servisi: ucu de parametre, hicbirinin sahibi degil
import { tarifeUcreti } from "../ortak/tarife-katalogu.mjs";
import { yetkiliMi } from "../ortak/sozlesme-kurallari.mjs";

export function enUygunTarife(gonderi, tarifeler, sozlesme) {
  const uygun = tarifeler.filter((t) => yetkiliMi(sozlesme, t));
  if (uygun.length === 0) throw new RangeError("uygulanabilir tarife yok");
  return uygun.reduce((en, t) =>
    tarifeUcreti(gonderi.agirlik, gonderi.bolge, t)
      < tarifeUcreti(gonderi.agirlik, gonderi.bolge, en) ? t : en);
}

export const netUcret = (gonderi, tarifeler, sozlesme) =>
  tarifeUcreti(gonderi.agirlik, gonderi.bolge, enUygunTarife(gonderi, tarifeler, sozlesme));

Alan servisi ile uygulama servisini ayıran çizgi, aynı işi yapan üçüncü dosyada görünür. Uygulama katmanının sorumluluğu Veri Erişim Katmanı ve İş Mantığı kursundaki Katman Sorumlulukları dersinde, servis katmanı kalıbı da Tasarım Kalıpları kursunda kurulmuştu; buradaki soru yalnız hangi bilginin hangi dosyada durduğudur.

// servis/fiyatla.mjs — uygulama servisi: depoyu, saati ve kaydetmeyi burasi bilir
import { netUcret } from "./tarife-secimi.mjs";

export function fiyatla(gonderiNo, depo, saat) {
  const gonderi = depo.gonderiBul(gonderiNo);
  const sozlesme = depo.sozlesmeBul(gonderi.sozlesmeNo);
  const tutar = netUcret(gonderi, depo.tarifeler(), sozlesme);
  depo.kaydet({ gonderiNo, tutar, an: saat() });
  return tutar;
}

Ölçüm

Dört ölçü alınır: gönderi modülünün ithal ettiği modül sayısı, bir gönderi örneğinden erişilebilen nesne sayısı, serileştirme uzunluğu ve iki dosyanın kalıcılık izi. Sona altı gönderide iki modelin aynı tarifeyi ve aynı tutarı verip vermediği eklenir.

// servis-olc.mjs — gonderinin bildigi dis ad, nesne grafigi, kalicilik izi ve esdegerlik
import { readFileSync } from "node:fs";
import { TARIFELER } from "./ortak/tarife-katalogu.mjs";
import { Gonderi as YukluGonderi } from "./yuklu/gonderi.mjs";
import { Gonderi as ServisGonderi } from "./servis/gonderi.mjs";
import { enUygunTarife, netUcret } from "./servis/tarife-secimi.mjs";

const SOZLESMELER = {
  "S-77": { sozlesmeNo: "S-77", yetkiliTarifeler: ["yogun-hacim"] },
  "S-90": { sozlesmeNo: "S-90", yetkiliTarifeler: [] },
};
const GIRDILER = [
  ["G-3001", 0.4, "34", "S-77"], ["G-3002", 3, "06", "S-77"],
  ["G-3003", 12, "65", "S-77"], ["G-3004", 0.9, "34", "S-90"],
  ["G-3005", 7, "06", "S-90"], ["G-3006", 18, "65", "S-90"],
];

const ithal = (d) => (readFileSync(d, "utf8").match(/^import /gm) ?? []).length;
console.log(`gonderi modulunun ithal ettigi modul: yuklu ${ithal("yuklu/gonderi.mjs")}, ` +
  `servis ${ithal("servis/gonderi.mjs")}`);

function erisilebilir(kok) {
  const gorulen = new Set();
  const kuyruk = [kok];
  while (kuyruk.length > 0) {
    const n = kuyruk.pop();
    if (n === null || typeof n !== "object" || gorulen.has(n)) continue;
    gorulen.add(n);
    for (const v of Object.values(n)) kuyruk.push(v);
  }
  return gorulen.size;
}

const y = new YukluGonderi("G-3001", 0.4, "34", SOZLESMELER["S-77"]);
const s = new ServisGonderi("G-3001", 0.4, "34", "S-77");
console.log(`bir gonderiden erisilebilen nesne: yuklu ${erisilebilir(y)}, ` +
  `servis ${erisilebilir(s)}`);
console.log(`serilestirme uzunlugu: yuklu ${JSON.stringify(y).length}, ` +
  `servis ${JSON.stringify(s).length}`);

const IZ = /depo|kaydet|saat/;
for (const d of ["servis/tarife-secimi.mjs", "servis/fiyatla.mjs"]) {
  const satir = readFileSync(d, "utf8").split("\n").filter((l) => IZ.test(l));
  console.log(`${d}: kalicilik izi tasiyan satir ${satir.length}`);
}

let ayrilan = 0;
for (const [no, agirlik, bolge, sozlesmeNo] of GIRDILER) {
  const yg = new YukluGonderi(no, agirlik, bolge, SOZLESMELER[sozlesmeNo]);
  const sg = new ServisGonderi(no, agirlik, bolge, sozlesmeNo);
  const sozlesme = SOZLESMELER[sozlesmeNo];
  const ayni = yg.enUygunTarife().ad === enUygunTarife(sg, TARIFELER, sozlesme).ad
    && yg.netUcret() === netUcret(sg, TARIFELER, sozlesme);
  if (!ayni) ayrilan += 1;
  console.log(`  ${no} ${String(agirlik).padStart(4)} kg ${bolge} ${sozlesmeNo} -> ` +
    `${yg.enUygunTarife().ad.padEnd(12)} ${String(yg.netUcret()).padStart(6)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GIRDILER.length}`);
node servis-olc.mjs
gonderi modulunun ithal ettigi modul: yuklu 2, servis 0
bir gonderiden erisilebilen nesne: yuklu 3, servis 1
serilestirme uzunlugu: yuklu 117, servis 69
servis/tarife-secimi.mjs: kalicilik izi tasiyan satir 0
servis/fiyatla.mjs: kalicilik izi tasiyan satir 6
  G-3001  0.4 kg 34 S-77 -> bolgesel       3600
  G-3002    3 kg 06 S-77 -> yogun-hacim    7965
  G-3003   12 kg 65 S-77 -> yogun-hacim   17640
  G-3004  0.9 kg 34 S-90 -> bolgesel       3600
  G-3005    7 kg 06 S-90 -> standart      15930
  G-3006   18 kg 65 S-90 -> standart      21240
ayrilan sonuc = 0 / 6

İlk satır davranışı nesneye yüklemenin dolaysız bedelini veriyor: gönderi modülü iki dış modülü ithal etmek zorunda. Bu, gönderiyi kullanan her yerin dolaylı olarak tarife kataloğunu ve sözleşme kurallarını da yüklemesi anlamına gelir.

İkinci ve üçüncü satır bedelin nesne düzeyindeki karşılığıdır. Yüklü modelde bir gönderiden üç nesneye erişilebiliyor — gönderinin kendisi, sözleşme ve sözleşmenin yetki listesi — servis modelinde bire. Serileştirme uzunluğu 117’den 69’a iniyor; gönderi tek başına taşındığında sözleşme onunla birlikte gitmiyor.

Dördüncü ölçü alan servisiyle uygulama servisinin çizgisini veriyor. Tarife seçimi dosyasında depo, kaydetme ve saat sözcüklerini taşıyan satır yok; aynı sözcükler uygulama servisinde altı satırda geçiyor. Alan servisi kararı verir, kararın nereden okunup nereye yazıldığını bilmez.

Son satır ölçünün ne olmadığını yine söylüyor: altı gönderinin altısında iki model aynı tarifeyi seçiyor ve aynı tutarı veriyor.

Alan Servisi Ne Değildir

Alan servisi bir kural taşıyıcısıdır, bir iş akışı yürütücüsü değildir. Ayrımın ölçüsü yukarıdaki dördüncü satırdır: alan servisinde kalıcılık izi sıfır olmalıdır. Bir servis depodan okuyor, işlem açıyor, saate bakıyor ya da bildirim gönderiyorsa uygulama katmanına aittir.

İkinci ayrım durumla ilgilidir. Alan servisi kendi durumunu tutmaz; girdisi parametreleriyle gelir, çıktısı dönüş değeridir. Durum tutan bir “servis”, adı ne olursa olsun sınırı belirsiz bir varlık nesnesidir ve önceki iki dersin ölçüleri onun için geçerlidir.

Ne Zaman Alan Servisi Açılmaz

Servisin bedeli bir dosya, bir dışa açılan ad ve her çağrıda geçirilen fazladan parametrelerdir. Ortak çalışan sayısı bir olan davranışlar için bu bedel karşılıksızdır: gönderinin hacimli sayılıp sayılmadığı kararı servise taşınırsa çağrı yeri gönderiyi parametre olarak geçirmek zorunda kalır ve kazanç sıfır olur.

İkinci karşı durum, servis adının bir kavramı örtmesidir. Üç nesne gerektiren bir kararın aslında dördüncü bir alan kavramı olduğu görülürse — tarife seçiminin bir “fiyatlandırma politikası” olduğu gibi — o kavram kendi tipini alır ve servis yeniden bir yöntem hâline gelir. “Servis” eki taşıyan her modül, alan diline karşılık gelmeyen bir kavramı saklıyor olabilir; ölçü, o modülün adının alan uzmanının cümlesinde geçip geçmediğidir.

Özet

  • Alan servisi, birden çok alan nesnesini birlikte gerektiren ve hiçbirine ait olmayan davranışın konulduğu yerdir; ölçüt ortak çalışan sayısı ve sahiplik sorusudur.
  • Tarife seçimi gönderiye yüklendiğinde gönderi modülü 2 dış modül ithal etti; servise verildiğinde 0.
  • Bir gönderiden erişilebilen nesne 3’ten 1’e, serileştirme uzunluğu 117’den 69’a indi; gönderi artık sözleşmeyi nesneyle değil numarasıyla taşıyor.
  • Alan servisiyle uygulama servisini ayıran ölçü kalıcılık izidir: tarife seçiminde 0 satır, uygulama servisinde 6 satır.
  • Altı gönderinin altısında iki model aynı tarifeyi ve aynı tutarı verdi; kazanç doğrulukta değil bağımlılık dağılımındadır.
  • Ortak çalışan sayısı bir olan davranış servise taşınmaz; servis adı bir alan kavramını örtüyorsa o kavram kendi tipini alır.

Sonraki Adım

Uygulama servisi gönderiyi bir depodan okudu ve tarifeleri ondan istedi. O deponun arayüzünde hangi sözcüklerin yazılı olduğu bu derste sorulmadı: gonderiBul mu yoksa sütun adları ve karşılaştırma işleçleri taşıyan bir sorgu nesnesi mi? Aynı soru yaratım için de geçerlidir — gönderi doğrudan yapıcıyla mı kuruluyor, yoksa değişmezini kuruluş anında güvenceye alan bir fabrikadan mı geçiyor? Sonraki ders depo arayüzündeki alan dışı ad sayısını ve bir sütun adı değiştiğinde dokunulan çağrı yeri sayısını ölçer, geçersiz bir gönderinin kaç yoldan yaratılabildiğini sayar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat