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.