---
title: 'Sözleşme Temelli Servis İletişimi'
source: 'https://academia.sh/tr/kurslar/go-uygulamalar/sozlesme-temelli-servis-iletisimi'
course: 'Standart Kütüphane ve Ağ Uygulamaları'
language: tr
updated: '2026-08-23T16:55:13+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

Ö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.

```go
// 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.Buffer`e 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ü `gonderen`in 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.

```go
// 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ğı, `ortakTarif`ten üretildiğinde, `gonderen`in 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.Stdout`a yazıyor, öbürü `os.Stdin`den 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`, `ortakTarif`in 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 **derleniyor** —
`Fiyat`in 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
`eskiAliciKaynagi`nin çö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 `Fiyat`i 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 `eskiTarif`ini 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.
