İçeriğe geç
academia.sh

Ders 02 / 21

İstek–Yanıt Yaşam Döngüsü

Önyükleme, servis sağlayıcıların kayıt ve önyükleme evreleri, çekirdeğin isteği işlemesi ve yanıt gönderildikten sonra koşan işin yanıtın dışında kalması; ertelenen iş ile doğrudan çağrılan işin karşılaştırılması.

Yayın Güncelleme

Bu derste

  • Önyüklemenin kayıt ve önyükleme evrelerini ve evrelerin sırasını bir servis sağlayıcı modelinde okur
  • Yanıttan sonra koşacak şekilde kaydedilen işin yanıt gövdesinde iz bırakmadığını ve bitir() çağrısından sonra koştuğunu ölçer
  • Aynı işin denetleyicide doğrudan çağrıldığında koştu sınıfına, yanıttan sonraya bırakıldığında ertelendi sınıfına düştüğünü karşılaştırır
  • Önyüklemede kurulan bir parçanın isteğin penceresinde koşup koşmadığını iki istekte ölçer
İçindekiler

Önceki ders dördüncü istekte üç parçanın yanıtın içinde olmadığını gösterdi: ikisi işçide, biri yanıt gönderildikten sonra koştu. Bu ders işin ne zaman koştuğunu bir isteğin ömrü boyunca sırayla izler. Çerçeve bir isteği tek bir çağrı olarak görmez; önyükleme, işleme ve gönderim evrelerine böler ve her evrenin kendi kaydı vardır. Evrelerin sırası bilinmezse, bir parçanın neden bu istekte görünmediği de anlaşılmaz.

Bir İsteğin Evreleri

Çerçeveyle yazılmış bir uygulamada bir isteğin ömrü dört ayrı evreye bölünür ve her evrenin sahibi farklıdır.

Evre Ne olur Kimin kodu
Önyükleme Sağlayıcılar kaydolur ve önyüklenir Uygulamanın sağlayıcıları
İşleme Çekirdek isteği karşılayan eylemi bulur ve çağırır Çerçeve çağırır, eylemi geliştirici yazar
Gönderim Eylemin döndürdüğü yanıt istemciye bırakılır Çerçeve
Yanıttan sonra Kaydedilmiş işler koşar Geliştiricinin kaydettiği iş, çerçevenin çağrısı

Tablonun üçüncü ve dördüncü satırı arasındaki sınır bu dersin konusudur. İstemci yanıtı aldığı anda, çerçevenin elinde hâlâ koşmamış işler olabilir. Bu işlerin yanıtın içeriğine karışmadığını görmek kolaydır; asıl soru, onların gerçekten koşup koşmadığını nereden bileceğimizdir.

Önyükleme ve Servis Sağlayıcılar

Önyükleme (bootstrap), çerçevenin ilk isteği işlemeden önce kendini kurduğu evredir. Bu evrede çerçeve servis sağlayıcıları (service provider) çağırır: her sağlayıcı, uygulamanın bir bölümünü çerçeveye tanıtan bir kayıt birimidir. Sağlayıcının işi iki evreye ayrılır. Kayıt evresinde sağlayıcı yalnızca neyi sunduğunu bildirir ve başka bir sağlayıcının sunduğuna dokunmaz. Önyükleme evresinde bütün sağlayıcılar kaydolmuş olduğundan, sağlayıcı artık başkalarının sunduklarına güvenebilir. Evreler sağlayıcı başına değil, bütün sağlayıcılar için sırayla koşar: önce herkesin kaydı, sonra herkesin önyüklemesi.

Aşağıdaki yasam.php bu yaşam döngüsünün küçük bir modelidir. Cekirdek’te kayıt çağrılarından ikisi bu modelin özüdür: saglayici() bir sağlayıcının kayıt ve önyükleme fonksiyonlarını, eylem() isteğin sonunda çağrılacak parçayı kaydeder. onyukle() iki evreyi sırayla koşturur, isle() isteği işler ve bitir() yanıt istemciye gittikten sonra çağrılır.

<?php
// yasam.php — servis sağlayıcılı küçük bir yaşam döngüsü
declare(strict_types=1);
require_once 'kostu.php';

final class Yanit {
    public function __construct(public int $durum, public string $govde = '') {}
}

final class Cekirdek {
    public array $evreler = [];
    private array $saglayicilar = [];
    private array $izleyiciler = [];
    private array $sonra = [];
    private ?array $eylem = null;
    public function saglayici(Closure $kaydet, Closure $onyukle): void {
        $this->saglayicilar[] = [$kaydet, $onyukle];
    }
    public function izleyici(string $parca, Closure $is): void { $this->izleyiciler[$parca] = $is; }
    public function eylem(string $parca, Closure $is): void { $this->eylem = [$parca, $is]; }
    public function yanittanSonra(string $parca, Closure $is): void {
        Gunluk::ertele($parca);
        $this->sonra[$parca] = $is;
    }
    public function onyukle(): void {
        foreach ($this->saglayicilar as [$kaydet]) $kaydet($this);     // 1. evre: kayıt
        foreach ($this->saglayicilar as [, $onyukle]) $onyukle($this); // 2. evre: önyükleme
    }
    public function isle(): Yanit {
        Gunluk::yeniIstek();
        $this->sonra = [];
        foreach ($this->izleyiciler as $parca => $is) {
            Gunluk::yola($parca); Gunluk::kostu($parca); $is();
        }
        [$parca, $is] = $this->eylem;
        Gunluk::yola($parca); Gunluk::kostu($parca);
        return $is($this);
    }
    public function bitir(): array {           // yanıt istemciye gitti
        foreach ($this->sonra as $is) $is();
        return array_keys($this->sonra);
    }
}

Bu sınıf kostu.php çekirdeğine üç çağrıyla bağlanır. Her derste aynı yirmi iki satır yeniden verilir.

<?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ı',
    };
}

ÇT6. onyukle() iki ayrı döngüdür ve bu bilerek böyledir. Birinci döngü bitmeden ikinci döngü başlamaz, yani B sağlayıcısının kaydı, A sağlayıcısının önyüklemesinden önce koşar. A, önyüklemesinde B’nin sunduğuna güvenebilir; bunun tersi de doğrudur. Tek döngüde sağlayıcı sırası bu güveni bozardı. isle() ise onyukle() çağrısından bağımsızdır: model, önyüklemenin bir kez koştuğu ve ardından birçok isteğin işlendiği bir süreci temsil eder. Bir sağlayıcıyı kayıt evresinde başkasının sunduğuna dayandırmak da yanlış olurdu: o anda öteki sağlayıcı henüz kaydolmamış olabilir ve ortada bağlanmış bir şey yoktur. Kayıt evresinde yalnızca bildirim yapılır, kullanım önyükleme evresine bırakılır.

C Temelleri kursunda derleme aşamaları (ön işleme, derleme, birleştirme, bağlama) program çalışmadan önce koşan ayrı adımlardı. Burada böyle bir ayrım yoktur: önyükleme, kayıt, işleme ve yanıttan sonra evresi, hepsi çalışma anında koşar ve dört sınıf da çalışma anındadır. Ertelendi bir aşama değil, işin yanıtın dışına bırakılmasıdır.

Yanıttan Sonra Koşan İş

Bir denetleyici bazen yanıtı geciktirmemesi gereken bir iş ister: bir sayacı artırmak, bir istatistik satırı yazmak. İş yanıtın içeriğini etkilemiyorsa, istemcinin onu beklemesi için bir neden yoktur. Çerçeve bunun için yanittanSonra() sunar: iş kaydedilir, yanıt oluşturulur ve istemciye gönderildikten sonra bitir() onu koşturur. istatistik işi iki biçimde ölçülür. Birinde eylem işi yanittanSonra() ile bırakır, ötekinde işi doğrudan çağırır ve Gunluk::kostu() ile bildirir.

<?php
// yasamdongusu.php — yanıttan sonra koşan iş yanıtın dışındadır
declare(strict_types=1);
require_once 'yasam.php';

function kur(bool $dogrudan): Cekirdek {
    $c = new Cekirdek();
    $c->saglayici(
        fn(Cekirdek $c) => $c->evreler[] = 'kayıt A',
        fn(Cekirdek $c) => $c->evreler[] = 'önyükleme A',
    );
    $c->saglayici(
        fn(Cekirdek $c) => $c->evreler[] = 'kayıt B',
        fn(Cekirdek $c) => $c->evreler[] = 'önyükleme B',
    );
    $c->eylem('kaydet', function (Cekirdek $c) use ($dogrudan): Yanit {
        $is = fn() => null;
        if ($dogrudan) { Gunluk::kostu('istatistik'); $is(); }
        else { $c->yanittanSonra('istatistik', $is); }
        return new Yanit(201, 'kaydedildi');
    });
    $c->onyukle();
    return $c;
}

foreach ([false, true] as $dogrudan) {
    $c = kur($dogrudan);
    echo implode(', ', $c->evreler), "\n";
    $yanit = $c->isle();
    printf("%d %s | istatistik %s\n", $yanit->durum, $yanit->govde, sinifla('istatistik'));
    echo "yanıttan sonra: ", implode(', ', $c->bitir()) ?: '-', "\n";
}
kayıt A, kayıt B, önyükleme A, önyükleme B
201 kaydedildi | istatistik ertelendi
yanıttan sonra: istatistik
kayıt A, kayıt B, önyükleme A, önyükleme B
201 kaydedildi | istatistik koştu
yanıttan sonra: -

ÇT7. İki koşumun yanıtı aynıdır: 201 ve kaydedildi. Gövdede istatistik ile ilgili tek bir iz yoktur; ikisini ayıran, yalnızca Gunluk’un işi hangi listede bulduğudur. yanittanSonra() ile bırakılan iş ertele() çağrısıyla $ertelenen listesine girdi, isle() döndüğünde hâlâ koşmamıştı ve bitir() ondan sonra onu koşturdu. Doğrudan çağrılan iş ise yanıt oluşmadan önce, eylemin içinde koştu.

Modelde gönderim, isle() çağrısının Yanit döndürmesidir ve bitir() bunun ardından gelir. Gerçek bir sunucuda gönderim, çıktının istemciye bırakılması ve bağlantının kapatılmasıdır; yanıttan sonra koşan iş aynı süreçte ve aynı bellekte, ama istemci artık beklemezken koşar. Bu, önceki dersin işçisinden farklıdır: Kuyruk işi başka bir sürecin alabileceği bir yere bırakır ve isteğin süreciyle bağı kopar, yanittanSonra() ise isteğin süreci içinde kalır. İkisi de ertelendi sınıfındadır, ama arıza biçimleri farklıdır; süreç yanıttan hemen sonra kapanırsa kayıtlı iş hiç koşmayabilir.

Aynı işin iki ayrı sınıfa düşmesi, sınıfın işin kendisine değil işin kaydedildiği yere bağlı olduğunu gösterir. Karar sırası (koştu, ertelendi, atlandı, bağlanmadı) burada da geçerlidir: ilk koşumda istatistik $kostu listesinde yoktur, $ertelenen listesindedir; ikincisinde $kostu listesindedir ve sıra orada biter. Beklenen parçayı geliştirici adlandırdı: iki koşumda da aynı istatistik adı sorgulandı.

Bu dersin ölçü sınıfı ertelendi’dir. Ertelenen iş kaybolmuş değildir ve yanıtın dışında koşar; ama bir hata çıkarsa yanıt çoktan 201 olarak gitmiştir ve hata isteğin sonucunda görünmez. Sınıfların tehlike sırası burada da geçerlidir. Atlanan parça bir durum koduyla, ertelenen parça işçide ya da bitir() sonrasında görünür; en tehlikeli sınıf yine bağlanmadı’dır, çünkü hiçbir yerde görünmez. Ertelenen işin bağlanmadı olup olmadığını, yani yanittanSonra() çağrısının hiç yapılıp yapılmadığını, yine yalnızca geliştiricinin beklediği ad söyler.

Hangi İş Yanıttan Sonraya Bırakılır

Bir işi yanıttan sonraya bırakmak her zaman doğru değildir. Ayrımı yapan soru, yanıtın o işin sonucuna bağlı olup olmadığıdır. Eylem bir kaydı kaydedip yanıtta kaydın numarasını döndürüyorsa, numara kaydın kendisinden gelir ve iş doğrudan çağrılmalıdır. Yanıt işin sonucuna bağlı değilse, bir istatistik satırı örneğinde olduğu gibi, iş yanıttan sonraya bırakılabilir ve istemci onu beklemez. İş sonradan başarısız olursa istemciye haber verecek bir yanıt kalmadığı için, bırakılan işin başarısızlığı başka bir kanaldan, bir günlükten ya da bir uyarıdan duyurulmalıdır; bu kanalı seçmek bu dersin konusu değildir.

İki koşumun ortak bir noktası da vardır: eylem iki durumda da çağrıldı ve iki durumda da 201 döndürdü. Yanıt kodu, işin koşup koşmadığı hakkında bilgi vermez. İşin kendisi Gunluk’ta aranır. Bu yüzden bir geliştirici “istatistik yazıldı mı” diye sorduğunda, yanıtın 201 olması cevap değildir; cevap işin hangi listede bulunduğudur.

Sınırlayıcı Ölçüm

Önyükleme bir kez koşar, istekler birçok kez. Sağlayıcının önyükleme evresinde kurduğu bir parça her istekte koşar mı? Bunu iki parçayla ölçeriz: önyükleme evresinde yalnızca bir kez çağrılan ayarYukle ve aynı evrede kaydedilen, sonra her istekte çağrılan zamanlayici. İki istek işlenir ve her birinde iki ad sınıflanır.

<?php
// onyukleme.php — önyüklemede kurulan parça her istekte koşar mı
declare(strict_types=1);
require_once 'yasam.php';

$c = new Cekirdek();
$c->saglayici(
    fn(Cekirdek $c) => null,
    function (Cekirdek $c): void {
        Gunluk::kostu('ayarYukle');                   // önyükleme evresinde, istek başlamadan
        $c->izleyici('zamanlayici', fn() => null);    // her istekte çağrılacak parça
    },
);
$c->eylem('goster', fn() => new Yanit(200, 'tamam'));
$c->onyukle();
printf("önyükleme sonrası  ayarYukle %s\n", sinifla('ayarYukle'));
foreach ([1, 2] as $n) {
    $c->isle();
    printf("istek %d  ayarYukle %-11s zamanlayici %s\n", $n, sinifla('ayarYukle'), sinifla('zamanlayici'));
}
önyükleme sonrası  ayarYukle koştu
istek 1  ayarYukle bağlanmadı zamanlayici koştu
istek 2  ayarYukle bağlanmadı zamanlayici koştu

ÇT8. Beklenen sonuç iki parçanın ayrı sınıflara düşmesidir ve ölçüm bunu doğruladı. ayarYukle önyüklemeden hemen sonra koştu olarak görünür, çünkü Gunluk hâlâ önyüklemenin kaydını tutuyor. İlk isle() çağrısı yeniIstek() ile listeleri boşaltır ve ayarYukle her iki istekte de bağlanmadı olur: parça gerçekten koşmuştur, ama isteğin penceresinin dışında. zamanlayici ise iki istekte de koştu. Önyüklemede kurulan şey her istekte koşar, önyüklemede koşan şey koşmaz. Bağlanmadı burada bir hatayı değil, işin ölçüm penceresinin dışında kalmasını anlatır.

Bu ayrımın pratik karşılığı şudur: bir sağlayıcının önyüklemesine bir izleyici eklemek, o izleyicinin her istekte koşmasını sağlar; ama sağlayıcının önyüklemede yaptığı bir hazırlık işi, bir ayar dosyasını okumak gibi, bir kez yapılır ve sonraki isteklerde yeniden yapılmaz. Geliştirici bir parçanın “her istekte” koşmasını bekliyorsa, onu önyüklemede değil, izleyici gibi isteğin yoluna kaydetmelidir. Önyüklemeye konan iş ise bir kez koşar ve onu her istekte bekleyen biri, ikinci istekte onu bulamaz.

Bu sonucun sınırı modelin kendisidir. Model önyüklemeyi bir kez çağırır; her istekte betiği baştan yükleyen bir düzende önyükleme her isteğin içine düşerdi ve ayarYukle her istekte koşardı. Hangi düzenin kullanıldığını çerçeve değil, uygulamanın çalıştırıldığı süreç belirler.

Özet

  • Önyükleme iki evrede koşar: önce bütün servis sağlayıcıların kaydı, sonra bütün önyüklemeleri; böylece bir sağlayıcı önyüklemesinde başkasının sunduğuna güvenebilir.
  • Dört sınıf da çalışma anındadır; ertelendi bir aşama değil, işin yanıtın dışına bırakılmasıdır.
  • yanittanSonra() ile bırakılan iş yanıt gövdesinde iz bırakmaz ve bitir() çağrısından sonra koşar; aynı iş eylemde doğrudan çağrılırsa koştu sınıfına düşer.
  • Önyüklemede kurulan zamanlayici iki istekte de koştu; önyüklemede koşan ayarYukle iki istekte de bağlanmadı olarak göründü.
  • Bu dersin ölçü sınıfı ertelendi’dir: iş yanıttan sonra koşar ve hatası yanıta yansımaz.

Sonraki Adım

Bu derste isteğin hangi eyleme düştüğü hazır verildi: tek bir eylem kayıtlıydı ve her istek ona gitti. Sonraki ders, Yönlendirme, birden çok kaydın aynı isteğe uyduğu durumu soruyor: iki kalıp aynı yola uyarsa hangisinin eylemi çağrılır ve öteki parça isteğe hiç bağlanmış olur mu?

İ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