İçeriğe geç
academia.sh

Ders 05 / 12

Sözleşme Temelli Servis İletişimi

İki bağımsız sürecin kaynağı aynı paylaşılan tariften (alan adı, alan tipi, JSON etiketi listesi) text/template ile üretiliyor ve ayrı ayrı derlenip çalıştırılıyor. Tarif ortakken üç alanın üçü de tipini koruyor — kursun on üç sınır geçişi arasında korunuyor sınıfına giren tek satır. Tarif ayrışınca (alıcı tarafın üreticisi güncel değilse) geçiş sessizce bozuluyor: üç alandan ikisi korunuyor, biri hiç hata vermeden kayboluyor.

İçindekiler

Önceki ders bir şablonun ürettiği metnin hiçbir tarafta okunmayı beklemediğini gösterdi: çıktı yalnızca metindi, ve o metni geri okuyup bir tipe bağlayacak hiçbir taraf yoktu. Bu ders tam tersi bir uçtan başlıyor. İki bağımsız program (iki ayrı süreç, iki ayrı derleme) bir değeri birbirine geçirecek, ve bu kez sınırın iki tarafında da aynı tarif duruyor olacak. Kursun sorusu değişmiyor — bu değer sınırı geçerken tip bilgisine ne oluyor? — ama bu ders, on üç sınır geçişinin korunuyor sınıfına giren tek satırını kuruyor.

Bir tarif burada, iki tarafın da bir alan listesi üzerinde anlaştığı bir açıklamadır: her alanın adı, Go tipi ve JSON etiketi. Önceki derslerde tarif hep sınırın bir tarafında duruyordu — JSON’da alıcının elindeki hedef tip, kod ailesinde inceleyicinin bildiği kural. Bu derste tarif, sınırın hiçbir tarafına ait olmayan, üçüncü bir yerde (bir Go dilimi olarak programın kendi belleğinde) tutuluyor ve aynı tarif iki tarafın da kaynağını üretmek için kullanılıyor. Üretim standart kitaplığın kendi şablon paketiyle yapılıyor; hiçbir üçüncü taraf araç, protokol ya da veri biçimi devreye girmiyor.

İki tarafın kaynağını elle, ayrı ayrı yazmak da mümkündü — iki Kayit tipi, iki dosyada, aynı alan adlarıyla. Ama bu durumda “aynı” olmaları bir disiplin meselesine dönüşürdü: biri değişirken öbürü unutulabilir, ve bu unutuluş kaynak kodunun kendisinde görünmezdi. Tarifi tek bir yerde tutup iki kaynağı ondan üretmek bu riski ortadan kaldırmıyor — birazdan görüleceği gibi, üreticinin kendisi güncellenmezse aynı sorun geri geliyor — ama riski tek bir noktaya topluyor: artık iki elle yazılmış kaynağın senkron kalması değil, tek bir tarifin iki üretime de aynı anda verilmesi yeterli.

Aynı Tariften İki Tarafın Kaynağını Üretmek

AG26. Aşağıdaki yordamlar bir tarifi (AlanTarifi dilimini) alıp, Rol alanına göre iki farklı kaynak üretebilen tek bir şablonu çalıştırıyor: "gonderen" rolü bir değeri JSON’a çevirip standart çıktıya yazan bir program üretiyor, "alici" rolü standart girdiden okuyup aynı yapıya çözen bir program üretiyor. calistir her iki üretilmiş kaynağı da ayrı bir geçici modülde, go run . ile, ayrı bir süreç olarak çalıştırıyor — bu iki “taraf” birbirinin kaynağını hiç görmüyor, yalnızca standart girdi/çıktı üzerinden bayt alışverişi yapıyor.

// sozlesme.go — paylasilan tarifi iki tarafin kaynagini uretecek sekilde calistiran yordamlar
package main

import (
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"
)

type AlanTarifi struct {
	Ad     string
	Tip    string
	Etiket string
	Ornek  string
}

type SablonVerisi struct {
	Alanlar []AlanTarifi
	Rol     string
}

func uret(sablon *template.Template, veri SablonVerisi) string {
	var b bytes.Buffer
	sablon.Execute(&b, veri)
	return b.String()
}

func calistir(kaynak string, girdi []byte) string {
	dizin, _ := os.MkdirTemp("", "sozlesme")
	defer os.RemoveAll(dizin)
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module taraf\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dizin, "main.go"), []byte(kaynak), 0o644)

	cmd := exec.Command("go", "run", ".")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	if girdi != nil {
		cmd.Stdin = bytes.NewReader(girdi)
	}
	cikti, _ := cmd.Output()
	return string(cikti)
}

uret şablonu bir bytes.Buffere yazdırıp dizgi olarak döndürüyor — bir önceki dersin ölçtüğü tam olarak bu adım, burada bir kaynak kodu üretmek için kullanılıyor. calistir ise Go Temelleri kursunun Go Komut Ailesi dersinde kurulan desenin aynısı: kaynağı geçici bir modüle yazıp derleyiciyi (burada go run üzerinden) program içinden çağırmak. Fark şurada — orada tek bir kaynak biçimlendiriliyor ya da inceleniyordu, burada iki farklı kaynak, aynı tariften türeyip, birbirinden habersiz iki ayrı süreçte çalıştırılıyor.

Şablonun kendisi ikinci bir blokta duruyor; şablon metni, tarif dilimindeki her alan için bir yapı alanı ve rolüne göre ya bir kodlama ya bir çözme gövdesi üretiyor. İçe aktarım listesi de role göre dallanıyor: "fmt" ile "io" yalnızca alici rolünde ekleniyor, çünkü gonderenin gövdesi ikisini de hiç kullanmıyor. Bu ayrım rastgele değil — kullanılmayan bir içe aktarımın Go’da derlemeyi durdurduğu, Go Temelleri kursunda kurulmuş bir kuraldı; şablon bu kuralı bilmiyor ama üretici (yani bu ders) biliyor, ve iki farklı kaynak metni üretirken her ikisinin de kendi başına derlenebilir kalmasını rolüne göre dallanan bir {{if}} bloğuyla sağlıyor.

// olcum.go — ortak tarifle ve ayrisan tarifle sozlesme temelli gecisin olcumu
package main

import (
	"fmt"
	"strings"
	"text/template"
)

const tikTik = "`"

var sablonMetni = `package main

import (
	"encoding/json"
{{if eq .Rol "alici"}}	"fmt"
	"io"
{{end}}	"os"
)

type Kayit struct {
{{range .Alanlar}}	{{.Ad}} {{.Tip}} ` + tikTik + `json:"{{.Etiket}}"` + tikTik + `
{{end}}}

func main() {
{{if eq .Rol "gonderen"}}	k := Kayit{
{{range .Alanlar}}		{{.Ad}}: {{.Ornek}},
{{end}}	}
	bayt, _ := json.Marshal(k)
	os.Stdout.Write(bayt)
{{else}}	bayt, _ := io.ReadAll(os.Stdin)
	var k Kayit
	json.Unmarshal(bayt, &k)
{{range .Alanlar}}	fmt.Printf("%s tip=%T deger=%v\n", "{{.Ad}}", k.{{.Ad}}, k.{{.Ad}})
{{end}}{{end}}}
`

func main() {
	sablon := template.Must(template.New("taraf").Parse(sablonMetni))

	ortakTarif := []AlanTarifi{
		{Ad: "Ad", Tip: "string", Etiket: "ad", Ornek: `"vida"`},
		{Ad: "Adet", Tip: "int", Etiket: "adet", Ornek: "12"},
		{Ad: "Fiyat", Tip: "float64", Etiket: "fiyat", Ornek: "19.90"},
	}

	gonderenKaynagi := uret(sablon, SablonVerisi{Alanlar: ortakTarif, Rol: "gonderen"})
	fmt.Println("-- uretilen gonderen kaynagi --")
	fmt.Println(gonderenKaynagi)

	tasinanBayt := calistir(gonderenKaynagi, nil)

	aliciKaynagi := uret(sablon, SablonVerisi{Alanlar: ortakTarif, Rol: "alici"})
	aliciCiktisi := calistir(aliciKaynagi, []byte(tasinanBayt))
	fmt.Println("-- tarif ortak: alici uc alani da okuyor --")
	fmt.Print(aliciCiktisi)
	korunanOrtak := strings.Count(strings.TrimRight(aliciCiktisi, "\n"), "\n") + 1

	eskiTarif := ortakTarif[:2]
	eskiAliciKaynagi := uret(sablon, SablonVerisi{Alanlar: eskiTarif, Rol: "alici"})
	eskiAliciCiktisi := calistir(eskiAliciKaynagi, []byte(tasinanBayt))
	fmt.Println()
	fmt.Println("-- tarif ayristi: alici tarafin uretici tarifi guncel degil --")
	fmt.Print(eskiAliciCiktisi)
	korunanEski := strings.Count(strings.TrimRight(eskiAliciCiktisi, "\n"), "\n") + 1

	fmt.Println()
	fmt.Printf("tarif ortak: %d/%d alanin tipi koruniyor\n", korunanOrtak, len(ortakTarif))
	fmt.Printf("tarif ayristi: %d/%d alanin tipi koruniyor, %d alan sessizce kayboldu\n",
		korunanEski, len(ortakTarif), len(ortakTarif)-korunanEski)
}
-- uretilen gonderen kaynagi --
package main

import (
	"encoding/json"
	"os"
)

type Kayit struct {
	Ad string `json:"ad"`
	Adet int `json:"adet"`
	Fiyat float64 `json:"fiyat"`
}

func main() {
	k := Kayit{
		Ad: "vida",
		Adet: 12,
		Fiyat: 19.90,
	}
	bayt, _ := json.Marshal(k)
	os.Stdout.Write(bayt)
}

-- tarif ortak: alici uc alani da okuyor --
Ad tip=string deger=vida
Adet tip=int deger=12
Fiyat tip=float64 deger=19.9

-- tarif ayristi: alici tarafin uretici tarifi guncel degil --
Ad tip=string deger=vida
Adet tip=int deger=12

tarif ortak: 3/3 alanin tipi koruniyor
tarif ayristi: 2/3 alanin tipi koruniyor, 1 alan sessizce kayboldu

AG27. ortakTarif üç öğeli bir Go dilimi ve main fonksiyonu içinde tam olarak bir kez tanımlanıyor. gonderenKaynagi ve aliciKaynagi ikisi de bu aynı değişkenden üretiliyor — uret çağrısına ikisinde de ortakTarif geçiyor, başka bir kopya, başka bir elle yazılmış liste yok. Bu, SINIR ortak tanımının koyduğu kuralı karşılıyor: bir ders “korunuyor” satırı yazacaksa, tarifin iki tarafa nasıl ulaştığını da göstermek zorunda — burada gösterilen yol, aynı bellek değerinin iki ayrı üretim çağrısına girdi olması. Üretilen kaynaklar sonra birbirinden tamamen bağımsız: ayrı geçici dizinlerde, ayrı go.mod dosyalarıyla, ayrı süreçler olarak derlenip çalıştırılıyor. gonderen tarafının kaynağı alici tarafının kaynağını hiç görmüyor, ikisi yalnızca tasinanBayt üzerinden, düz bir bayt dizisiyle konuşuyor.

Ayrı süreç olarak çalıştırma burada bir ayrıntı değil, ölçümün gövdesi. Eğer gonderen ve alici, aynı Go paketinden içe aktarılan tek bir Kayit tipini paylaşsaydı, tip korunması Go’nun derleyicisinin garantisi olurdu — iki taraf zaten aynı bellek düzenine bakıyor olurdu, ve bu ders ölçmek istediği şeyi baştan varsaymış olurdu. Ayrı derlenen, ayrı çalışan iki ikili arasında yalnızca JSON baytlarıyla konuşmak, sınırı gerçekten bir sınır yapıyor: iki taraf, tarif ortak olduğu için değil, tarif ortak olduğunu her ikisinin de bağımsızca ürettiği için aynı tipte anlaşıyor.

Kör nokta. Bu ölçümün “korunuyor” sınıfı, tarifin iki tarafa da gerçekten ulaştığı varsayımı üzerine kurulu — ve bu ders bu ulaşmayı, iki üretim çağrısının aynı Go dilimini okuması olarak modelliyor. Üretimde iki tarafın aynı tarifi paylaşması genellikle bir dağıtım adımı gerektirir (bir tarafın güncellenmiş tarifi öbürüne bir şekilde ulaştırması); bu ders o dağıtım adımının kendisini ölçmüyor, yalnızca dağıtım başarılı olduğunda ne kazanıldığını ve başarısız olduğunda (aşağıdaki bölüm) ne kaybedildiğini ölçüyor.

Tarif Ortakken Üç Alanın Üçü de Tipini Koruyor

AG28. alici kaynağı, ortakTariften üretildiğinde, gonderenin yazdığı JSON’u kendi Kayit tipine çözüyor ve üç alanın üçünü de doğru tipiyle okuyor: Ad bir string, Adet bir int, Fiyat bir float64 olarak geri geliyor — kaynak taraftaki tiplerle birebir aynı. Bu satır kursun on üç sınır geçişinin tam dağılımındaki (1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor; 03. derste kurulan sayı) tek korunuyor satırı. Önceki derslerdeki “yeniden kuruluyor” satırları da bir tarif kullanıyordu (yapı etiketi, hedef tip, biçim dizgisindeki damga), ama o tarif hep sınırın bir tarafındaydı ve karşı taraf kendi tarifini kendi başına, ayrı bir kaynaktan biliyordu. Burada ikisi de aynı tarifin ürünü — bu fark, “yeniden kuruluyor” ile “korunuyor” arasındaki bütün mesafe.

AG29. Gönderen ve alıcının kaynak kodları birbirinden farklı: biri json.Marshal çağırıyor, öbürü json.Unmarshal; biri os.Stdouta yazıyor, öbürü os.Stdinden okuyor. Ama ikisinin Kayit tipi bayt bayt aynı alan adlarını ve aynı JSON etiketlerini taşıyor, çünkü ikisi de {{range .Alanlar}} döngüsünü aynı ortakTarif dilimi üzerinde çalıştırdı. Kodun kendisi aynı değil, ama kodu üreten tarif aynı — ve JSON’un alan adı bağlaması (bir etiketin dize karşılığı üzerinden kurulur) yalnızca bu ikisinin etiketleri aynı olduğu sürece çalışıyor. Tarif tek bir kaynaktan geldiği için etiketlerin aynı olması bir tesadüf değil, üretim biçiminin doğrudan sonucu.

Tarif Ayrışınca: Sessiz Kayıp

AG30. eskiTarif, ortakTarifin ilk iki öğesi — Fiyat alanı yok. Bu, alıcı tarafın üreticisinin güncellenmediği, gönderen tarafın ise güncel kaldığı bir durumu modelliyor: iki taraf artık aynı tarifi paylaşmıyor. eskiAliciKaynagi bu eksik tariften üretiliyor ve kendi Kayit tipinde yalnızca Ad ile Adet alanları var. Bu kaynak derleniyorFiyatin yokluğu Go’nun tip sisteminde hiçbir sorun değil, çünkü eksik alan başka bir tipin parçası olmayı hiç talep etmiyor — ve çalışıyor, json.Unmarshal çağrısı hiçbir hata döndürmüyor.

AG31. tasinanBayt üç alanı da taşıyor ({"ad":"vida","adet":12,"fiyat":19.9}), ama eskiAliciKaynaginin çözdüğü yapı yalnızca ikisini okuyabiliyor. fiyat anahtarı JSON metninde duruyor, json.Unmarshal onu görüyor, ve hedef yapıda karşılığı olmadığı için sessizce atlıyor — üçüncü paydaki JSON dersinin “alıcıda karşılığı olmayan alan sessizce düşüyor” gözlemiyle birebir aynı düzenek, burada tek farkla: kayıp, bir programlama hatasından değil, iki tarafın tarifinin birbirinden ayrışmasından doğuyor. Ölçümün son satırı bunu sayıyla veriyor: tarif ortakken 3/3, tarif ayrışınca 2/3 alanın tipi korunuyor, ve kayıp alan hakkında hiçbir uyarı, hiçbir hata çıkmıyor.

Yönün önemi burada ortaya çıkıyor: eksik olan taraf alıcı. Gönderen tarafın kaynağı güncel kaldığı, yani Fiyati hâlâ gönderdiği sürece, hatanın hiçbir belirtisi gönderen tarafta görünmüyor — gonderenKaynagi bu derste hiç değişmedi, calistir(gonderenKaynagi, nil) çağrısı iki senaryoda da aynı baytı üretti. Sorun yalnızca alıcı tarafın ürettiği kaynakta, ve bu kaynağı üreten tarafın (kendi eskiTarifini kullanan üretici) güncellenmemiş olmasında. Eğer roller tersine çevrilseydi — gönderen eski tarifle, alıcı güncel tarifle üretilseydi — sonuç farklı olurdu: alıcının beklediği Fiyat alanı JSON’da hiç bulunmazdı ve yapı bu alanı kendi sıfır değeriyle (0) doldururdu, çünkü encoding/json eksik bir anahtarı da tıpkı bilinmeyen bir anahtar gibi sessizce görmezden geliyor. İki yön de aynı sınıfa (kayboluyor) düşüyor, ama kaybın nerede göründüğü değişiyor: bir yönde alıcının okuduğu yapıda alan hiç yok, öbür yönde alan var ama hep sıfır.

AG32. Bu, sözleşme temelli iletişimin bütün iddiasını tersinden kanıtlıyor: koruma tarifin kendisinden gelmiyor, tarifin dağıtımından geliyor. Aynı tarif iki tarafa aynı anda ulaştığı sürece geçiş korunuyor sınıfında kalıyor; taraflardan biri güncellenip öbürü kalırsa, geçiş hiçbir hata vermeden yeniden “kayboluyor” sınıfına düşüyor. Tek bir paylaşılan tarif kaynağı (bu derste ortakTarif değişkeni) korumanın önkoşulu; tarifin kendisi hiçbir zaman bir garanti değil, yalnızca iki tarafın ne kadar senkron kaldığına bağlı bir olasılık.

Bu sonucun kursun genel iddiasıyla ilişkisi dolaylı değil, doğrudan: on üç sınır geçişinin beşinde tip “yeniden kuruluyordu” ve her birinde geri kuran taraf, karşı tarafın elinde bulunan bir tarifti — ama o tarif hep karşı tarafın kendi tarifiydi, gönderen tarafın tarifiyle aynı olduğunu doğrulayan hiçbir mekanizma yoktu. Bu ders o iki tarifi tek bir tarife indirgeyerek “yeniden kurma” ile “koruma” arasındaki farkı ortaya koydu: yeniden kurma, karşı tarafın tahmininin doğru çıkmasına dayanıyordu; koruma, tahmine hiç gerek kalmamasına.

Özet

  • İki bağımsız sürecin kaynağı, aynı paylaşılan tarif dilimi üzerinden text/template ile üretilip ayrı ayrı geçici modüllerde derlenip çalıştırıldı; taraflar birbirinin kaynağını görmedi, yalnızca bayt alışverişi yaptı.
  • Tarif ortakken üç alanın üçü de tipini koruyor: Ad, Adet, Fiyat — kaynak ve hedef taraftaki tipler birebir eşleşti. Bu, kursun on üç sınır geçişi arasında korunuyor sınıfına giren tek satır.
  • Koruma, tarifin uret çağrısına aynı değerden geçirilmesiyle gösterildi; kanıt olmadan bir “korunuyor” satırı yazılamayacağı kuralı burada tam olarak karşılandı.
  • Tarif iki tarafta ayrışınca (bir alan eksik üretilince) kaynak yine derleniyor, program yine çalışıyor, ama eksik alan hiçbir hata vermeden düşüyor — 3/3’ten 2/3’e sessiz bir kayıp.
  • Sözleşme temelli iletişimin koruması bir garanti değil, iki tarafın tarifinin senkron kalmasına bağlı bir sonuç.

Sonraki Adım

Bu derste ölçülen paylaşım, tarifin ne olduğuyla değil, ne zaman kurulduğuyla ilgiliydi: gönderen tarafın kaynağı üretilip çalıştırıldı, sonra alıcı tarafın kaynağı ayrı bir süreçte başladı ve gönderenin bıraktığı baytı okudu — iki taraf hiç aynı anda çalışmadı, ve aralarında tek bir alışveriş oldu: bir istek gibi görünen bir yazma, bir yanıt gibi görünen bir okuma. Sıradaki ders bu varsayımı kaldırıyor: bağlantı uzun sürdüğünde, tek bir alışveriş yerine art arda birçok geçiş aynı bağlantı üzerinden aktığında, ve taraflardan biri karşı taraftan bir istek gelmeden veri itebildiğinde, sınır ne değişiyor?

Çerçeveleme, mesajın nerede başlayıp bittiğini taşıyan tek bilgi olarak devreye giriyor — bir akışın neresinin bir mesajın sonu, neresinin bir sonrakinin başı olduğunu söyleyen tek düzenek. Ama bu ders tam olarak gösterdi ki bir sınırı doğru çizmek, içindeki değerin tipini bilmekle aynı şey değil: çerçeveleme bir mesajın nerede bittiğini söyler, o mesajın içinde Ad, Adet, Fiyat gibi alanların durup durmadığını değil. O soruyu yanıtlamak yine bir tarif gerektirecek — bu dersin ölçtüğü türden bir tarif.

İ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