Ders 16 / 21
Bildirimler
Bildirim sınıfı, bildirim kanalı ve alıcının kanal tercihi; seçilen eşzamanlı kanalların koşması, seçilmeyen kanalın bağlanmaması; bildirim kuyruklu işaretlenince aynı kanalların ertelenmesi.
Yayın Güncelleme
Bu derste
- Bildirim sınıfı, bildirim kanalı ve alıcı tercihini birbirinden ayırır
- Alıcının seçtiği kanalların koştuğunu, seçmediği kanalın yola hiç girmediğini ölçer
- Bildirimi kuyruklu işaretlemenin aynı kanalları isteğin dışına nasıl bıraktığını gösterir
İçindekiler
Olaylar ve Dinleyiciler dersinde yazı kaydedilince dizin güncellendi; kullanıcıya haber vermek ayrı bir yan etkidir. Haber verme yolu birden çok olabilir ve herkes aynısını istemez. Bu ders şunu sorar: bir bildirim hangi yollarla gider ve alıcının seçmediği yol çağrılır mı?
Bildirim sınıfı (notification), ne söylendiğini ve hangi bildirim kanallarından gidebileceğini tanımlar. Bildirim kanalı (channel), bildirimi bir yere ileten parçadır: posta, veritabanı ve kısa mesaj bu derste üç kanaldır. Alıcının kanal tercihi hangilerinin kullanılacağını söyler. Bildirim sınıfı üç kanalı da sayar; alıcı birini ya da ikisini seçer. Bildirici bu ikisinin kesişimini yola koyar. Kesişimin dışında kalan kanal ne çağrılır ne de yola girer.
US4. Üç kanalın hepsi bir dizi kaydıdır: posta kanalı bir posta sunucusuna, kısa mesaj kanalı bir operatöre bağlanmaz, her kanal gönderilen metni kendi dizisine ekler ve ölçülen, o dizide bir kayıt olup olmadığıdır. Model, önceki dersler gibi aynı süreçtedir. Başarısız gönderim, yeniden gönderme ve sınırlama modellenmez. Bildirimin kuyruklu olması bildirim sınıfının bir alanıdır; bildirici bu alana bakar.
Ölçüm Çekirdeği
Kurs boyunca her derste aynı ölçüm kullanılır. kostu.php beklenen bir parçanın bu istekte koşup koşmadığını kaydeder; dosya her derste birebir verilir ve çıktı bloğu yoktur, yalnız diske yazılır. Gunluk üç liste tutar: isteğin yoluna konanlar, istek içinde çağrılanlar ve isteğin dışına bırakılanlar. sinifla() bunlara sırayla bakar. Karar sırası sinifla()’nın kendisidir: koştu, ertelendi, atlandı, bağlanmadı. Parçayı geliştirici adlandırır; ölçülen, geliştiricinin bu istekte koşmasını beklediği parçalardır.
<?php // kostu.php — beklenen bir parça bu istekte koştu mu? declare(strict_types=1); final class Gunluk { public static array $yol = []; // isteğin yoluna konan parçalar public static array $kostu = []; // istek içinde çağrılanlar public static array $ertelenen = []; // isteğin dışına bırakılanlar public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; } public static function yola(string $parca): void { self::$yol[] = $parca; } public static function kostu(string $parca): void { self::$kostu[] = $parca; } public static function ertele(string $parca): void { self::$ertelenen[] = $parca; } } function sinifla(string $parca): string { return match (true) { in_array($parca, Gunluk::$kostu, true) => 'koştu', in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi', in_array($parca, Gunluk::$yol, true) => 'atlandı', default => 'bağlanmadı', }; }
Bu derste her kanal bir parçadır: posta, veritabani, kisamesaj. Bildirici alıcının seçtiği kanalı yola koyar ve eşzamanlıysa kostu() ile çağırır, kuyrukluysa ertele() ile kuyruğa bırakır. Seçilmeyen kanal için hiçbir çağrı yapılmaz.
Bildirim, Kanal ve Alıcı
bildirim.php dilimin tamamıdır. Alici bir ad ve kanal tercihleri taşır. YorumBildirimi bir yorum başlığı, üç kanalın adı (kanallar()) ve kuyruklu bayrağı taşır. BildirimKanali arayüzü iki yöntemden oluşur, ad() ve gonder(); DiziKanali bunu bir dizi kaydıyla gerçekler. Bildirici::gonder() bildirimin kanallarını gezer; alıcının tercihinde olmayanı atlar (continue), olanı yola koyar ve ya çağırır ya kuyruğa bırakır. isle() kuyruktakileri koşturur. YorumDenetleyicisi::yorumYap() yorumu kaydeder ve bildiriciye bırakır.
<?php // bildirim.php — bildirim sınıfı, kanallar ve alıcı tercihi declare(strict_types=1); require_once 'kostu.php'; final class Alici { public function __construct(public readonly string $ad, public readonly array $tercihler) {} } final class YorumBildirimi { public function __construct(public readonly string $baslik, public readonly bool $kuyruklu = false) {} public function kanallar(): array { return ['posta', 'veritabani', 'kisamesaj']; } public function metin(): string { return "Yeni yorum: {$this->baslik}"; } } interface BildirimKanali { public function ad(): string; public function gonder(Alici $alici, YorumBildirimi $bildirim): void; } final class DiziKanali implements BildirimKanali { // her kanal bir dizi kaydıdır public array $kayitlar = []; public function __construct(private string $ad) {} public function ad(): string { return $this->ad; } public function gonder(Alici $alici, YorumBildirimi $bildirim): void { $this->kayitlar[] = $alici->ad . ': ' . $bildirim->metin(); } } final class Bildirici { private array $kanallar = []; private array $kuyruk = []; public function kanalEkle(BildirimKanali $kanal): void { $this->kanallar[$kanal->ad()] = $kanal; } public function kanal(string $ad): DiziKanali { return $this->kanallar[$ad]; } public function gonder(Alici $alici, YorumBildirimi $bildirim): void { foreach ($bildirim->kanallar() as $ad) { if (!in_array($ad, $alici->tercihler, true)) continue; // alıcı seçmedi Gunluk::yola($ad); if ($bildirim->kuyruklu) { Gunluk::ertele($ad); $this->kuyruk[] = [$this->kanallar[$ad], $alici, $bildirim]; } else { Gunluk::kostu($ad); $this->kanallar[$ad]->gonder($alici, $bildirim); } } } public function isle(): int { // işçi: kuyruklu bildirimleri koşturur $sayi = count($this->kuyruk); while ($is = array_shift($this->kuyruk)) { [$kanal, $alici, $bildirim] = $is; $kanal->gonder($alici, $bildirim); } return $sayi; } } final class YorumDenetleyicisi { public function __construct(private Bildirici $bildirici) {} public function yorumYap(Alici $alici, YorumBildirimi $bildirim): array { Gunluk::yeniIstek(); Gunluk::yola('yorumYap'); Gunluk::kostu('yorumYap'); $this->bildirici->gonder($alici, $bildirim); return [201, 'yorum yapildi']; } } function bildiriciKur(): Bildirici { $bildirici = new Bildirici(); foreach (['posta', 'veritabani', 'kisamesaj'] as $ad) $bildirici->kanalEkle(new DiziKanali($ad)); return $bildirici; }
Alıcının Seçtiği Kanal
İlk ölçüm iki alıcı kurar. Ayşe posta ve veritabanını seçmiştir; Burak yalnız kısa mesajı. İkisi için de aynı yorum bildirimi gönderilir.
<?php // tercih.php — iki alıcı, iki farklı kanal tercihi require_once 'bildirim.php'; $alicilar = [ new Alici('ayse', ['posta', 'veritabani']), new Alici('burak', ['kisamesaj']), ]; foreach ($alicilar as $alici) { $bildirici = bildiriciKur(); $yanit = (new YorumDenetleyicisi($bildirici))->yorumYap($alici, new YorumBildirimi('Merhaba')); echo "{$alici->ad} (", implode(', ', $alici->tercihler), ") -> {$yanit[0]}\n"; foreach (['posta', 'veritabani', 'kisamesaj'] as $ad) { printf(" %-11s kayıt: %d %s\n", $ad, count($bildirici->kanal($ad)->kayitlar), sinifla($ad)); } }
ayse (posta, veritabani) -> 201 posta kayıt: 1 koştu veritabani kayıt: 1 koştu kisamesaj kayıt: 0 bağlanmadı burak (kisamesaj) -> 201 posta kayıt: 0 bağlanmadı veritabani kayıt: 0 bağlanmadı kisamesaj kayıt: 1 koştu
Bu dersin gözlemi ilk alıcıda: seçilen iki bildirim kanalı da eşzamanlı olduğu için koştu ve her birinde bir kayıt vardır. Yanıt 201’dir. Ayşe’nin seçmediği kisamesaj ise bağlanmadı: bildirim sınıfı o kanalı sayıyordu, ama bildirici onu yola hiç koymadı, çünkü alıcının tercihinde yoktu. Kayıt sayısı 0’dır ve bu, kısa mesajın başarısız olduğunu değil, hiç denenmediğini söyler.
İkinci alıcı karşıttır ve aynı kuralı tersinden gösterir. Burak yalnız kisamesaj’ı seçmiştir; kisamesaj koştu, posta ve veritabanı bağlanmadı. Aynı bildirim sınıfı, aynı bildirici, aynı denetleyici; değişen yalnız alıcının tercihidir. Seçilmeyen kanalın atlanmadığına dikkat etmek gerekir: atlanan yola girmiş ve çağrılmamış olandır, ama seçilmeyen kanal yola hiç konmadı.
Sınırlayıcı Ölçüm
Bildirim kuyruklu işaretlenirse aynı kanallar ne olur? Ayşe’ye aynı yorum bildirimi gönderilir, ama bu kez kuyruklu: true ile. Ölçüm yanıtın hemen ardından kanalları, işçi çalıştıktan sonra yine kanalları sayar.
<?php // kuyruklu_bildirim.php — bildirim kuyruklu işaretlenirse require_once 'bildirim.php'; $alici = new Alici('ayse', ['posta', 'veritabani']); $bildirici = bildiriciKur(); $yanit = (new YorumDenetleyicisi($bildirici))->yorumYap($alici, new YorumBildirimi('Merhaba', kuyruklu: true)); echo "kuyruklu bildirim -> {$yanit[0]}\n"; foreach (['posta', 'veritabani', 'kisamesaj'] as $ad) { printf(" %-11s kayıt: %d %s\n", $ad, count($bildirici->kanal($ad)->kayitlar), sinifla($ad)); } echo ' işçi koşturdu: ', $bildirici->isle(), "\n"; foreach (['posta', 'veritabani', 'kisamesaj'] as $ad) { printf(" %-11s kayıt: %d\n", $ad, count($bildirici->kanal($ad)->kayitlar)); }
kuyruklu bildirim -> 201 posta kayıt: 0 ertelendi veritabani kayıt: 0 ertelendi kisamesaj kayıt: 0 bağlanmadı işçi koşturdu: 2 posta kayıt: 1 veritabani kayıt: 1 kisamesaj kayıt: 0
Yanıt aynıdır, 201. Seçilen iki kanal artık ertelendi: yola kondular, isteğin dışına bırakıldılar ve yanıt anında kayıt sayıları 0’dır. Seçilmeyen kanal yine bağlanmadı. isle() iki iş koşturdu ve sonrasında posta ve veritabanında birer kayıt vardır; kısa mesajda hâlâ 0 kayıt var, çünkü onun kuyruğa girmesini gerektiren bir seçim yoktu. Bildirimin kuyruklu olması tercih kuralını değiştirmedi; yalnız seçilen kanalların koştuğu anı yanıtın ardına taşıdı.
Bu, Kuyruklar ve İşler dersindeki ayrımın bir kez daha görünümüdür: kuyruğa konmuş olmak, kayıt ancak işçi çalıştığında vardır demektir. Bildirimin gittiğine inanan bir uygulama, yanıtı gördüğü anda değil, işçinin ardından kayda bakmalıdır.
Özet
- Bu dersin ölçü sınıfı koştu: Ayşe’nin seçtiği posta ve veritabanı kanalları eşzamanlıydı, yanıt içinde çağrıldı ve her birinde bir kayıt bıraktı.
- Bildirim sınıfı kanalları sayar, alıcının tercihi seçer; bildirici ikisinin kesişimini yola koyar.
- Karşıt: Ayşe’nin seçmediği
kisamesajve Burak’ın seçmediği posta ile veritabanı bağlanmadı; kayıt sayısı 0’dır ve kanal hiç denenmemiştir. - Sınırlayıcı ölçümde bildirim kuyruklu işaretlenince seçilen kanallar ertelendi,
isle()iki iş koşturdu ve kayıtlar ancak sonra göründü. - Seçilmeyen kanal atlanmış değildir: atlanan yola girer, seçilmeyen girmez.
Sonraki Adım
Bildirim kanalları her istekte aynı sorguyu yinelemek zorunda kalır: alıcının tercihi, yorum sayısı, bir listenin sıralaması. Aynı sonucu tekrar hesaplamanın bir bedeli var ve bazen onu atlamak yeterlidir. Önbellekleme dersi hesaplanmış bir değeri bir anahtar altında saklar ve isabet ederse hesaplamanın atlanıp atlanmadığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.