---
title: 'Uzak Yordam Çağrısı ve gRPC'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/uzak-yordam-cagrisi-ve-grpc'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:44+00:00'
license: 'CC BY-SA 4.0'
---

# Uzak Yordam Çağrısı ve gRPC

Sözleşmeyi önce yazan yaklaşım: şema dosyasından üretilen doğrulayıcı, alan numarasının tel üzerindeki kimlik olması, kendi ikili çerçevelemesiyle metin biçiminin bayt karşılaştırması ve gRPC'nin bu tasarımı akış türleriyle birlikte getirmesi.

Önceki ders REST'in kısıtlarını sınadı. O kısıtların ortak yanı, sözleşmeyi ağın kurallarına
yaslamalarıdır: adresler, yöntemler, durum kodları. Sözleşmenin ayrıntısı — bir alanın tipi,
bir alanın zorunlu olup olmadığı — telin dışında, belgede kalır.

Bunun tersini yapan bir yaklaşım vardır. **Uzak yordam çağrısı** (remote procedure call),
ağ üzerindeki bir işlemi yerel bir fonksiyon çağrısı gibi gösterir: istemci `KitapGetir`
diye bir işlev çağırır, çağrı ağa iner, karşı tarafta çalışır ve sonuç döner. Bu gösterimin
işleyebilmesi için iki tarafın çağrının imzası üzerinde anlaşması gerekir ve bu anlaşma
belgede değil, makine okunur bir **şemada** durur.

## Yordam Çağrısı Benzetmesinin Sınırı

Benzetme yararlıdır ama tam değildir ve nerede bozulduğunu bilmek arayüz tasarımını
belirler. Yerel bir çağrı ölçülemeyecek kadar hızlıdır; uzak çağrı en iyi durumda bir ağ
turu sürer. Yerel bir çağrı ya çalışır ya da bir istisna atar; uzak çağrı üçüncü bir sonuç
üretebilir — **kısmi başarısızlık**: isteğin gidip gitmediği, işin yapılıp yapılmadığı
bilinmez. Yerel çağrıda argümanlar bellekteki nesnelerdir; uzak çağrıda bayt dizisine
çevrilmeleri gerekir ve bu çevirinin kuralları sözleşmenin kendisidir.

Bu üç fark, uzak yordam çağrısı biçemini kullanan arayüzlerin tasarımına yansır: çağrılar
kaba taneli tutulur (çok sayıda küçük çağrı yerine az sayıda dolu çağrı), yeniden deneme
davranışı açıkça tanımlanır ve veri biçimi sıkı biçimde belirlenir.

## Sözleşme Önce Yazılır

Şema dosyası arayüzün tek doğruluk kaynağıdır. Aşağıdaki dosya bir mesaj tipi ile bir servis
tanımlar; her alanın bir **numarası**, bir tipi, bir adı ve bir zorunluluk durumu vardır.

```
mesaj Kitap {
  1 dizgi    isbn      zorunlu
  2 dizgi    ad        zorunlu
  3 tamsayi  sayfa     istege-bagli
  4 mantiksal oduncte  zorunlu
}
servis Katalog {
  KitapGetir(KitapIstek) -> Kitap
  KitapAra(AramaIstek) -> akis Kitap
}
```

Bu dosyadan iki şey üretilir: alanların varlığını ve tipini denetleyen bir **doğrulayıcı**,
ve nesneyi bayt dizisine çeviren bir **kodlayıcı**. Kodlayıcının biçimi, alan adlarını tele
hiç çıkarmaz; her alan kendi numarasıyla anılır ve sayılar değişken uzunlukta yazılır.

```js
// uret.mjs — sema dosyasindan dogrulayici ve ikili kodlayici uretir (sozlesme onceliklidir).
import { readFileSync } from "node:fs";

export const semaOku = (dosya) => {
  const mesajlar = {}, servisler = {};
  let acik = null;
  for (const ham of readFileSync(dosya, "utf8").split("\n")) {
    const satir = ham.trim();
    if (!satir) continue;
    let e;
    if ((e = /^mesaj (\w+) \{$/.exec(satir))) { acik = mesajlar[e[1]] = []; continue; }
    if ((e = /^servis (\w+) \{$/.exec(satir))) { acik = servisler[e[1]] = []; continue; }
    if (satir === "}") { acik = null; continue; }
    if ((e = /^(\d+) (\w+)\s+(\w+)\s+(\S+)$/.exec(satir)))
      acik.push({ numara: +e[1], tip: e[2], ad: e[3], zorunlu: e[4] === "zorunlu" });
    else if ((e = /^(\w+)\((\w+)\) -> (akis )?(\w+)$/.exec(satir)))
      acik.push({ yordam: e[1], girdi: e[2], akis: Boolean(e[3]), cikti: e[4] });
  }
  return { mesajlar, servisler };
};

const TIPLER = { dizgi: "string", tamsayi: "number", mantiksal: "boolean" };

// Uretilmis dogrulayici: alanlarin varligini ve tipini semaya gore denetler.
export const dogrulayiciUret = (alanlar) => (nesne) => {
  const kusur = [];
  for (const a of alanlar) {
    const v = nesne[a.ad];
    if (v === undefined) { if (a.zorunlu) kusur.push(`${a.ad}: zorunlu alan eksik`); continue; }
    if (typeof v !== TIPLER[a.tip]) kusur.push(`${a.ad}: ${a.tip} bekleniyordu, ${typeof v} geldi`);
  }
  for (const ad of Object.keys(nesne))
    if (!alanlar.some((a) => a.ad === ad)) kusur.push(`${ad}: semada yok`);
  return kusur;
};

// Degisken uzunluklu tam sayi: kucuk sayilar az bayt tutar.
const varintYaz = (n) => {
  const b = [];
  do { let p = n & 0x7f; n >>>= 7; b.push(n ? p | 0x80 : p); } while (n);
  return Buffer.from(b);
};
const varintOku = (b, i) => {
  let n = 0, k = 0;
  while (b[i] & 0x80) { n |= (b[i++] & 0x7f) << k; k += 7; }
  return [n | (b[i++] << k), i];
};

// Ikili cerceveleme: her alan (numara<<3 | tur) anahtariyla yazilir; alan adi tele hic cikmaz.
export const kodlayiciUret = (alanlar) => ({
  kodla(nesne) {
    const parcalar = [];
    for (const a of alanlar) {
      const v = nesne[a.ad];
      if (v === undefined) continue;
      const tur = a.tip === "dizgi" ? 2 : 0;
      parcalar.push(varintYaz((a.numara << 3) | tur));
      if (tur === 0) parcalar.push(varintYaz(a.tip === "mantiksal" ? (v ? 1 : 0) : v));
      else { const g = Buffer.from(v, "utf8"); parcalar.push(varintYaz(g.length), g); }
    }
    return Buffer.concat(parcalar);
  },
  cozumle(bayt) {
    const nesne = {};
    let i = 0;
    while (i < bayt.length) {
      let anahtar; [anahtar, i] = varintOku(bayt, i);
      const alan = alanlar.find((a) => a.numara === anahtar >> 3);
      if ((anahtar & 7) === 2) {
        let uzunluk; [uzunluk, i] = varintOku(bayt, i);
        nesne[alan?.ad ?? `bilinmeyen_${anahtar >> 3}`] = bayt.toString("utf8", i, i + uzunluk);
        i += uzunluk;
      } else {
        let sayi; [sayi, i] = varintOku(bayt, i);
        nesne[alan?.ad ?? `bilinmeyen_${anahtar >> 3}`] = alan?.tip === "mantiksal" ? Boolean(sayi) : sayi;
      }
    }
    return nesne;
  },
});
```

## Ölçüm

Ölçüm dört soruyu yanıtlar: şemadan hangi yüzey üretilir, doğrulayıcı hangi kayıtları
reddeder, ikili biçim ne kadar bayt tutar ve alan adı değiştiğinde tel üzerindeki baytlara
ne olur?

```js
// olc.mjs — sema oncelikli sozlesmeyi sinar: dogrulama, ikili/metin bayt karsilastirmasi, alan adi degisimi
import { writeFileSync } from "node:fs";
import { semaOku, dogrulayiciUret, kodlayiciUret } from "./uret.mjs";

const { mesajlar, servisler } = semaOku("kutuphane.sema");
const alanlar = mesajlar.Kitap;
const dogrula = dogrulayiciUret(alanlar);
const { kodla, cozumle } = kodlayiciUret(alanlar);

console.log("-- 1) semadan uretilen servis yuzeyi --");
for (const y of servisler.Katalog)
  console.log(`  ${y.yordam}(${y.girdi}) -> ${y.akis ? "akis " : ""}${y.cikti}`);

console.log("\n-- 2) uretilmis dogrulayici --");
const ORNEKLER = [
  { isbn: "978-0262033848", ad: "Algoritmalara Giris", sayfa: 1312, oduncte: false },
  { isbn: "978-0201896831", ad: "Programlama Sanati", oduncte: true },   // istege bagli alan yok
  { isbn: "978-0131103627", oduncte: true },                             // zorunlu alan eksik
  { isbn: "978-0596007126", ad: "Head First", sayfa: "272", oduncte: true },  // tip yanlis
  { isbn: "978-0000000000", ad: "Deneme", oduncte: true, raf: "R-12" },  // semada olmayan alan
];
for (const o of ORNEKLER) {
  const kusur = dogrula(o);
  console.log(`  ${JSON.stringify(o).slice(0, 52).padEnd(54)} ${kusur.length ? "RED  " + kusur[0] : "GECER"}`);
}

console.log("\n-- 3) ikili cerceveleme ile metin biciminin bayt sayisi --");
const gecerli = ORNEKLER.filter((o) => dogrula(o).length === 0);
for (const o of gecerli) {
  const ikili = kodla(o), metin = Buffer.from(JSON.stringify(o), "utf8");
  console.log(`  ${o.isbn}  ikili=${String(ikili.length).padStart(3)} B  ` +
    `metin=${String(metin.length).padStart(3)} B  kazanc=%${Math.round(100 * (1 - ikili.length / metin.length))}`);
}
const toplamIkili = gecerli.reduce((t, o) => t + kodla(o).length, 0);
const toplamMetin = Buffer.byteLength(JSON.stringify(gecerli));
console.log(`  toplam       ikili=${toplamIkili} B  metin=${toplamMetin} B  ` +
  `kazanc=%${Math.round(100 * (1 - toplamIkili / toplamMetin))}`);

console.log("\n-- 4) tel uzerinde alan adi yok: sema alan adini degistirir --");
const bayt = kodla(gecerli[0]);
console.log(`  eski semayla cozumlenen : ${JSON.stringify(cozumle(bayt))}`);
writeFileSync("yeni.sema", `mesaj Kitap {
  1 dizgi    isbn      zorunlu
  2 dizgi    baslik    zorunlu
  3 tamsayi  sayfa     istege-bagli
  4 mantiksal oduncte  zorunlu
}
`);
const yeni = kodlayiciUret(semaOku("yeni.sema").mesajlar.Kitap);
console.log(`  yeni semayla cozumlenen : ${JSON.stringify(yeni.cozumle(bayt))}`);
console.log(`  ayni baytlar, ${bayt.length} B; degisen tek sey alan numarasinin adi.`);
```

```
-- 1) semadan uretilen servis yuzeyi --
  KitapGetir(KitapIstek) -> Kitap
  KitapAra(AramaIstek) -> akis Kitap

-- 2) uretilmis dogrulayici --
  {"isbn":"978-0262033848","ad":"Algoritmalara Giris",   GECER
  {"isbn":"978-0201896831","ad":"Programlama Sanati","   GECER
  {"isbn":"978-0131103627","oduncte":true}               RED  ad: zorunlu alan eksik
  {"isbn":"978-0596007126","ad":"Head First","sayfa":"   RED  sayfa: tamsayi bekleniyordu, string geldi
  {"isbn":"978-0000000000","ad":"Deneme","oduncte":tru   RED  raf: semada yok

-- 3) ikili cerceveleme ile metin biciminin bayt sayisi --
  978-0262033848  ikili= 42 B  metin= 81 B  kazanc=%48
  978-0201896831  ikili= 38 B  metin= 66 B  kazanc=%42
  toplam       ikili=80 B  metin=150 B  kazanc=%47

-- 4) tel uzerinde alan adi yok: sema alan adini degistirir --
  eski semayla cozumlenen : {"isbn":"978-0262033848","ad":"Algoritmalara Giris","sayfa":1312,"oduncte":false}
  yeni semayla cozumlenen : {"isbn":"978-0262033848","baslik":"Algoritmalara Giris","sayfa":1312,"oduncte":false}
  ayni baytlar, 42 B; degisen tek sey alan numarasinin adi.
```

## Ölçümün Söyledikleri

**Doğrulama sözleşmeden üretilir, elle yazılmaz.** Beş kaydın üçü reddedildi ve üç ret üç
ayrı türdendir: eksik zorunlu alan, yanlış tip, şemada bulunmayan alan. Bu denetimlerin
hiçbiri uygulama kodunda yazılmamıştır; şema dosyasındaki satırlardan çıkmıştır. Şema
değiştiğinde doğrulama da değişir ve iki tarafın kuralları ayrışamaz.

**İkili biçim aynı kaydı yaklaşık yarı bayta indirir.** Kazancın kaynağı sıkıştırma değil,
gereksiz olanın hiç yazılmamasıdır: alan adları, tırnak işaretleri, iki nokta ve virgüller
tele çıkmaz. Sayılar da metin olarak değil, değişken uzunluklu tam sayı olarak yazılır —
`1312` dört karakter yerine iki bayt tutar.

**Alan numarası kimliktir, alan adı değil.** Dördüncü bölüm, birinci derste kırıcı sayılan
bir değişikliği tekrarlar: `ad` alanı `baslik` olarak yeniden adlandırılır. Metin biçiminde
bu değişiklik tüketiciyi kırmıştı. İkili biçimde aynı 42 baytlık dizi, yeni şemayla
sorunsuz çözümlenir; yalnız çıktıdaki anahtar değişir. Tel üzerindeki kimlik `2` sayısıdır.

Bunun aynadaki karşılığı da doğrudur ve daha tehlikelidir: **numara değiştirmek sessizce
kırar.** İki alanın numaraları takas edilirse doğrulayıcı hiçbir şey söylemez, çünkü baytlar
geçerlidir; yalnız değerler yanlış alanlara yerleşir. Şema öncelikli tasarımın değişmez
kuralı buradan gelir: bir alan numarası bir kez verildikten sonra başka bir alana verilmez,
kaldırılan numara yeniden kullanılmaz.

## gRPC'nin Getirdikleri

Bu dersteki şema, doğrulayıcı ve çerçeveleme, bu tasarımın küçük bir örneğidir. **gRPC**
aynı yapıyı hazır bir bütün olarak sunar: mesajlar **Protocol Buffers** şemasıyla tanımlanır,
istemci ve sunucu tarafı bu şemadan üretilir, taşıma katmanı olarak HTTP/2 kullanılır.

HTTP/2'nin seçilmesi bir ayrıntı değil, dört çağrı türünü mümkün kılan şeydir. Şema
dosyasındaki `akis` işareti bunu gösterir: bir çağrı tek istek–tek yanıt olabileceği gibi,
sunucu tarafı akış (bir istek, çok yanıt), istemci tarafı akış (çok istek, tek yanıt) ya da
çift yönlü akış olabilir. Kütüphane servisinde "raf sayımını akış olarak gönder" ya da
"katalogda eşleşenleri geldikçe ver" gibi işler bu türlere oturur.

Bedeli de bellidir. Tel üzerindeki içerik gözle okunamaz; bir isteği incelemek için şemayı
bilen bir araç gerekir. Tarayıcı bu protokolü doğrudan konuşamaz, araya bir çeviri katmanı
girer. Ve arayüzü kullanmak için karşı tarafın şemasına erişmek zorunludur — REST'te bir
adrese istek atıp yanıtı okumak yeterken, burada sözleşme olmadan tek bayt anlaşılmaz.

Bu bedeller, gRPC'nin en çok nerede kullanıldığını da açıklar: sistemin kendi bileşenleri
arasında, iki tarafın da aynı ekipçe yönetildiği ve çağrı sayısının yüksek olduğu yerlerde.

## Özet

- Uzak yordam çağrısı, ağ üzerindeki işlemi yerel bir çağrı gibi gösterir; benzetme gecikme,
  kısmi başarısızlık ve bayta çevirme noktalarında bozulur ve tasarımı bu noktalar belirler.
- Şema öncelikli tasarımda sözleşme makine okunur bir dosyadır; doğrulayıcı ve kodlayıcı
  ondan üretilir, böylece iki tarafın kuralları ayrışamaz.
- Ölçümde ikili çerçeveleme aynı kayıtları metin biçimine göre yaklaşık yarı bayta indirdi;
  kazanç alan adlarının ve ayraçların tele hiç çıkmamasından gelir.
- Tel üzerindeki kimlik alan numarasıdır: alan adı değiştiğinde aynı baytlar sorunsuz
  çözümlenir, ama numara değiştiğinde hata sessizce yanlış değer olarak görünür.
- gRPC bu tasarımı Protocol Buffers şeması ve HTTP/2 taşımasıyla birleştirir; dört çağrı
  türünü mümkün kılar, karşılığında okunabilirlik ve tarayıcıdan doğrudan erişim kaybolur.

## Sonraki Adım

Şema öncelikli tasarım yanıtın biçimini sözleşmede sabitler ve bu, ikinci derste ölçülen
soruna geri döner: istemcinin gereksinimi değiştiğinde sunucu değişmek zorundadır. Bir de
bunun tam tersini yapan yaklaşım vardır — biçimi istemcinin belirlediği, ama bunu gevşek bir
seçim metniyle değil, sunucunun yayımladığı bir tip sistemi içinde yaptığı bir yaklaşım.
Sonraki ders bu yaklaşımı kurar: tek uç nokta, tip sistemiyle sınırlanmış istemci güdümlü
sorgu, alan başına çözümleme ve kısmen başarılı yanıt kavramı.
