İçeriğe geç
academia.sh

Ders 18 / 19

Servis Keşfi ve Ağ Geçidi

Bir çağrının hedefini kim belirledi — kaynaktaki sabit bir referans mı, bir kayıt mı, yoksa kabın kendi varsayılan çözümü mü? Keşif ve yönlendirme modellenir, gerçek ağ çağrısı yoktur. Sınırlayıcı ölçüm, keşfin bir hedefi bulmakla o hedefin hâlâ yanıt verip vermediğini doğrulamak arasındaki farkı ölçer — kayıttan silinmemiş ölü bir hedef aramada aynı biçimde bulunur, kusur ancak çağrı anında düşer.

İçindekiler

Önceki ders bir değerin hangi bileşene ulaştığını ölçtü. Bu ders bir çağrının hangi hedefe ulaştığını soruyor. Bir bileşen başka bir bileşeni çağırmak istediğinde, o hedefi nereden buluyor? Üç yol modelleniyor: kaynakta sabit yazılı doğrudan bir referans, bir kayıttan okunan bir hedef, ve kayıtta hiçbir şey bulunmadığında devreye giren kabın kendi varsayılan çözümü. Servis keşfinin ve ağ geçidinin kavramı M19/K03 Servis Mimarileri kursunda kuruldu ve burada tekrarlanmıyor; bu ders yalnız hedefin kaynakta nereden geldiğini ölçüyor. Gerçek bir ağ çağrısı yok — Servis arayüzünü gerçekleştiren birkaç sınıf, doğrudan Java çağrılarıyla birbirine bağlanıyor, ama sorulan soru gerçek bir dağıtık sistemdekiyle aynı: hedefi kim seçti, ve o hedef hâlâ orada mı?

Gerçek bir dağıtık sistemde bir hedefin “adresi” bir ana makine adı ve bir bağlantı noktasıdır, ve bu ikisi zamanla değişebilir — bir kopya kapanır, yenisi başka bir adreste açılır. Bu ders bu değişkenliği bir ana makine adıyla değil bir Java nesnesiyle modelliyor: ServisKaydi’nin tuttuğu şey bir ağ adresi değil, doğrudan bir Servis örneği, ama sorduğu soru aynı — bir ad, hangi somut hedefe karşılık geliyor, ve bu karşılık zamanla değişebiliyor mu?

Hedefi Kim Belirledi: Doğrudan Referans, Kayıt, Varsayılan

Dört sınıf aynı Servis arayüzünü gerçekleştiriyor: EnvanterServisiV1, EnvanterServisiV2, YedekServis, ve ilerleyen bölümde kullanılacak OluServis. Bir ServisKaydi, adları servis örneklerine eşliyor; bir AgGecidi, bir ad verildiğinde önce kayda bakıyor, kayıtta yoksa kendi varsayılan servisine dönüyor.

// Servis.java — cagrilabilecek her hedefin ortak arayuzu (yardimci kaynak, main yok)
interface Servis { String yanitVer(); }
// EnvanterServisiV1.java — bir hedef gerceklestirimi (yardimci kaynak, main yok)
class EnvanterServisiV1 implements Servis {
    public String yanitVer() { return "envanter-v1"; }
}
// EnvanterServisiV2.java — ayni mantiksal adin ikinci bir gerceklestirimi (yardimci kaynak, main yok)
class EnvanterServisiV2 implements Servis {
    public String yanitVer() { return "envanter-v2"; }
}
// YedekServis.java — kayitta karsiligi olmayan her hedef icin kabin varsayilan cozumu (yardimci kaynak, main yok)
class YedekServis implements Servis {
    public String yanitVer() { return "yedek"; }
}
// ServisKaydi.java — adlari servis orneklerine eslen kayit (yardimci kaynak, main yok)
import java.util.*;

class ServisKaydi {
    final Map<String, Servis> kayitlar = new HashMap<>();
    void kaydet(String ad, Servis s) { kayitlar.put(ad, s); }
    Servis bul(String ad) { return kayitlar.get(ad); }
}
// AgGecidi.java — bir ad verildiginde once kayda bakan, bulamazsa varsayilana donen yonlendirici (yardimci kaynak, main yok)
class AgGecidi {
    final ServisKaydi kayit;
    final Servis varsayilanServis;

    AgGecidi(ServisKaydi kayit, Servis varsayilanServis) {
        this.kayit = kayit;
        this.varsayilanServis = varsayilanServis;
    }

    String cagir(String hedefAdi) {
        Servis s = kayit.bul(hedefAdi);
        return (s != null ? s : varsayilanServis).yanitVer();
    }
}
// Olcum.java — sonuc yazdirma yardimcisi (yardimci kaynak, main yok)
class Olcum {
    static void goster(String etiket, Object deger) {
        System.out.printf("%-52s: %s%n", etiket, deger);
    }
}
// HedefiKimBelirledi.java — ayni cagri seklinin uc ayri kaynaktan hedef aldigi gosteriliyor
public class HedefiKimBelirledi {
    public static void main(String[] args) {
        Servis dogrudan = new EnvanterServisiV1();
        Olcum.goster("dogrudan referans ile cagri", dogrudan.yanitVer());

        ServisKaydi kayit = new ServisKaydi();
        kayit.kaydet("envanter", new EnvanterServisiV2());
        AgGecidi agGecidi = new AgGecidi(kayit, new YedekServis());

        Olcum.goster("ag gecidi uzerinden, kayittaki hedef", agGecidi.cagir("envanter"));
        Olcum.goster("ag gecidi uzerinden, kayitta olmayan hedef", agGecidi.cagir("fatura"));
    }
}
dogrudan referans ile cagri                         : envanter-v1
ag gecidi uzerinden, kayittaki hedef                : envanter-v2
ag gecidi uzerinden, kayitta olmayan hedef          : yedek

GT28. Doğrudan bir referansla yapılan çağrının hedefi kaynakta sabit yazılıdır — bu bir çağrı: new EnvanterServisiV1() satırı hangi gerçekleştirimin kullanılacağını doğrudan belirliyor, ve bu karar HedefiKimBelirledi sınıfı derlendiği anda sabitleniyor. GT29. Ağ geçidi üzerinden yapılan çağrının hedefi kayıttan gelir; kaynakta sabit olan yalnız bir ad — "envanter" — hangi nesnenin o adı karşıladığı değil. agGecidi.cagir("envanter") satırı hiç değişmeden, kayda konan nesne EnvanterServisiV1’den EnvanterServisiV2’ye değişse bile aynı çağrı farklı bir gerçekleştirime ulaşır. GT30. Kayıtta karşılığı olmayan bir ad istendiğinde ağ geçidi kabın kendi varsayılan servisine döner; bu üçüncü yol kaynakta hiç görünmez — "fatura" adının kayıtlı olmadığı AgGecidi.cagir yönteminin içine bakmadan anlaşılamaz.

Doğrudan referans en hızlı yol, ama en katı: EnvanterServisiV1’i EnvanterServisiV2’ye değiştirmek HedefiKimBelirledi sınıfının kendisini değiştirip yeniden derlemeyi gerektiriyor. Kayıt dolaylı bir katman ekliyor — çağıran taraf yalnız bir ad biliyor, hedefin hangi sınıftan geldiğini hiç bilmiyor — ve bu dolaylılığın bedeli, hedefi bulmanın artık bir harita araması gerektirmesi. Varsayılan servis üçüncü bir seçenek sunuyor: kayıt eksik olsa bile çağıran taraf bir istisna almak yerine bir yanıt alıyor, karşılığında o yanıtın gerçek hedeften mi yoksa yedekten mi geldiğini kaynaktan anlamak imkânsız hâle geliyor.

Üç yolun ortak noktası, hiçbirinin diğerinden “daha doğru” olmaması. dogrudan.yanitVer() çağrısı hiçbir dolaylılık taşımadığı için en hızlı ve en öngörülebilir, ama değişikliğe en kapalı olan da bu — kaynak metni değiştirmeden hedefi değiştirmenin hiçbir yolu yok. Kayıt üzerinden çağrı bu katılığı kaldırıyor: ServisKaydi içeriği koşum sırasında değişebilir, ve agGecidi.cagir("envanter") satırı bir kez yazılıp bir daha hiç değişmeden farklı hedeflere yönlendirilebilir. Varsayılan servis ise bir üçüncü ödünleşim sunuyor — hiçbir kayıt olmasa bile çağıran taraf boş elle dönmüyor, ama bu rahatlığın bedeli, “yanıt geldi” bilgisinin “doğru hedeften yanıt geldi” bilgisiyle aynı şey olmaması.

Sınırlayıcı Ölçüm: Keşif Hedefi Bulur, Doğrulamaz

ServisKaydi.bul yönteminin tek işi bir adı bir nesneye çevirmek; o nesnenin hâlâ doğru çalışıp çalışmadığına dair hiçbir bilgisi yok. Kayıt sınıfının kendi kaynağına bakıldığında bu açıkça görülüyor: kayitlar.get(ad) bir harita araması, ve bir haritadan okumak hiçbir zaman elindeki nesneyi çağırmaz — yalnız var olup olmadığına bakar. Bunu görmek için kayda bilinçli olarak bozuk bir hedef ekleniyor — OluServis, çağrıldığında her zaman bir istisna fırlatan bir gerçekleştirim, ama kayıttan hiç silinmemiş; kayıt açısından fatura adı hâlâ geçerli bir kayıt, tıpkı envanter gibi.

// OluServis.java — kayitta duran ama cagrildiginda her zaman basarisiz olan bir hedef (yardimci kaynak, main yok)
class OluServis implements Servis {
    public String yanitVer() { throw new IllegalStateException("hedefe ulasilamiyor"); }
}
// KesifDogrulamaz.java — kayit hem saglikli hem olu hedefi ayni bicimde "buldum" diyor, fark yalnizca cagri aninda cikiyor
public class KesifDogrulamaz {
    public static void main(String[] args) {
        ServisKaydi kayit = new ServisKaydi();
        kayit.kaydet("envanter", new EnvanterServisiV1());
        kayit.kaydet("fatura", new OluServis());

        Olcum.goster("kayit /envanter hedefini buldu mu", kayit.bul("envanter") != null);
        Olcum.goster("kayit /fatura hedefini buldu mu (hedef aslinda olu)", kayit.bul("fatura") != null);

        AgGecidi agGecidi = new AgGecidi(kayit, new YedekServis());
        try {
            agGecidi.cagir("fatura");
            Olcum.goster("cagri basarili mi", "evet");
        } catch (IllegalStateException e) {
            Olcum.goster("cagri basarili mi", "hayir, " + e.getClass().getSimpleName() + " cagri aninda dustu");
        }
    }
}
kayit /envanter hedefini buldu mu                   : true
kayit /fatura hedefini buldu mu (hedef aslinda olu) : true
cagri basarili mi                                   : hayir, IllegalStateException cagri aninda dustu

İlk iki satır birebir aynı: kayit.bul hem sağlıklı hem ölü hedef için true döndürüyor, çünkü ikisi de haritada bir anahtara karşılık geliyor — kayıt yalnız bir eşlemeyi kontrol ediyor, hedefin canlılığını değil. GT31. Keşif bir hedefi bulur ama onun hâlâ yanıt verip vermediğini doğrulamaz; kayıttan silinmemiş ölü bir hedef aramada sağlıklı bir hedefle aynı biçimde bulunur, kusur ancak çağrı anında düşer. AgGecidi.cagir çağrısı üçüncü satırda IllegalStateException fırlatana kadar hiçbir şey fatura hedefinin bozuk olduğunu söylemiyor — ne kayıt, ne ağ geçidi, ne de bir önceki bul çağrısı. Bu, kursun başka bir sessiz-öncesi kusuru: keşif adımı başarıyla geçiyor, kusur bir adım sonra, hiç beklenmeyen bir anda düşüyor.

Bu ayrım pratikte önemli, çünkü bir sistemin “sağlıklı” göründüğü an ile “gerçekten çalıştığı” an aynı an olmayabiliyor. ServisKaydi gibi bir yapı yalnız kayıtlı adları listeleyebiliyor; kayıtlı bir adın arkasındaki hedefin hâlâ ayakta olup olmadığını sormak tamamen ayrı bir sorumluluk, ve bu ders o sorumluluğun bul yönteminde bulunmadığını gösteriyor. Bu boşluğu kapatmanın bir yolu, kaydı canlılık bilgisiyle birlikte tutmak — her hedefin yanına bir “son yanıt verme” bilgisi eklemek ve bul yerine yalnız bu bilgisi güncel olan hedefleri döndüren ayrı bir yöntem yazmak. Bu ders o yapıyı kurmuyor; yalnız onun eksikliğinin sonucunu ölçüyor, çünkü sınırlayıcı ölçümün amacı düzeltmeyi göstermek değil, düzeltilmediğinde ne olduğunu saymak.

Üç bölümün ölçtüğü şey tek tabloda toplanabilir:

Soru Yanıt Kaynaktaki karşılığı
Hedefi kim belirledi (doğrudan referans) EnvanterServisiV1 new EnvanterServisiV1() — çağrı
Hedefi kim belirledi (ağ geçidi) kayıttaki neyse o "envanter" adı — kayıt
Kayıtta olmayan ad istenirse YedekServis hiçbir satır — varsayılan
bul("fatura") hedefi buluyor mu evet, true keşif yalnız eşlemeye bakar
cagir("fatura") başarılı mı hayır, istisna canlılık yalnız çağrı anında sınanır

Tablonun son iki satırı aynı hedef için farklı sonuç veriyor — bu, dersin özeti aslında: bir hedefin var olması ile o hedefin çalışıyor olması iki ayrı sorudur, ve keşif yalnız birincisine yanıt verir.

Özet

  • Doğrudan bir referansla yapılan çağrının hedefi kaynakta sabit yazılıdır; bu, davranışın kaynağı çağrı olan durumdur.
  • Ağ geçidi üzerinden yapılan çağrının hedefi bir kayıttan gelir; kaynakta sabit olan yalnız bir ad, hangi nesnenin o adı karşıladığı değil.
  • Kayıtta karşılığı olmayan bir ad istendiğinde ağ geçidi kabın kendi varsayılan servisine döner; bu yol kaynakta hiç görünmez.
  • Keşif bir hedefi bulur ama onun hâlâ yanıt verip vermediğini doğrulamaz; sağlıklı ve ölü hedef aramada aynı biçimde bulunur.
  • Kayıttan silinmemiş ölü bir hedefin kusuru keşif anında değil, çağrı anında düşer.

Sonraki Adım

Bu ders bir çağrının hedefe ulaşıp ulaşamadığını, ulaştıysa ne zaman başarısız olduğunu ölçtü. Sıradaki ders bu başarısızlığı bir veri noktası değil, tekrar eden bir olay olarak ele alıyor: bir hedef arka arkaya kaç kez başarısız olursa çağıran taraf denemeyi bırakmalı, ve bu karar nereden geliyor? Kurs ve müfredat kapanışı bu son sınırı ö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