İçeriğe geç
academia.sh

Ders 07 / 19

Gömülü Sunucu ve İstek İşleme Modeli

İstek işleme zincirinin halkaları üç kaynağa ayrılır: nesne kabının kendi varsayılanı, bir açıklama ve açık bir çağrı. Aynı halka kümesi farklı sırayla bildirildiğinde kabın ürettiği çalışma sırası değişmiyor; açıklama taşıyan bir sınıf kabın dışında elle çağrıldığında ise açıklamanın hiçbir etkisi kalmıyor.

İçindekiler

Önceki konunun son dersi çağrı yerine hiç dokunmadan davranış eklenebildiğini, bunun sınırının da vekilin ulaşabildiği yer olduğunu gösterdi. Bu konu aynı kabı bir soruyla izliyor: kap bir isteği karşılarken aynı üç kaynak — çağrı, açıklama, varsayılan — hangi oranda devrede? Uç noktalar, doğrulama ve veri erişimi de aynı üç kaynaktan doğuyor; bu ders zincirin en dışındaki katmandan başlıyor ve isteğin kaba girişinden çıkışına kadar hangi halkadan geçtiğini sayıyor.

Sunucu burada modellenir. Gerçek bir soket açılmaz, gerçek bir bağlantı noktası dinlenmez, ağdan gerçek bir bayt okunmaz — bir isteğin kaptan geçerken izlediği yol, bir dizgenin üzerinden sırayla geçen küçük işlev nesneleri olarak kurulur. Bu basitleştirme dersin sorusunu değiştirmiyor: gerçek bir gömülü sunucuda da istek, dinleyici soketten uygulama koduna ulaşana kadar sabit bir halka dizisinden geçer, ve bu dizinin kaç halkasının kaynakta bir karşılığı olduğu sorusu soket gerçek olsun ya da olmasın aynı kalır. Sunucunun kendisinin görevi, bağlantıyı kabul etmesi ve isteği bu zincire sokması — M16/K01 Sunucu Temelleri ve Backend’in Rolü kursu bunu ve zincirin genel fikrini kavram olarak kurdu, burada tekrarlanmıyor. Kursun kendi terimi istek işleme zinciri, aynı kursun ara katman zinciri teriminden ayrı tutulur: ara katman zinciri çağıranın elle dizdiği bir sıradır, istek işleme zinciri ise bu derste görüleceği gibi kabın kendi kuralına göre kurduğu bir sıradır.

Sunucunun modellenmesinin bir başka nedeni ölçümün kendisiyle ilgili: gerçek bir bağlantı noktası açılsaydı, hangi bağlantı noktasının boşta olduğu makineye göre değişir ve bu ders serisinin bütününde geçerli olan ortama bağlı veri yasağı böyle bir değeri yazmayı zaten engeller. Dizgeyle kurulan model bu sorunu baştan kaldırıyor — aynı programın iki ayrı makinede iki ayrı çıktı verme ihtimali sıfıra iniyor, çünkü ölçülen şey ağdaki bir gecikme ya da bir bağlantı noktası numarası değil, kabın sabit bir sırayla uyguladığı işlev zinciri. Gerçek bir gömülü sunucuda bu zincirin en dış halkası ağdan okuma ve ağa yazmadır; burada o halka atlanıyor ve zincir doğrudan uygulama kodunun gördüğü ilk noktadan, isteğin kendisinden başlıyor.

Halkaların Üç Kaynağı

  • WV1. İstek burada bir dizge (govde) olarak modellenir; her halka bu dizgeye kendi imzasını ekler, ağdan okuma ve ağa yazma modellenmez.
  • WV2. Açıklamalı sınıfların keşfi gerçek bir sınıf yolu taraması yapmaz; kap, tara çağrısına verilen aday sınıf listesini gözden geçirir. Bu, gerçek bir çerçevenin sınıf yolunu dolaşan taramasının modellenmiş bir karşılığıdır.

Zincirin bir halkası üç yoldan biriyle var olabiliyor. Kabın kendi gövdesine yazılmış, hiçbir sınıfa hiçbir açıklama koymadan hep çalışan bir halka varsayılandır — burada isteğe bir kimlik ekleyen ve zincirin sonunda hataları yakalayan iki halka bu gruba giriyor, ikisi de Kap sınıfının kendi calistir yönteminin içinde yazılı ve hiçbir dış çağrıya ihtiyaç duymuyor. Bir sınıfın üzerine konan @Halka açıklaması, kap o sınıfı tanıdığında zincire eklenmesini istiyor; bu istek kabın tara yöntemine bir aday listesi verilmeden hiçbir şey yapmıyor. Üçüncü yol ise en çıplak olanı: kodun kendisi kap.ekle(...) çağrısıyla bir halkayı doğrudan zincire koyuyor — kaynakta bunu doğuran satır tam olarak bu satır.

// Kap.java — istek isleme zincirinin halkalarini kim ekledi: cagiran mi, aciklama mi, kabin varsayilani mi
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Kap {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Halka { int oncelik() default 100; }

    interface HalkaIslevi { String uygula(String govde, List<String> iz); }

    final List<Class<?>> aciklamaliSinif = new ArrayList<>();
    final List<HalkaIslevi> cagriliHalka = new ArrayList<>();
    final List<String> cagriliAd = new ArrayList<>();

    void tara(Class<?>... adaylar) {
        for (Class<?> aday : adaylar)
            if (aday.isAnnotationPresent(Halka.class)) aciklamaliSinif.add(aday);
    }

    void ekle(String ad, HalkaIslevi h) {
        cagriliAd.add(ad);
        cagriliHalka.add(h);
    }

    String calistir(String govde, List<String> iz) {
        try {
            String s = govde;
            iz.add("istek-kimligi(varsayilan)");

            List<Class<?>> siraliAciklamali = new ArrayList<>(aciklamaliSinif);
            siraliAciklamali.sort(Comparator.comparingInt(c -> c.getAnnotation(Halka.class).oncelik()));
            for (Class<?> c : siraliAciklamali) {
                HalkaIslevi h = (HalkaIslevi) c.getDeclaredConstructor().newInstance();
                iz.add(c.getSimpleName() + "(aciklama)");
                s = h.uygula(s, iz);
            }
            for (int i = 0; i < cagriliHalka.size(); i++) {
                iz.add(cagriliAd.get(i) + "(cagri)");
                s = cagriliHalka.get(i).uygula(s, iz);
            }
            iz.add("hata-yakalama(varsayilan)");
            return s;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Kap kap1 = new Kap();
        kap1.tara(GunlukHalkasi.class, SikistirmaHalkasi.class);
        kap1.ekle("baslik-ekleme", (g, iz) -> g + "+baslik");
        kap1.ekle("uzunluk-yazma", (g, iz) -> g + "+uzunluk");
        List<String> iz1 = new ArrayList<>();
        String sonuc1 = kap1.calistir("istek", iz1);

        Kap kap2 = new Kap();
        kap2.tara(SikistirmaHalkasi.class, GunlukHalkasi.class);
        kap2.ekle("baslik-ekleme", (g, iz) -> g + "+baslik");
        kap2.ekle("uzunluk-yazma", (g, iz) -> g + "+uzunluk");
        List<String> iz2 = new ArrayList<>();
        String sonuc2 = kap2.calistir("istek", iz2);

        Kap kap3 = new Kap();
        kap3.tara(GunlukHalkasi.class, SikistirmaHalkasi.class);
        kap3.ekle("uzunluk-yazma", (g, iz) -> g + "+uzunluk");
        kap3.ekle("baslik-ekleme", (g, iz) -> g + "+baslik");
        List<String> iz3 = new ArrayList<>();
        String sonuc3 = kap3.calistir("istek", iz3);

        System.out.println("-- kurulum-1: G,S tarandi; baslik sonra uzunluk eklendi --");
        iz1.forEach(x -> System.out.println("  " + x));
        System.out.println("-- kurulum-2: S,G tarandi (aday sirasi ters); baslik sonra uzunluk eklendi --");
        iz2.forEach(x -> System.out.println("  " + x));
        System.out.println("-- kurulum-3: G,S tarandi; uzunluk sonra baslik eklendi (cagri sirasi ters) --");
        iz3.forEach(x -> System.out.println("  " + x));

        System.out.println("kurulum-1 ile kurulum-2 sirasi ayni mi (aciklamali kisim): " + iz1.equals(iz2));
        System.out.println("kurulum-1 ile kurulum-3 sirasi ayni mi (cagrili kisim de dahil): " + iz1.equals(iz3));
        System.out.println("govde sonuclari ayni mi (1 ve 2): " + sonuc1.equals(sonuc2));

        int varsayilan = 0, aciklama = 0, cagri = 0;
        for (String satir : iz1) {
            if (satir.contains("(varsayilan)")) varsayilan++;
            else if (satir.contains("(aciklama)")) aciklama++;
            else if (satir.contains("(cagri)")) cagri++;
        }
        System.out.printf("zincir: %d varsayilan, %d aciklama, %d cagri, toplam %d halka%n",
                varsayilan, aciklama, cagri, varsayilan + aciklama + cagri);

        System.out.println("-- elle kurulan zincir: kap hic devrede degil --");
        List<String> izElle = new ArrayList<>();
        SikistirmaHalkasi s1 = new SikistirmaHalkasi();
        GunlukHalkasi g1 = new GunlukHalkasi();
        String govdeElle = "istek";
        izElle.add("SikistirmaHalkasi(elle)");
        govdeElle = s1.uygula(govdeElle, izElle);
        izElle.add("GunlukHalkasi(elle)");
        govdeElle = g1.uygula(govdeElle, izElle);
        izElle.forEach(x -> System.out.println("  " + x));
        System.out.println("elle kurulumda varsayilan halka sayisi: "
                + izElle.stream().filter(x -> x.contains("varsayilan")).count());
        System.out.println("elle kurulumda ilk cagrilan sinif Sikistirma mi (kap'ta oncelik 20, Gunluk'ten sonra gelmesi gerekirdi): "
                + izElle.get(0).startsWith("Sikistirma"));
        System.out.println("kusur turu: etkisiz (istisna yok, @Halka.oncelik degeri hic okunmadi, sira degismedi)");
    }
}

@Kap.Halka(oncelik = 10)
class GunlukHalkasi implements Kap.HalkaIslevi {
    public String uygula(String govde, List<String> iz) { return govde + "+gunluk"; }
}

@Kap.Halka(oncelik = 20)
class SikistirmaHalkasi implements Kap.HalkaIslevi {
    public String uygula(String govde, List<String> iz) { return govde + "+sikistirma"; }
}
-- kurulum-1: G,S tarandi; baslik sonra uzunluk eklendi --
  istek-kimligi(varsayilan)
  GunlukHalkasi(aciklama)
  SikistirmaHalkasi(aciklama)
  baslik-ekleme(cagri)
  uzunluk-yazma(cagri)
  hata-yakalama(varsayilan)
-- kurulum-2: S,G tarandi (aday sirasi ters); baslik sonra uzunluk eklendi --
  istek-kimligi(varsayilan)
  GunlukHalkasi(aciklama)
  SikistirmaHalkasi(aciklama)
  baslik-ekleme(cagri)
  uzunluk-yazma(cagri)
  hata-yakalama(varsayilan)
-- kurulum-3: G,S tarandi; uzunluk sonra baslik eklendi (cagri sirasi ters) --
  istek-kimligi(varsayilan)
  GunlukHalkasi(aciklama)
  SikistirmaHalkasi(aciklama)
  uzunluk-yazma(cagri)
  baslik-ekleme(cagri)
  hata-yakalama(varsayilan)
kurulum-1 ile kurulum-2 sirasi ayni mi (aciklamali kisim): true
kurulum-1 ile kurulum-3 sirasi ayni mi (cagrili kisim de dahil): false
govde sonuclari ayni mi (1 ve 2): true
zincir: 2 varsayilan, 2 aciklama, 2 cagri, toplam 6 halka
-- elle kurulan zincir: kap hic devrede degil --
  SikistirmaHalkasi(elle)
  GunlukHalkasi(elle)
elle kurulumda varsayilan halka sayisi: 0
elle kurulumda ilk cagrilan sinif Sikistirma mi (kap'ta oncelik 20, Gunluk'ten sonra gelmesi gerekirdi): true
kusur turu: etkisiz (istisna yok, @Halka.oncelik degeri hic okunmadi, sira degismedi)

Toplam altı halka üç kaynağa bölünüyor: istek-kimligi ve hata-yakalama varsayılandır — Kap sınıfının hiçbir dış girdiye ihtiyaç duymadan kendi gövdesine yazdığı davranış. GunlukHalkasi ve SikistirmaHalkasi açıklamadır — kaynakta bu iki sınıfı çağıran hiçbir satır yok, yalnız üzerlerinde bir @Halka işareti var ve o işareti okuyan tara çağrısı var. baslik-ekleme ile uzunluk-yazma ise çağrıdır — kap.ekle(...) satırı olmadan bu iki halka zincirde hiç bulunmaz. Altı halka üç kaynağa eşit bölünüyor — ikişer ikişer — ama bu eşitlik bir kural değil, bu dersin seçtiği örneğin bir özelliği; ortak tanımın on iki davranışı çağrı, açıklama ve varsayılan arasında eşit bölünmüyordu, açıklama ağır basıyordu. Kursun sorduğu soru burada da aynı: bu kez ölçülen şey bir bileşenin kuruluşu değil, bir isteğin bir kaptan geçerken izlediği yol.

Kaynak metni yukarıdan aşağı okuyan biri bu altı halkanın yalnızca ikisini doğrudan görüyor. kap.ekle("baslik-ekleme", ...) ve kap.ekle("uzunluk-yazma", ...) satırları oradadır, ve bir okuyucu bu iki halkanın var olduğunu ve hangi sırayla eklendiğini kaynaktan çıkarabilir. Geri kalan dördü kaynağın başka bir yerinde iz bırakıyor: GunlukHalkasi ile SikistirmaHalkasi için tek iz sınıf tanımının üzerindeki @Halka satırı, istek-kimligi ile hata-yakalama için ise hiçbir iz yok — bu ikisi yalnız Kap.calistir yönteminin gövdesini okuyarak bulunabiliyor, ve o yöntem çağıran kodun hiç görmediği bir dosyada duruyor. Kaynağı yalnız çağıran tarafın dosyasından okuyan biri, zincirin üçte ikisinin nereden geldiğini kaçırıyor.

Kap Kendi Sırasını Uygular

  • WV3. oncelik değeri küçük olan açıklamalı halka önce çalışır; bu, kabın kendi kuralıdır ve kaynakta hiçbir yerde yazılı değildir.

Kurulum-1 ile kurulum-2 arasındaki tek fark tara çağrısına verilen sınıfların sırası: birinde önce günlük, sonra sıkıştırma sınıfı taranıyor, ötekinde tam tersi. Kaynak metni farklı yazıldığı hâlde iz listesi birebir aynı çıkıyor, çünkü kap taranan sınıfları kendi oncelik alanına göre yeniden sıralıyor ve tara çağrısına verilen dizinin sırasını hiç kullanmıyor. Halkaların çalışma sırası çağıranın kaynakta yazdığı sıra değildir; kap kendi sırasını uygular, ve iki farklı yazım aynı sırayı üretir.

Ama bu kural yalnız açıklamayla bulunan halkalar için geçerli. Kurulum-3, kurulum-1’le aynı tarama sırasını kullanıyor, farkı yalnız iki ekle çağrısının sırası: önce uzunluk, sonra başlık. İz listesi bu kez farklı çıkıyor — uzunluk-yazma önce, baslik-ekleme sonra görünüyor. Kap, açıklamayla bulduğu halkalar için kendi sıralama kuralını uyguluyor, ama açık bir ekle çağrısıyla eklenen halkalar için hiçbir sıralama yapmıyor; onları tam olarak çağrıldıkları sırada zincire koyuyor. Sıra kuralı yalnız kabın keşfettiği şeye uygulanıyor, kabın söylenildiği şeye değil — bir açıklama kabın kendi kararına bırakılmış bir istektir, bir çağrı ise çağıranın kendi kararıdır ve kap onu değiştirmiyor.

Bu ayrımın pratik bir sonucu var: iki geliştirici aynı iki açıklamalı sınıfı farklı bir sırayla tara’ya verse — biri dosyaları alfabetik, öteki değişiklik tarihine göre listeleyen bir araçla topladıysa — kaynak metinleri farklı görünür ama üretilen zincir birebir aynı kalır. Sıra kaynakta değil, oncelik alanında yaşıyor; kod incelemesi yapan biri iki sürümün de aynı davranışı ürettiğini görmek için tara çağrısındaki sıraya değil, açıklama tiplerinin kendi tanımına bakmak zorunda. Açık çağrı için bu güvence yok — ekle çağrılarının sırasını değiştirmek zincirin sırasını da değiştiriyor, çünkü kap orada hiçbir kural uygulamıyor.

Elle Kurulan Zincirde Açıklamanın Hiçbir Etkisi Yok

  • WV4. Elle kurulan çağrıda SikistirmaHalkasi ile GunlukHalkasi doğrudan örnekleniyor ve uygula yöntemleri sırasıyla çağrılıyor; hiçbir Kap nesnesi oluşturulmuyor.
  • WV5. @Halka açıklaması iki sınıfın üzerinde durmaya devam ediyor; kaldırılmadı, silinmedi. Elle kurulumda etkisiz kalmasının nedeni açıklamanın kaybolması değil, onu okuyan bir tarafın olmaması.

Son ölçüm kabı tamamen devre dışı bırakıyor: SikistirmaHalkasi ile GunlukHalkasi new ile elle kuruluyor, uygula yöntemleri elle, sırayla çağrılıyor. İki sonuç dikkat çekiyor. Birincisi, iz listesinde varsayilan etiketli hiçbir satır yok — istek-kimligi ve hata-yakalama Kap.calistir yönteminin gövdesine yazılı, ve bu yöntem hiç çağrılmadığı için o iki davranış da hiç gerçekleşmiyor. İkincisi, SikistirmaHalkasi önce çağrılıyor, GunlukHalkasi sonra — oysa kap içinde bu ikisinin sırası tam tersiydi, çünkü SikistirmaHalkasi’nin onceliki 20, GunlukHalkasi’ninki 10. Elle çağrıda bu sayı hiç okunmuyor; @Halka(oncelik = 20) açıklaması sınıfın üzerinde duruyor ama onu okuyacak bir tara çağrısı, bir sıralama adımı yok. Açıklama bir özellik değil bir istektir; isteği yerine getirecek bir kap yoksa iki sınıf da sıradan iki Java nesnesinden farksız çağrılıyor ve sıra tamamen çağıranın elinde kalıyor.

Bu, dersin başındaki üç kaynak ayrımını tamamlıyor. Varsayılan davranış kabın kendi kodunun parçası olduğu için kap yoksa hiç yok; açıklama bir isteği taşıdığı için okuyan kap yoksa etkisiz; yalnız çağrı — bir yöntemin doğrudan çağrılması — kap olsun olmasın aynı davranışı üretiyor, çünkü çağrı zaten kaynakta yazılı bir ifadedir. Bu üç davranışın kap içindeki ve kap dışındaki farkı, kabın asıl kattığı şeyin ne olduğunu gösteriyor: kap yeni bir yetenek eklemiyor, kaynakta yazılmayan iki türden isteği (varsayılan ve açıklama) okuyup uyguluyor.

Bu sonucun sınayıcı için pratik bir uyarısı var: GunlukHalkasi ya da SikistirmaHalkasi sınıfını kap dışında bir birim testinde çağırmak, o sınıfın kendi mantığını doğrular ama zincirdeki yerini, sırasını ya da onunla birlikte çalışan varsayılan halkaları hiç sınamaz. İki ölçüm ayrı sorulara yanıt veriyor: sınıfın kendi davranışı doğru mu, ve kap onu doğru yere mi koyuyor. Birinci soru elle kurulan nesneyle yanıtlanabilir, ikinci soru yalnız kabın kendisi çalıştırılarak yanıtlanabilir — bu derste kurulum-1, kurulum-2 ve kurulum-3’ün birlikte yaptığı da tam olarak bu.

Özet

  • İstek işleme zincirinin altı halkası üç kaynağa ayrıldı: ikisi kabın varsayılanı, ikisi bir açıklama, ikisi açık bir çağrı.
  • Açıklamayla bulunan halkaların çalışma sırası, kabın kendi oncelik kuralına göre belirleniyor ve kaynaktaki tarama sırasından bağımsız; iki farklı tarama sırası aynı sonucu üretti.
  • Açık çağrıyla eklenen halkaların sırası kabın kuralına girmiyor; çağıranın yazdığı sıra aynen korunuyor.
  • Kap devre dışı bırakılıp aynı sınıflar elle çağrıldığında varsayılan halkalar hiç çalışmadı ve açıklamadaki oncelik değeri hiç okunmadı; açıklama okuyan bir taraf olmadan etkisizdi.
  • İki kurulumun ürettiği govde sonucu, sıra aynı kaldığı sürece birebir aynı çıktı.

Sonraki Adım

Bu ders bir isteğin kaptan geçerken izlediği zinciri ölçtü, ama zincirin sonunda isteğin hangi işi karşıladığı hiç belirlenmedi — halkalar isteği yalnız değiştirdi, yönlendirmedi. Sıradaki ders bu boşluğu kapatıyor: bir uç noktanın kaynakta hangi ifadeyle değil hangi açıklamayla bildirildiğini, ve eşleşmeyen bir isteğe kabın ne yanıt verdiğini ölçüyor.

İ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