---
title: 'Çapraz Platform Üretim Pratiği'
source: 'https://academia.sh/tr/kurslar/capraz-platform-uretim'
language: tr
updated: '2026-10-21T05:02:50+00:00'
license: 'CC BY-SA 4.0'
lessons: 14
outcomes:
    - 'Paylaşılan mimariyi platform farklarını gizlemeden kurar'
    - 'Çevrimdışı çalışma ve eşitleme stratejisi tasarlar'
    - 'Mobil sürekli teslimat hattını iki mağaza için kurar'
---

# Çapraz Platform Üretim Pratiği

Çapraz platform bir uygulama üretimde ağ kesilince, aygıt güç tasarrufuna geçince, eski bir sürüm açık kalınca ya da yeni bir sürüm yalnız bir kovaya ulaşınca yaşamaya devam eder. Kurs her konuyu tek bir soruyla ölçer — bu koşulda bu özellik ne kadar çalışıyor? Bir özellik tam çalışır, kısıtlı çalışır, ertelenir ya da kapanır; paylaşılan mimari, tasarım sistemi, yapılandırma, erişilebilirlik, çevrimdışı öncelikli tasarım, eşitleme, arka plan sınırları, bağlantı durumu, mobil sürekli teslimat, sürüm yönetimi, aşamalı yayın, zorunlu güncelleme, uygulama boyutu ve izleme aynı çerçeveden geçirilir.

## Paylaşılan Mimari

Paylaşılan ve platforma özel katmanlar, tek tasarım dilinin iki platform beklentisine uyarlanması, çoklu ortam ve gizli değerler, iki platformda erişilebilirlik.

### Modüler Kod Paylaşımı

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/moduler-kod-paylasimi

Modüler kod paylaşımı, ortak kodu paylaşılan modüle ve platforma özgü katmana ayırır. Ders kursun işlerlik ölçüsünü kurar ve tamamen paylaşılan bir modülün bir platformda kapalı kalabildiğini ölçer.

Önceki kurslar bir çapraz platform uygulamasının nasıl kurulduğunu sordu: kod platformlar arasında nasıl paylaşılıyor, görünüm ağacı nasıl güncelleniyor, bir bileşenin genişliğini kim belirliyor. Üretimdeki bir uygulama için asıl soru başkadır. Aynı ekran bir aygıtta çevrimiçi, ötekinde çevrimdışı açılır; bir üçüncüsünde arka plan işi kısılmıştır; bir dördüncüsü eski bir istemci sürümünü çalıştırır. Tek bir uygulama tek bir koşulda çalışmaz.

Bu kurs her özelliği tek bir soruyla sınıflar: bu koşulda bu özellik ne kadar çalışıyor? İlk ders soruyu kurar ve ilk konunun konusuna, ortak kodun nasıl dilimlendiğine bağlar. Bu kursta "özellik" bir uygulama özelliği, yani feature demektir: kullanıcının yaptığı bir iş, örneğin katalog gezmek ya da ödeme yapmak. Bir bileşenin dışarıdan aldığı değer anlamına gelmez.

#### Dört İşlerlik

Sorunun yanıtına işlerlik denir ve dört değeri vardır. Kurs boyunca bu dört ad değişmez.

- **tam:** özelliğin ihtiyaç duyduğu her yetenek bu koşulda vardır; özellik tasarlandığı gibi çalışır.
- **kısıtlı:** bir yetenek eksiktir; özellik eksik yeteneğin yerine daha az şey yaparak hemen çalışır, örneğin önbellekteki içeriği gösterir.
- **ertelenen:** bir yetenek eksiktir; özelliğin işi kaybolmaz, sonraya bırakılır ve yetenek dönünce yapılır.
- **kapalı:** eksik yeteneklerden en az birinin yedeği yoktur; özellik bu koşulda çalışmaz.

Çıktıda bu adlar ASCII yazılır: `tam`, `kisitli`, `ertelenen`, `kapali`. Kapalı ile ertelenen aynı şey değildir. Kapalı bir özelliğin işi o koşulda yapılmaz ve sonraya da kalmaz; ertelenen bir özelliğin işi ise yerinde durur.

İşlerliği bir etiket değil, bir kural verir. Özellik ihtiyaç duyduğu yetenekleri sayar, koşul o anda var olan yetenekleri sayar, aradaki fark eksikleri verir. Her eksik için özellik bir yedek tanımlamış olabilir. Kural sırayla üç soru sorar:

1. Eksik yetenek yoksa özellik tamdır.
2. Eksik yeteneklerden birinin yedeği yoksa özellik kapalıdır.
3. Aksi hâlde eksik yeteneklerin yedeklerinden en ağırı seçilir; ertelenen, kısıtlıdan ağırdır.

Bu üç soru çekirdeğin içinde kod olarak durur. Çekirdek bu kursun her dersinde aynı biçimde yeniden verilir; ilk kez burada okunur.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

`feature(name, needs, fallback)` bir özellik kurar: `needs` ihtiyaç duyduğu yetenekleri, `fallback` bir yetenek eksik olduğunda neye düşeceğini söyler. `fallback` verilmezse boştur; o özelliğin hiçbir yeteneğinin yedeği yoktur ve her eksik onu kapalıya düşürür. `condition(name, has)` bir koşul kurar; `has` o koşulda var olan yeteneklerdir. Yetenek bir dizgidir (`network`, `background`, `storage`) ve çekirdek onu yorumlamaz, yalnız `has` içinde olup olmadığına bakar.

`assess` her koşul için her özelliği değerlendirir: koşul dışta, özellik içte döner. Her kayıt `level`, `feature`, `condition` ve `missing` alanlarını taşır. `printLevels` kayıtları basar; `countLevels` dört işlerliği sabit sırayla sayar ve son satırda etkilenen toplamını verir. Etkilenen, kısıtlı, ertelenen ve kapalı kayıtların toplamıdır: özelliğin tasarlandığı gibi çalışmadığı kayıt sayısı. Tam kayıt bu sayıya girmez.

#### Dokuz Kayıt

Senaryo, paylaşılan kodla iki platformda yayımlanan bir mağaza uygulamasıdır. Üç özellik vardır. `Catalog` ağdan içerik okur; ağ yokken önbellekteki içeriğe düşer. `Checkout` ödeme yapar ve hiçbir yedeği yoktur. `PhotoUpload` fotoğraf gönderir; hem ağa hem arka plan işine ihtiyaç duyar ve ikisinin yokluğunda da işini sonraya bırakır. Üç koşul vardır: `online` her yeteneği taşır, `offline` ağı kaybeder, `low-power` ağı korur ama arka plan işini kaybeder.

```javascript
// main.mjs
import { feature, condition, assess, printLevels, countLevels } from "./operability.mjs";

const features = [
  feature("Catalog", ["network"], { network: "kisitli" }),
  feature("Checkout", ["network"]),
  feature("PhotoUpload", ["network", "background"], {
    network: "ertelenen",
    background: "ertelenen",
  }),
];
const conditions = [
  condition("online", ["network", "background"]),
  condition("offline", ["background"]),
  condition("low-power", ["network"]),
];
const records = assess(features, conditions);
printLevels(records);
countLevels(records);
```

```text
tam: Catalog @ online eksik=-
tam: Checkout @ online eksik=-
tam: PhotoUpload @ online eksik=-
kisitli: Catalog @ offline eksik=network
kapali: Checkout @ offline eksik=network
ertelenen: PhotoUpload @ offline eksik=network
tam: Catalog @ low-power eksik=-
tam: Checkout @ low-power eksik=-
ertelenen: PhotoUpload @ low-power eksik=background
tam: 5
kisitli: 1
ertelenen: 2
kapali: 1
etkilenen: 4
```

İlk üç satır `online` koşuludur: her yetenek vardır, üç özellik de tamdır. `offline` koşulunda ağ yoktur. `Catalog` ağ için yedek tanımladığı için kısıtlıdır, `PhotoUpload` işini sonraya bıraktığı için ertelenendir, `Checkout`'un yedeği olmadığı için `Checkout @ offline` kapalıdır. `low-power` koşulunda ağ vardır ama arka plan yoktur: arka plana ihtiyaç duymayan `Catalog` ve `Checkout` tamdır, `PhotoUpload` ertelenendir.

Dokuz kaydın beşi tam, biri kısıtlı, ikisi ertelenen, biri kapalıdır; etkilenen dört. Bu sayı kursun açılış sayısıdır. Sonraki dersler ona atıfla anar, değiştirmez; her ders kendi kesitini ayrıca ölçer. Kullanıcıya hiçbir şey sunmayan tek kayıt `Checkout @ offline`'dır. Etkilenen dört kaydın üçünde özellik bir şekilde çalışmayı sürdürür.

**PM1.** Model, bir özelliğin bir koşuldaki davranışını yalnız yetenek kümesi olarak taklit eder. Kural yeteneği ve yedeği görür; işin kendisini, kullanıcıyı ve süreyi görmez. Kısıtlı `Catalog`'un önbellekteki içeriği ne kadar eski göstereceği, ertelenen `PhotoUpload`'ın işi ne zaman yapacağı ve kaç kez deneyeceği modelde yoktur ve işlerliği değiştirmez. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

#### Paylaşılan Modül ve Platforma Özgü Katman

Çapraz platform bir uygulamada kod iki parçaya ayrılır. **Paylaşılan modül** iki platformda aynı yazılan koddur: iş kuralları, veri modeli, akış mantığı. **Platforma özgü katman** her platformun kendi dilinde ve kendi API'siyle yazılan koddur: kamera, bildirim, dosya sistemi, biyometri. Paylaşılan modül bu yeteneklere doğrudan uzanmaz. İstediğini bir sözleşme olarak söyler; platforma özgü katman sözleşmeyi karşılar.

İki parçanın arasındaki çizgiye katman sınırı denir. Sınırın iyi çizilmesi, platforma özgü kodun az ve dar kalması demektir: paylaşılan modül ne istediğini söyler, hangi platformda nasıl karşılandığını bilmez. Aşağıdaki parça sözleşmeyi ve onu kullanan paylaşılan özelliği gösterir. Parça ölçülmez.

```kotlin
// Platform kodu: paylasilan sozlesme
interface PlatformLayer {
    val hasScanner: Boolean
    fun scan(onCode: (String) -> Unit)
}

class ScanFeature(private val layer: PlatformLayer) {
    fun open(onCode: (String) -> Unit): Boolean {
        if (!layer.hasScanner) return false
        layer.scan(onCode)
        return true
    }
}
```

`ScanFeature` iki platformda aynı koddur. Yeteneğin varlığını katmana sorar; yoksa `false` döner ve hiçbir şey olmaz. Sözleşmenin iki karşılığı platforma göre ayrılır. Android tarafı Kotlin'dedir, iOS tarafı aynı sözleşmenin Swift karşılığına uyar.

```kotlin
// Platform kodu: Android katmani (tarayici henuz yazilmadi)
class AndroidLayer : PlatformLayer {
    override val hasScanner = false
    override fun scan(onCode: (String) -> Unit) = Unit
}
```

```swift
// Platform kodu: iOS katmani
final class IosLayer: PlatformLayer {
    let hasScanner = true

    func scan(onCode: @escaping (String) -> Void) {
        // kamera oturumu kurulur, okunan kod onCode ile iletilir
    }
}
```

Model bu ayrımı bir işlevle kurar. `platformLayer(platform)` bir platformun katmanından koşul üretir: katman bir yeteneği karşılıyorsa koşulun `has` listesinde o yetenek vardır, karşılamıyorsa yoktur. Gerçek uygulamada yetenek kümesini kod aygıtın ve katmanın durumundan üretir; model de aynı yolu izler.

```javascript
// layers.mjs — platforma ozgu katmanin sundugu yetenekleri uretir
import { condition } from "./operability.mjs";

const LAYERS = {
  ios: ["network", "storage", "scanner"],
  android: ["network", "storage"],
};

export function platformLayer(platform, written = []) {
  return condition(platform, [...LAYERS[platform], ...written]);
}
```

İlk ölçümde iki özellik iki katmana karşı değerlendirilir. İkisi de yalnız her iki katmanın karşıladığı yeteneklere ihtiyaç duyar.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { platformLayer } from "./layers.mjs";

const features = [
  feature("Catalog", ["network"], { network: "kisitli" }),
  feature("Profile", ["storage"]),
];
const conditions = [platformLayer("ios"), platformLayer("android")];
printLevels(assess(features, conditions));
```

```text
tam: Catalog @ ios eksik=-
tam: Profile @ ios eksik=-
tam: Catalog @ android eksik=-
tam: Profile @ android eksik=-
```

Dört kaydın dördü tamdır. Paylaşılan kodun iki platformda aynı işi gördüğü durum budur ve sık görülen durum da budur. Sınır, katmanın bir yeteneği karşılamadığı yerde görünür olur.

#### Sınırlayıcı Ölçüm

Sınırlayıcı ölçümde listeye tek bir özellik eklenir: `Scan`. Tarama özelliğinin iş mantığı paylaşılan modüldedir ve iki platformda aynı koddur. Ama kamerayı kullanabilmek için `scanner` yeteneği gerekir ve bu yetenek platforma özgü katmandan gelir. Senaryoda katman iOS için yazılmıştır, Android için henüz yazılmamıştır. `Scan` için yedek tanımlanmamıştır.

```javascript
// main.mjs
import { feature, assess, printLevels, countLevels } from "./operability.mjs";
import { platformLayer } from "./layers.mjs";

const features = [
  feature("Catalog", ["network"], { network: "kisitli" }),
  feature("Scan", ["scanner"]),
];
const conditions = [platformLayer("ios"), platformLayer("android")];
const records = assess(features, conditions);
printLevels(records);
countLevels(records);
```

```text
tam: Catalog @ ios eksik=-
tam: Scan @ ios eksik=-
tam: Catalog @ android eksik=-
kapali: Scan @ android eksik=scanner
tam: 3
kisitli: 0
ertelenen: 0
kapali: 1
etkilenen: 1
```

Dersin odak kaydı `Scan @ android`'dir ve kapalı çıkar. `Scan` iki platformda aynı paylaşılan koddur; iOS'ta tamdır. Android'de `scanner` yeteneği eksiktir, özelliğin o yetenek için yedeği yoktur, bu yüzden kural ikinci soruda kapalıya varır. Bu ölçümün etkilenen sayısı bir, açılıştaki dört değildir; iki ölçüm farklı kümeleri sayar.

**PM2.** Model, Android'de kameranın kullanılamadığını söylemez. Senaryo yalnız bu uygulamanın Android katmanında tarayıcı sözleşmesinin henüz karşılanmadığını kurar. `scanner` dersin kurgusudur ve çekirdek onu yorumlamaz. Katmanı yazmanın ne kadar iş olduğu modelde yoktur.

Bu sonuç M32/K01'in sorusundan farklıdır. O ders kodun platformlar arasındaki dağılımını sordu: ne kadarı paylaşılıyor, ne kadarı platforma özgü. İşlerlik bu soruyu sormaz. Tamamen paylaşılan bir modül de bir platformun koşulunda kapalı olabilir; işlerlik kodun nerede yazıldığını değil, o koşulda ne kadar çalıştığını sınıflar. Kodun ne kadarının paylaşıldığı kaydı değiştirmez, çünkü `feature` bir paylaşım oranı taşımaz ve kural yalnız `needs`, `fallback` ve `has`'e bakar. `Scan`'in iş mantığının tamamı paylaşılsa da bir kısmı paylaşılsa da `Scan @ android` aynı kalır.

Kapalıyı değiştiren şey platformun katmanıdır. İkinci ölçüm Android katmanına `scanner` yeteneğini yazdırır ve aynı özelliği yeniden değerlendirir.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { platformLayer } from "./layers.mjs";

const scan = [feature("Scan", ["scanner"])];
printLevels(assess(scan, [platformLayer("android")]));
printLevels(assess(scan, [platformLayer("android", ["scanner"])]));
```

```text
kapali: Scan @ android eksik=scanner
tam: Scan @ android eksik=-
```

Paylaşılan modüldeki tek satır değişmedi; yalnız katman yeteneği karşıladı ve kayıt tama döndü. Kapalının bir başka çıkışı da yedek tasarlamaktır. `Scan` için kodun elle girilmesi gibi bir yedek tanımlansaydı aynı kayıt kısıtlı olurdu. Kapalı olması, bir yedeğin tasarlanmadığını söyler.

#### Özet

- Bu dersin işlerliği: kapalı. `Scan @ android` kaydında `scanner` yeteneği eksiktir ve yedeği yoktur; paylaşılan modül tamamen ortak olsa da kayıt kapalı çıkar.
- İşlerlik bir kuralın çıktısıdır: eksik yetenek yoksa tam, yedeği olmayan bir eksik varsa kapalı, aksi hâlde eksiklerin en ağır yedeği (ertelenen, kısıtlıdan ağırdır).
- Açılış ölçümünde dokuz kaydın beşi tam, biri kısıtlı, ikisi ertelenen, biri kapalı çıktı; etkilenen dört.
- Paylaşılan modül sözleşmeyi söyler, platforma özgü katman yeteneği karşılar; katman sınırı bu çizgidir.
- Kodun paylaşım oranı kaydı değiştirmez; `Scan @ android` kapalıdan tama ancak Android katmanı `scanner` yeteneğini karşılayınca döndü.

#### Sonraki Adım

Bu derste platforma göre değişen şey bir yetenekti ve eksik olduğunda özellik kapandı. Sıradaki ders, `Tasarım Sistemi Uyarlaması`, platforma göre değişen şeyin görünüm olduğu durumu ölçer: tek bir tasarım dili iki platformda farklı biçimde uyarlanır ve bir tarih seçici iki biçimde de tam çalışır.

### Tasarım Sistemi Uyarlaması

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/tasarim-sistemi-uyarlamasi

Tasarım sistemi uyarlaması, tek bir tasarım dilini iki platformun beklentisine göre ayarlar. Ders uyarlanmış bir bileşenin her iki platformda da tam çalıştığını, başka bir bileşenin ise sürüm koşulunda kısıtlı kaldığını ölçer.

Önceki derste platforma göre değişen şey bir yetenekti: katman yeteneği karşılamayınca özellik kapandı. Bu derste platforma göre değişen şey görünümdür. Bir ürünün tek bir tasarım dili vardır; renkleri, aralıkları, köşe yuvarlaklıkları ve yazı ölçüleri ürünün iki platformda da tanınmasını sağlar. Ama iOS ve Android kullanıcıları kendi platformlarından farklı alışkanlıklar getirir ve iyi bir tasarım sistemi bu farkı bilerek uyarlar. Ders önce uyarlamanın nasıl kurulduğunu anlatır, sonra uyarlanmış bir bileşenin işlerliğini ölçer.

#### Tasarım Belirteçleri

Tasarım sistemi, bir ürünün görünümünü tutarlı kılan parçalar bütünüdür: belirteçler, bu belirteçlerden kurulan bileşenler ve bileşenlerin ne zaman nasıl kullanılacağını söyleyen yönergeler. **Tasarım belirteci**, bir tasarım kararının adı konmuş değeridir: `accent` bir renk, `gap` bir aralık, `radius` bir köşe yuvarlaklığıdır. Bileşen rengi ya da aralığı kendi içine yazmaz, belirteci adıyla okur. Kazancı tek yerde değişmektir: vurgu rengi belirteçte değişir ve onu okuyan her bileşen ona uyar.

Belirteçler üç katmanda düşünülür. Temel katman ürünün iki platformda da aynı tuttuğu değerleri taşır. Uyarlama katmanı bir platformda temelin üstüne yazılan değerleri taşır. Bileşen son değeri bu ikisinin birleşiminden okur: platformda tanımlı bir değer varsa o, yoksa temel değer. Böylece uyarlama bir istisna listesi olarak küçük kalır; ürünün çoğu değeri hâlâ tek yerde durur.

Aşağıdaki parça bu okumayı bir bileşen üzerinde gösterir. Tarih seçici biçimini ve genişliğini belirteçten alır; hangi platformda koştuğunu kendisi sormaz. Parça ölçülmez.

```jsx
// Platform kodu: belirteci okuyan bilesen
function DatePicker({ tokens, value, onChange }) {
  const Picker = tokens.picker === "tekerlek" ? WheelPicker : CalendarPicker;
  return (
    <Picker
      value={value}
      onChange={onChange}
      width={tokens.width}
      radius={tokens.radius}
    />
  );
}
```

Bileşenin içinde bir platform denetimi yoktur. Hangi biçimin seçileceğine belirteç karar verir ve belirteci platformun uyarlama katmanı doldurur. Bu, önceki dersin katman sınırının görünüm tarafındaki karşılığıdır: bileşen ne istediğini söyler, platformun nasıl karşıladığını bilmez.

#### Platforma Göre Uyarlama

Uyarlamanın gerekçesi alışkanlıktır. Tarih seçmek iOS'ta bir tekerleği çevirmek ya da küçük bir takvim açmak olabilir; Android'de bir takvim penceresinde güne dokunmaktır. İki kullanıcı da kendi platformunda bunu bekler. Aynı bileşenin her yerde tek biçimde çıkması ürüne tutarlılık kazandırır ama kullanıcının platformdan getirdiği beklentiyi bozar. Uyarlamak beklentiyi korur, bedelini de ödetir: ekip artık iki biçimi tasarlamak, yapmak ve sınamak zorundadır.

Bu bedel küçümsenmemelidir. Uyarlanan her bileşen bir sınama çarpanıdır: iki platformda iki biçim, iki ayrı genişlik, iki ayrı köşe yuvarlaklığı demektir. Bu yüzden iyi bir tasarım sistemi neyin uyarlanacağını bilinçli seçer. Tarih seçici gibi platformun kendi alışkanlığı güçlü olan bileşenler uyarlanır; markanın kimliğini taşıyan renk ve yazı ölçüleri tek kalır.

Uyarlama yalnız görünüm değildir. Bazı bileşenler platformun sunduğu bir görsel yeteneğe dayanır; bulanık bir arka plan gibi. Bu yetenek her platformda ve her sürümde yoktur. Bileşen bu durumda iki yoldan birini seçer: yeteneği şart koşar ve yoksa çalışmaz, ya da yoksa sade bir arka plana düşer. İkinci yol bir yedektir ve ders bunu sınırlayıcı ölçümün ikinci yarısında ölçer.

#### Belirteçten Koşula

Model uyarlamayı iki adımda kurar. Önce belirteçleri bir yardımcı dosyada tutar. Sonra `themeFor(platform)` bir platformun belirteçlerinden koşul üretir: seçicinin biçimi tanımlıysa koşulun `has` listesinde `picker` vardır, bulanık arka plan destekleniyorsa `blur` vardır. Dersin çekirdeği her derste olduğu gibi aşağıda yeniden verilir.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Dokuz kayıtlık açılış ölçümünün sayısı ilk derste verildi; bu ders onu değiştirmez, yalnız kendi kesitini ölçer. Belirteçler ve `themeFor` ders-yerel bir yardımcı dosyada durur. Üç uyarlama vardır: iOS, Android ve sürüm koşulunu temsil eden eski bir Android.

```javascript
// tokens.mjs — tek tasarim dilinin platforma gore uyarlanan degerleri
import { condition } from "./operability.mjs";

const BASE = { accent: "mavi", gap: 8, radius: 4, blur: true };

const ADAPT = {
  ios: { picker: "tekerlek", radius: 10, width: 280 },
  android: { picker: "takvim", width: 320 },
  "android-legacy": { picker: "takvim", width: 320, blur: false },
};

export function tokensFor(platform) {
  return { ...BASE, ...ADAPT[platform] };
}

export function themeFor(platform, tokens = tokensFor(platform)) {
  const has = [];
  if (tokens.picker) has.push("picker");
  if (tokens.blur) has.push("blur");
  return condition(platform, has);
}
```

İlk ölçüm işlerliği değil belirteçleri basar. İki platformun son değerleri sıralı anahtarlarla yazılır.

```javascript
// main.mjs
import { tokensFor } from "./tokens.mjs";

for (const platform of ["ios", "android"]) {
  const tokens = tokensFor(platform);
  const keys = Object.keys(tokens).sort();
  console.log(`${platform}: ${keys.map((k) => `${k}=${tokens[k]}`).join(" ")}`);
}
```

```text
ios: accent=mavi blur=true gap=8 picker=tekerlek radius=10 width=280
android: accent=mavi blur=true gap=8 picker=takvim radius=4 width=320
```

Uyarlama katmanı yalnız farkı taşır. `accent` ve `gap` iki platformda temelden gelir, `radius` yalnız iOS'ta ezilir, `picker` ve `width` iki platformda da kendi değerini taşır. Değerlerin hepsi dersin kurgusudur ve birim taşımaz.

**PM3.** Belirteç değerleri (`gap`, `radius`, `width`) dersin kurgusudur; gerçek bir tasarım sisteminin ölçüleri değildir. Model belirteçten yalnız iki sonuç çıkarır: seçicinin biçimi tanımlı mı, bulanık arka plan destekleniyor mu. Bileşenin gerçekte nasıl çizildiği, iki biçimin kullanıcıya nasıl hissettirdiği modelde yoktur. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

#### Sınırlayıcı Ölçüm

Dersin odak kaydı `DatePicker @ android`'dir. `DatePicker` yalnız `picker` yeteneğine ihtiyaç duyar. Belirteçler iki platformda farklıdır: iOS'ta tekerlek, Android'de takvim. Ama ikisinde de bir seçici biçimi vardır, bu yüzden yetenek iki koşulda da var olur.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { themeFor } from "./tokens.mjs";

const features = [feature("DatePicker", ["picker"])];
const conditions = [themeFor("ios"), themeFor("android")];
printLevels(assess(features, conditions));
```

```text
tam: DatePicker @ ios eksik=-
tam: DatePicker @ android eksik=-
```

`DatePicker @ android` tamdır. Bu ölçüm M32/K03'ün sorusundan farklıdır. O ders düzen aşamasında bir bileşenin genişliğinin sahibini sordu. Tasarım sistemi bir bileşeni platforma göre uyarladığında genişliği değişebilir; işlerlik genişliği değil, bileşenin o koşulda işini görüp görmediğini sınıflar. İki platformda seçici iki farklı genişlikte, iki farklı biçimde çizilir ve ikisinde de işini görür.

Kuralın belirteç değerlerini görmediğini ikinci ölçüm doğrudan gösterir. Android uyarlamasında köşe yuvarlaklığı, genişlik ve seçicinin biçimi değiştirilir; önceki ve sonraki kayıtlar karşılaştırılır.

```javascript
// main.mjs
import { feature, assess } from "./operability.mjs";
import { tokensFor, themeFor } from "./tokens.mjs";

const features = [feature("DatePicker", ["picker"])];
const before = assess(features, [themeFor("android")]);
const edited = { ...tokensFor("android"), radius: 16, width: 360, picker: "liste" };
const after = assess(features, [themeFor("android", edited)]);
console.log(JSON.stringify(before) === JSON.stringify(after) ? "kayit ayni" : "kayit degisti");
```

```text
kayit ayni
```

Üç belirteç değişti, kayıt aynı kaldı. Kural yalnız `needs`, `fallback` ve `has`'e girenleri görür; yetenek kümesine yansımayan bir değişiklik kaydı bozmaz. Bu bir çelişki değil, ölçünün kendisidir: genişliğin dar ya da geniş olması kullanıcıyı etkileyebilir, ama model yalnız seçicinin işini görüp görmediğini söyler.

İkinci karşılaştırma yedek taşıyan bir bileşendir. `FrostedCard` bulanık bir arka plan ister; bulanıklık yoksa sade arka plana düşer. Bulanıklığı karşılamayan eski bir Android sürümü koşulu eklenir.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { themeFor } from "./tokens.mjs";

const features = [
  feature("DatePicker", ["picker"]),
  feature("FrostedCard", ["blur"], { blur: "kisitli" }),
];
const conditions = [themeFor("ios"), themeFor("android"), themeFor("android-legacy")];
printLevels(assess(features, conditions));
```

```text
tam: DatePicker @ ios eksik=-
tam: FrostedCard @ ios eksik=-
tam: DatePicker @ android eksik=-
tam: FrostedCard @ android eksik=-
tam: DatePicker @ android-legacy eksik=-
kisitli: FrostedCard @ android-legacy eksik=blur
```

Eski sürüm koşulunda `DatePicker` hâlâ tamdır, `FrostedCard` ise kısıtlıdır: bulanıklık eksiktir, özellik sade arka planla hemen çalışır. Altı kaydın beşi tamdır. Bu sayı açılıştaki 5/1/2/1 ölçümünün sayısı değildir; iki ölçüm farklı kümeleri sayar.

**PM4.** Eski sürüm koşulunun bulanıklığı karşılamadığı dersin kurgusudur. Hangi gerçek sürümün bu yeteneği taşıdığını model söylemez; bu yüzden metinde sürüm numarası yazılmaz. Sade arka planın kullanıcıya ne kadar kötü ya da iyi göründüğü de modelde yoktur.

Yedek, kapalıyı kısıtlıya çeviren tasarım kararıdır. `FrostedCard`'ın `fallback` eşlemi boş olsaydı aynı eksik kapalı sonuç verirdi. Tasarım sistemi bu kararı bileşenin içine yazar: bileşen yeteneği bulamadığında ne yapacağını önceden bilir.

#### Özet

- Bu dersin işlerliği: tam. `DatePicker @ android` kaydında seçici iOS'ta tekerlek, Android'de takvim olsa da `picker` yeteneği iki koşulda vardır ve eksik yoktur.
- Tasarım belirteci bir tasarım kararının adı konmuş değeridir; uyarlama katmanı temelin üstüne yalnız platform farkını yazar.
- Genişlik, köşe yuvarlaklığı ve seçici biçimi değişse de kayıt aynı kaldı; kural yetenek kümesine yansımayan değişikliği görmez.
- Bulanıklığı karşılamayan eski sürüm koşulunda `FrostedCard` kısıtlı çıktı, `DatePicker` tam kaldı.
- Uyarlama beklentiyi korur ve sınama çarpanı doğurur; yedek ise kapalıyı kısıtlıya çeviren tasarım kararıdır.

#### Sonraki Adım

Bu derste yeteneği belirtecin içeriği belirledi. Sıradaki ders, `Yapılandırma ve Ortam Yönetimi`, yeteneğin yapılandırmadan geldiği durumu ele alır: geliştirme, hazırlık ve üretim ortamları, gizli değerler ve uzaktan yapılandırmaya ulaşılamayan ilk açılışta bir bayrağın değeri.

### Yapılandırma ve Ortam Yönetimi

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/yapilandirma-ve-ortam-yonetimi

Yapılandırma ve ortam yönetimi, aynı uygulamanın geliştirme, hazırlık ve üretim ortamlarında farklı değerlerle çalışmasını sağlar. Ders varsayılandan açılan bir bayrağın özelliği tam bıraktığını ölçer.

Önceki derste görünüm platforma göre uyarlandı. Bu derste değişen şey platform değil, uygulamanın çalıştığı ortamdır. Aynı derleme geliştiricinin makinesinde, sınama ekibinin elinde ve gerçek kullanıcıda koşar; üçünde de aynı sunucuya konuşmaz, aynı günlük ayrıntısını yazmaz, aynı bayrakları açık bulmaz. Yapılandırma, bu farkı koda dokunmadan taşıyan düzenektir. Ders önce yapılandırmanın parçalarını anlatır, sonra uzaktan yapılandırmaya ulaşılamayan bir ilk açılışta bir özelliğin ne kadar çalıştığını ölçer.

#### Ortamlar ve Yapılandırma Katmanları

**Ortam**, uygulamanın hangi amaçla ve hangi arka uca karşı çalıştığını adlandırır. Üç ortam yaygındır: geliştirme ortamı tek geliştiricinindir ve her şeyin hızlıca denenmesine izin verir; hazırlık ortamı üretime benzer ama gerçek veri taşımaz; üretim ortamı gerçek kullanıcıların kullandığı ortamdır. Ortamları ayırmanın kazancı hatayı doğru yerde yakalamaktır: bir değişiklik önce geliştirmede, sonra hazırlıkta sınanır ve ancak sonra üretime çıkar.

Her ortamın kendi **yapılandırması** vardır: sunucu adresi, günlük ayrıntısı, bir hizmetin anahtarı. Bu değerlerin kod içine yazılması iki sorun doğurur. Birincisi, ortam değişince kodun değişmesi gerekir ve kod değişince sınanmış yapı artık sınanmış yapı olmaz. İkincisi, anahtar gibi değerler kodla birlikte depoya girer. Bu yüzden değerler koddan ayrılır ve derleme sırasında ortama göre seçilir.

Aşağıdaki parça bu ayrımı bir yapılandırma dosyasında gösterir. Gizli değerin kendisi dosyada durmaz; yalnız adı durur ve gerçek değer derleme düzeneğinin gizli deposundan yerine konur. Parça ölçülmez.

```yaml
# Platform kodu: ortam basina yapilandirma
environments:
  staging:
    api_host: api.staging.example.test
    api_key: ${API_KEY}      # gizli depodan derleme aninda yerine konur
    log_level: ayrintili
  production:
    api_host: api.example.test
    api_key: ${API_KEY}
    log_level: sade
defaults:
  search: true               # uzaktan yapilandirma gelmezse gecerli deger
  banner: false
```

**Gizli değer**, açığa çıkması hâlinde bir hizmetin kötüye kullanılmasına yol açan değerdir: bir anahtar, bir parola, bir imza anahtarı. Kural basittir ve istisnasız uygulanır: gizli değer koda, yapılandırma dosyasına ve uygulama paketine gömülmez. Pakete giren her şey paketi açan herkesin okuyabileceği şeydir. Gizli değer derleme düzeneğinin gizli deposunda durur, yapılandırma yalnız onun adını taşır. Gerçekten istemcide kalmaması gereken bir değer ise hiç istemciye gönderilmez; sunucuda durur.

#### Varsayılan Değer ve Uzaktan Yapılandırma

Bazı değerler derleme anında sabitlenemez, çünkü kullanıcı uygulamayı yükledikten sonra da değişmeleri gerekir. **Uzaktan yapılandırma**, uygulamanın çalışırken bir sunucudan değer almasıdır. En yaygın kullanımı **özellik bayrağı**dır: bir özelliğin açık ya da kapalı olduğunu kod değişmeden belirleyen bir değer. Bayrağın özel bir türü **uzaktan kapatma bayrağı**dır: üretimde sorun çıkaran bir özelliği yeni sürüm beklemeden kapatan bayrak.

Uzaktan yapılandırmanın bir kusuru vardır: sunucuya ulaşılamayabilir. İlk açılışta ağ olmayabilir, sunucu geçici olarak yanıt vermeyebilir, kullanıcı uçak kipinde olabilir. Bu durumda uygulamanın bir değeri olmalıdır. İşte **varsayılan değer** budur: paketin içinde gelen, uzaktan hiçbir şey alınamadığında geçerli olan değer. Uygulama değeri şu sırayla çözer: uzaktan gelen değer varsa o, yoksa varsayılan.

Varsayılanın seçimi bir ürün kararıdır ve iki yönde riski vardır. Varsayılan açık olursa özellik, uzaktan yapılandırma gelmese de çalışır; ama kapatılması gereken bir özellik ilk açılışta açık kalır. Varsayılan kapalı olursa özellik güvenle kapalıdır; ama ilk açılışta kullanıcı özelliği hiç görmez. Ders bu seçimin işlerliğe nasıl yansıdığını ölçer.

#### Koşuldan Yeteneğe

Model yapılandırmayı bir koşula çevirir. `configFor(name, remote, defaults)` ortamın bayraklarını çözer: uzaktan değer varsa onu, yoksa varsayılanı alır. Açık her bayrak koşulun `has` listesine `flag:<ad>` biçiminde bir yetenek olarak girer. Bir özellik bir bayrağa ihtiyaç duyuyorsa `needs` listesinde o yeteneği ister. Dersin çekirdeği her derste olduğu gibi aşağıda yeniden verilir.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Yapılandırma yardımcısı ders-yerel bir dosyadır. Ortam tablosu gizli değeri değil, onun adını tutar.

```javascript
// config.mjs — ortam tablosu, varsayilan degerler ve bayrak cozumu
import { condition } from "./operability.mjs";

export const ENVS = {
  dev: { host: "dev.example.test", logs: "ayrintili", secret: "ref:API_KEY_DEV" },
  staging: { host: "staging.example.test", logs: "ayrintili", secret: "ref:API_KEY_STG" },
  prod: { host: "api.example.test", logs: "sade", secret: "ref:API_KEY_PROD" },
};

export const DEFAULTS = { search: true, banner: false };

export function configFor(name, remote = null, defaults = DEFAULTS) {
  const flags = { ...defaults, ...(remote ?? {}) };
  const has = Object.keys(flags)
    .filter((key) => flags[key])
    .sort()
    .map((key) => `flag:${key}`);
  return condition(name, has);
}
```

İlk ölçüm işlerlik değildir; üç ortamın tablosunu basar.

```javascript
// main.mjs
import { ENVS } from "./config.mjs";

for (const name of Object.keys(ENVS)) {
  const env = ENVS[name];
  console.log(`${name}: host=${env.host} logs=${env.logs} secret=${env.secret}`);
}
```

```text
dev: host=dev.example.test logs=ayrintili secret=ref:API_KEY_DEV
staging: host=staging.example.test logs=ayrintili secret=ref:API_KEY_STG
prod: host=api.example.test logs=sade secret=ref:API_KEY_PROD
```

`secret` alanı bir değer değil gizli depodaki bir kaydın adıdır. Üç ortam üç ayrı gizli değere işaret eder ve hiçbirinin değeri tabloda görünmez.

**PM5.** Alan adları, sunucu adresleri ve gizli değer adları dersin kurgusudur ve `example.test` alanı gerçek bir sunucuya çıkmaz. Model, gizli değerin çözülmesini, uzaktan yapılandırmanın ağdan alınmasını ve bir bayrağın sunucuda nasıl tanımlandığını içermez; yalnız bayrağın açık ya da kapalı olduğunu ve bunun bir yetenek olarak koşula girdiğini kurar.

#### Uzaktan Kapatma Bayrağı

İkinci ölçüm iki özelliği üretim ortamında iki durumda sınar. `Search` yeni arama özelliğidir ve `flag:search` ister. `Banner` ise `flag:banner` ister ve bayrak yoksa sade bir düzene düşer. İlk koşulda uzaktan yapılandırmaya hiç ulaşılamamıştır, yalnız varsayılanlar geçerlidir. İkinci koşulda uzaktan yapılandırma gelmiş ve arama bayrağını kapatmıştır.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { configFor } from "./config.mjs";

const features = [
  feature("Search", ["flag:search"]),
  feature("Banner", ["flag:banner"], { "flag:banner": "kisitli" }),
];
const conditions = [
  configFor("prod-default"),
  configFor("prod-remote", { search: false }),
];
printLevels(assess(features, conditions));
```

```text
tam: Search @ prod-default eksik=-
kisitli: Banner @ prod-default eksik=flag:banner
kapali: Search @ prod-remote eksik=flag:search
kisitli: Banner @ prod-remote eksik=flag:banner
```

Üç şey görünür. Varsayılan koşulda `Search` tamdır, çünkü varsayılanda arama açıktır. `Banner` varsayılanda kapalıdır; ama bir yedeği vardır, sade düzen, bu yüzden kapalı değil kısıtlıdır. Uzaktan kapatma bayrağı geldiğinde `Search` kapalıya düşer, çünkü arama için tanımlı bir yedek yoktur. Uzaktan kapatma bayrağının amacı da budur: özelliği yeni sürüm beklemeden devreden çıkarmak.

**PM6.** Bayrak adları ve varsayılan değerleri dersin kurgusudur. Gerçek bir uygulamada arama özelliğinin kapalıyken ne göstereceği ayrı bir ürün kararıdır; model yalnız yedeğin var olup olmadığını bilir.

#### Sınırlayıcı Ölçüm

Dersin odak kaydı `Search @ prod-default`'tur: ilk açılışta, uzaktan yapılandırmaya ulaşılamazken, yeni arama bayrağı varsayılandan gelir. Varsayılan açıktır, bu yüzden yetenek vardır ve kayıt tamdır. Karşılaştırma için aynı koşulun varsayılanı kapalı olan bir benzeri eklenir.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { configFor, DEFAULTS } from "./config.mjs";

const features = [feature("Search", ["flag:search"])];
const conditions = [
  configFor("prod-default"),
  configFor("prod-default-off", null, { ...DEFAULTS, search: false }),
];
printLevels(assess(features, conditions));
```

```text
tam: Search @ prod-default eksik=-
kapali: Search @ prod-default-off eksik=flag:search
```

Varsayılan açıkken kayıt tam, kapalıyken kapalıdır. Fark değerin kendisidir: açık bir bayrak yetenek olarak koşula girer, kapalı bir bayrak girmez.

Bu sonuç M31/K04'ün sorusundan farklıdır. O ders bir okumanın kökenini sordu: yanıt bellekten mi, diskten mi, ağdan mı, varsayılandan mı geldi. Uzaktan yapılandırmanın değeri varsayılandan gelse de özellik tam olabilir; işlerlik değerin kaynağını değil, yeteneğin varlığını sınıflar. Aşağıdaki ölçüm bunu doğrudan gösterir. Aynı bayrak bir koşulda varsayılandan, ötekinde uzaktan gelir ve iki kayıt karşılaştırılır.

```javascript
// main.mjs
import { feature, assess } from "./operability.mjs";
import { configFor } from "./config.mjs";

const features = [feature("Search", ["flag:search"])];
const fromDefault = assess(features, [configFor("prod")]);
const fromRemote = assess(features, [configFor("prod", { search: true })]);
console.log(JSON.stringify(fromDefault) === JSON.stringify(fromRemote) ? "kayit ayni" : "kayit degisti");
```

```text
kayit ayni
```

Değer bir koşulda varsayılandan, ötekinde uzaktan geldi; kayıt aynı kaldı. Kaynak kayda girmez. Bu, kaynağın önemsiz olduğu anlamına gelmez: bir ekip varsayılanı güvenli seçmek ve uzaktan değerin gecikmesini bilmek zorundadır. Ama bunlar okumanın kökenine dair sorulardır; işlerlik soruyu başka yerden sorar: bu koşulda bu özellik ne kadar çalışıyor?

#### Özet

- Bu dersin işlerliği: tam. `Search @ prod-default` kaydında uzaktan yapılandırmaya ulaşılamasa da arama bayrağı varsayılandan açıktır ve yetenek vardır.
- Aynı bayrağın varsayılanı kapalı olsaydı `Search @ prod-default-off` kapalı çıkardı; fark değerin kaynağında değil, yeteneğin varlığındadır.
- Varsayılandan ve uzaktan gelen aynı açık değer aynı kaydı verdi; işlerlik değerin kaynağını kayda katmaz.
- Uzaktan kapatma bayrağı gelince yedeği olmayan `Search` kapalıya, yedeği olan `Banner` kısıtlıya düştü.
- Gizli değer koda, yapılandırma dosyasına ve pakete gömülmez; yapılandırma yalnız adını taşır.

#### Sonraki Adım

Bu derste yetenek bir yapılandırma değerinden geldi ve özellik tam kaldı. Sıradaki ders, `Erişilebilirlik`, yeteneğin kullanıcının kendi ayarından düştüğü durumu ele alır: ekran okuyucu açıldığında görünüm ağacında hiçbir düğüm değişmeden bir özellik kısıtlıya düşebilir.

### Erişilebilirlik

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/erisilebilirlik

Erişilebilirlik, bir uygulamanın ekran okuyucu, büyük yazı ve dokunma gibi farklı kullanım biçimlerinde de işini görmesini sağlar. Ders ekran okuyucu açıldığında bir hareketin menüye düşerek kısıtlı çalıştığını ölçer.

Önceki derste yeteneği bir yapılandırma değeri belirledi. Bu derste yeteneği kullanıcının kendi ayarı belirler. Bir kullanıcı ekran okuyucuyu açmıştır, bir başkası yazı boyutunu büyütmüştür, üçüncüsü dokunmak yerine bir anahtar aygıtıyla gezinir. Bunlar nadir bir kenar durum değildir; uygulamanın gerçek koşullarının bir parçasıdır. **Erişilebilirlik**, uygulamanın bu koşullarda da işini görmesini sağlayan tasarım ve uygulama pratiğidir. Ders önce erişilebilirliğin parçalarını anlatır, sonra ekran okuyucu açıkken bir hareketin ne kadar çalıştığını ölçer.

#### Ekran Okuyucu ve Anlamsal Özellikler

**Ekran okuyucu**, ekrandaki öğeleri sesli ya da dokunsal olarak aktaran, platformun sunduğu yardımcı teknolojidir. Kullanıcı ekrana bakmaz; öğeler arasında gezinir ve ekran okuyucu her öğenin ne olduğunu, adını ve yapılabilecek eylemleri söyler. Ekran okuyucu bu bilgiyi görüntüden çıkarmaz. Uygulamanın verdiği **anlamsal özelliklerden** okur: bir öğenin etiketi, rolü, durumu ve eylemleri.

Anlamsal özellikler görünümden ayrı bir bilgi katmanıdır. Görünüm bir kartı bir resim ve iki metin olarak çizer; anlamsal özellikler aynı kartı "Sipariş kartı, başlık şu, silmek için eylem var" diye tanımlar. Bir öğenin anlamsal özelliği yoksa ekran okuyucu onu ya atlar ya da anlamsız bir ad okur. Bu yüzden etiketsiz bir düğme, görünüşte tam olsa da ekran okuyucu kullanıcısı için yoktur.

Aşağıdaki iki parça aynı kart satırının anlamsal özelliklerini iki platformda gösterir. İkisi de kartın başlığını etiket olarak verir ve silme eylemini bir hareket yerine adlandırılmış bir eylem olarak sunar. Parçalar ölçülmez.

```swift
// Platform kodu: iOS erisilebilirlik eylemi
struct CardRow: View {
    let card: Card
    var body: some View {
        Text(card.title)
            .accessibilityLabel(card.title)
            .accessibilityAction(named: "Sil") { remove(card) }
    }
}
```

```kotlin
// Platform kodu: Android erisilebilirlik eylemi
@Composable
fun CardRow(card: Card) {
    Text(
        text = card.title,
        modifier = Modifier.semantics {
            contentDescription = card.title
            customActions = listOf(CustomAccessibilityAction("Sil") { remove(card); true })
        }
    )
}
```

Aynı fikir iki platformda iki farklı API ile söylenir. Bu, çapraz platform uygulamasının erişilebilirlikte de paylaşılan bir düşünce ile platforma özgü bir katmana ayrıldığının göstergesidir: ne söylenecek ortaktır, nasıl söyleneceği platformundur.

#### Dokunma Hedefi ve Dinamik Yazı Boyutu

**Dokunma hedefi**, bir öğenin parmakla dokunulabilir alanıdır. Küçük bir hedef, motor becerisi sınırlı bir kullanıcı için ve hareket halindeki herhangi bir kullanıcı için yanlış dokunmaya yol açar. Platform yönergeleri hedefler için en küçük bir boyut önerir; bu boyut platformdan platforma değişir ve ders bir sayı vermez. Önemli olan, görsel öğenin küçük çizilse bile dokunma alanının yönergeye uygun tutulabilmesidir.

**Dinamik yazı boyutu**, kullanıcının sistem ayarından yazı boyutunu büyütmesidir. Uygulama bu ayara uyar: metin büyür ve metnin içinde durduğu düzen buna yer açmak zorundadır. Sabit genişlikte ve sabit yükseklikte çizilmiş bir bileşen büyük yazıda metni keser ya da taşırır. Düzen bu yüzden metne uyum sağlayacak biçimde, esnek tasarlanır. Önceki ders bir bileşenin uyarlanmasını anlattı; dinamik yazı boyutu, uyarlamanın kullanıcının kendi ayarından da geldiğini gösterir.

Her iki ayar da özelliğin ihtiyaç duyduğu bir yeteneği ortadan kaldırabilir. Ekran okuyucu açıkken serbest bir kaydırma hareketi güvenilir biçimde yapılamayabilir, çünkü ekran okuyucu hareketlerin çoğunu kendi gezinmesi için ayırır. Büyük yazıda sabit genişlikli bir tablo satıra sığmaz. Model bu iki düşüşü iki yetenek olarak kurar.

#### Ayardan Koşula

Model erişilebilirlik ayarlarını bir koşula çevirir. `conditionsFor(name, settings)` üç yetenekle başlar: `free-gesture` serbest hareket yeteneğidir, `fixed-width` sabit genişlikli düzen yeteneğidir, `semantics` anlamsal özelliklerin var olmasıdır. Ekran okuyucu açıksa `free-gesture` yetenekten düşer; büyük yazı açıksa `fixed-width` düşer. Dersin çekirdeği aşağıda yeniden verilir.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Ayarları koşula çeviren yardımcı ders-yerel bir dosyadır. İçinde görünüm ağacındaki düğümleri döndüren bir işlev de vardır; bu işlev ayara bakmaz.

```javascript
// settings.mjs — erisilebilirlik ayarlarindan kosul ve dugum listesi uretir
import { condition } from "./operability.mjs";

export function conditionsFor(name, settings = {}) {
  const has = ["free-gesture", "fixed-width", "semantics"];
  const kept = has.filter((item) => {
    if (item === "free-gesture") return !settings.screenReader;
    if (item === "fixed-width") return !settings.largeText;
    return true;
  });
  return condition(name, kept);
}

export function nodesFor(settings = {}) {
  return ["Card", "Label", "Actions"];
}
```

İlk ölçüm işlerliği değil, üç ayarın bıraktığı yetenekleri basar.

```javascript
// main.mjs
import { conditionsFor } from "./settings.mjs";

const conditions = [
  conditionsFor("default"),
  conditionsFor("screen-reader", { screenReader: true }),
  conditionsFor("large-text", { largeText: true }),
];
for (const item of conditions) {
  console.log(`${item.name}: ${[...item.has].sort().join(",")}`);
}
```

```text
default: fixed-width,free-gesture,semantics
screen-reader: fixed-width,semantics
large-text: free-gesture,semantics
```

Her ayar bir yeteneği düşürür ve `semantics` hiçbirinde düşmez. Anlamsal özellikler uygulamanın kendi sorumluluğudur; ayar onları kaldırmaz.

**PM7.** Ekran okuyucunun serbest hareketi nasıl ele aldığı platformdan ve ayardan ayrıntılara göre değişir. Model bu ayrıntıyı iki duruma indirger: hareket ya yapılabilir ya yapılamaz. Büyük yazıda sabit düzenin nerede taştığı da modelde yoktur; yalnız taşıp taşmadığı bir yetenek olarak kurulur. Bu, işlerliğin kuralını gösterir, gerçek bir ekran okuyucuyu sınamanın yerini tutmaz.

#### Üç Koşulda İki Özellik

İkinci ölçüm iki özelliği üç ayarda sınar. `CardSwipe` kartı kaydırarak silen harekettir ve `free-gesture` ister; yedeği eylem menüsüdür, bu yüzden eksik olduğunda kısıtlı düşer. `PriceTable` sabit genişlikli bir fiyat tablosudur ve `fixed-width` ister; yedeği tek sütunlu düzendir.

```javascript
// main.mjs
import { feature, assess, printLevels } from "./operability.mjs";
import { conditionsFor } from "./settings.mjs";

const features = [
  feature("CardSwipe", ["free-gesture"], { "free-gesture": "kisitli" }),
  feature("PriceTable", ["fixed-width"], { "fixed-width": "kisitli" }),
];
const conditions = [
  conditionsFor("default"),
  conditionsFor("screen-reader", { screenReader: true }),
  conditionsFor("large-text", { largeText: true }),
];
printLevels(assess(features, conditions));
```

```text
tam: CardSwipe @ default eksik=-
tam: PriceTable @ default eksik=-
kisitli: CardSwipe @ screen-reader eksik=free-gesture
tam: PriceTable @ screen-reader eksik=-
tam: CardSwipe @ large-text eksik=-
kisitli: PriceTable @ large-text eksik=fixed-width
```

Her ayar yalnız ilgili özelliği etkiler. Ekran okuyucu `CardSwipe`'ı kısıtlıya düşürür ve `PriceTable`'a dokunmaz; büyük yazı ise tersini yapar. Kısıtlı kayıt çalışmayan bir özellik değildir: kart eylem menüsüyle silinebilir, tablo tek sütunda okunabilir.

**PM8.** Bu iki yedeğin eylem menüsü ve tek sütunlu düzen olduğu dersin kurgusudur. Yedeğin gerçekte erişilebilir olup olmadığı, yani menünün ekran okuyucuyla gerçekten kullanılabilir çıkıp çıkmadığı ayrıca sınanır; model yalnız yedeğin tanımlı olduğunu söyler.

#### Sınırlayıcı Ölçüm

Dersin odak kaydı `CardSwipe @ screen-reader`'dır. Ekran okuyucu açıldığında `free-gesture` yetenekten düşer ve kayıt kısıtlıya iner. Bu ölçümde görünüm ağacının düğümleri de basılır ve karşılaştırmaya, yedeği olmayan özel bir hareket olan `CustomGesture` eklenir.

```javascript
// main.mjs
import { feature, assess, printLevels, countLevels } from "./operability.mjs";
import { conditionsFor, nodesFor } from "./settings.mjs";

console.log(`dugum: ${nodesFor({ screenReader: false }).join(",")}`);
console.log(`dugum: ${nodesFor({ screenReader: true }).join(",")}`);

const features = [
  feature("CardSwipe", ["free-gesture"], { "free-gesture": "kisitli" }),
  feature("CustomGesture", ["free-gesture"]),
];
const conditions = [
  conditionsFor("default"),
  conditionsFor("screen-reader", { screenReader: true }),
];
const records = assess(features, conditions);
printLevels(records);
countLevels(records);
```

```text
dugum: Card,Label,Actions
dugum: Card,Label,Actions
tam: CardSwipe @ default eksik=-
tam: CustomGesture @ default eksik=-
kisitli: CardSwipe @ screen-reader eksik=free-gesture
kapali: CustomGesture @ screen-reader eksik=free-gesture
tam: 2
kisitli: 1
ertelenen: 0
kapali: 1
etkilenen: 2
```

Düğüm listesi iki ayarda aynıdır: `Card`, `Label` ve `Actions`. Ekran okuyucu açıldığında görünüm ağacında hiçbir düğüm değişmedi, ama `CardSwipe` tamdan kısıtlıya düştü. `CustomGesture` ise yedeği olmadığı için kapalıya düştü; yedeksiz özel hareket ekran okuyucu kullanıcısı için hiç çalışmaz.

Bu sonuç M32/K02'nin sorusundan farklıdır. O ders bir güncellemenin görünüm ağacındaki düğüme etkisini sordu: hangi düğüm eklendi, değişti, kaldırıldı. Burada hiçbir düğüm değişmedi ve özellik yine de düştü. İşlerlik ağacın biçimini değil, koşulda yeteneğin var olup olmadığını sınıflar. Ağaç sabitken yetenek değişebilir, çünkü yetenek ağacın değil, kullanıcının ayarının işidir.

Etkilenen sayısı ikidir ve açılış ölçümünün dört sayısından farklıdır; iki ölçüm farklı kümeleri sayar.

#### Özet

- Bu dersin işlerliği: kısıtlı. `CardSwipe @ screen-reader` kaydında ekran okuyucu açıkken `free-gesture` yeteneği düşer ve özellik eylem menüsüyle çalışmayı sürdürür.
- Ekran okuyucu açıldığında görünüm ağacındaki düğümler aynı kaldı; özellik düğümlerden bağımsız olarak kısıtlıya düştü.
- Yedeği olmayan `CustomGesture` aynı koşulda kapalı çıktı; sayımda tam 2, kısıtlı 1, ertelenen 0, kapalı 1 ve etkilenen 2 görüldü.
- Büyük yazı yalnız `fixed-width` yeteneğini düşürdü; `PriceTable` kısıtlıya düştü, `CardSwipe` tam kaldı.
- Anlamsal özellikler hiçbir ayarda düşmedi; ekran okuyucu etiketi, rolü ve eylemleri bu katmandan okur.

#### Sonraki Adım

Bu derste yetenek kullanıcının ayarından düştü ve özellik bir yedekle kısıtlı kaldı. Sıradaki konunun ilk dersi, `Çevrimdışı Öncelikli Tasarım`, yeteneğin ağdan düştüğü durumu ele alır: uygulama yerel veriyle çalışmayı sürdürür ve kısıtlı bir akış tam bir akışın yerini alır.

## Çevrimdışı ve Eşitleme

Yerel veriyi doğruluk kaynağı yapan tasarım, değişiklik kuyruğu ve çakışma çözümü, platformun arka plan işleme sınırları, kesintili ağda kullanıcı deneyimi.

### Çevrimdışı Öncelikli Tasarım

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/cevrimdisi-oncelikli-tasarim

Çevrimdışı öncelikli tasarım, yerel veriyi doğruluk kaynağı yapar ve ağı yalnız tazeleme için kullanır. Ders aynı akışı iki tasarımla kurar ve ağ yokken hangisinin ne kadar çalıştığını ölçer.

Önceki konu, ortak kodun nasıl dilimlendiğini ve her platformun kendi katmanını nasıl taşıdığını ölçtü. Bu konu bir uygulamanın en sık karşılaştığı koşula iner: ağ yok. Metroda, asansörde, uçakta ya da kapsama alanının dışında uygulama yine açılır ve kullanıcı yine bir şey bekler. Bu derste bu beklentiyi karşılamanın iki tasarımı yan yana konur.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Açılış dersindeki dokuz kayıt beş tam, bir kısıtlı, iki ertelenen ve bir kapalı çıkmıştı. Bu ders o sayıyı değiştirmez; yalnız kendi kesitini ölçer.

#### İki Tasarım

**Ağ öncelikli** bir tasarımda ekran her açılışta veriyi sunucudan ister ve gelen yanıtı gösterir. Doğruluk kaynağı sunucudur; yerel depo en fazla bir önbellektir. Ağ varken bu tasarım basit ve doğrudur. Ağ yokken ekranın gösterecek bir şeyi kalmaz.

**Çevrimdışı öncelikli** bir tasarımda doğruluk kaynağı yereldir. Ekran her zaman yerel depodan okur. Ağ okumanın yolu değildir; yalnız yerel depoyu tazelemenin yoludur. Sunucudan gelen yeni kayıtlar yerel depoya yazılır ve ekran yerel depodaki değişikliği gösterir. Ağ gidince ekranın okuduğu şey değişmez, yalnız tazelenmez.

Bu ayrım bir kuraldır, bir kütüphane seçimi değildir. Aynı gömülü veritabanı iki tasarımda da bulunabilir. Fark, ekranın okuma yolunun nereden geçtiğidir. Aşağıdaki Android parçası çevrimdışı öncelikli okuma yolunu gösterir. Parça ölçülmez.

```kotlin
// Platform kodu: Android yerel okuma
class FeedRepository(private val db: SQLiteDatabase) {
    fun readPosts(): List<Post> {
        val cursor = db.query("posts", null, null, null, null, null, "published DESC")
        return cursor.use { rows ->
            generateSequence { if (rows.moveToNext()) rows.toPost() else null }.toList()
        }
    }
}
```

Ekran depoyu çağırır, ağı çağırmaz. Tazeleme ayrı bir iştir: ağdan gelen kayıtlar `posts` tablosuna yazılır ve okuma yolu bir sonraki okumada onları görür. iOS tarafında aynı sözleşme Swift'te aynı biçimde kurulur; yerel depodan okuyan bir katman ve ağdan tazeleyen ayrı bir katman vardır.

#### Özellikleri Kurmak

İki tasarımı ölçmek için kursun çekirdeğine ihtiyaç var. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. Akış için iki özellik kurulur. `FeedRemote` ağ öncelikli akıştır: yalnız `network` ister ve yedeği yoktur. `FeedLocal` çevrimdışı öncelikli akıştır: `storage` ve `network` ister, ağ eksik olduğunda `kisitli` yedeğine düşer. Üçüncü özellik `Bookmarks` yer imleridir ve yalnız `storage` ister. `stateOf(link)` bir bağlantı durumundan koşul üretir: ağ varsa `network`, depolama alanı varsa `storage` yeteneği koşula girer.

```javascript
// feeds.mjs — iki akis tasarimini ve baglanti durumlarini kurar
import { feature, condition } from "./operability.mjs";

export const remoteFeed = feature("FeedRemote", ["network"]);
export const localFeed = feature("FeedLocal", ["storage", "network"], { network: "kisitli" });
export const bookmarks = feature("Bookmarks", ["storage"]);

export function stateOf(link) {
  const has = [];
  if (link.network) has.push("network");
  if (link.storage !== false) has.push("storage");
  return condition(link.name, has);
}
```

#### Ağ Yokken İki Tasarım

İlk ölçüm iki akışı iki bağlantı durumunda koşar. `online` durumunda ağ ve depolama alanı vardır. `offline` durumunda yalnız depolama alanı vardır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { remoteFeed, localFeed, stateOf } from "./feeds.mjs";

const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
];
printLevels(assess([remoteFeed, localFeed], links.map(stateOf)));
```

```text
tam: FeedRemote @ online eksik=-
tam: FeedLocal @ online eksik=-
kapali: FeedRemote @ offline eksik=network
kisitli: FeedLocal @ offline eksik=network
```

Ağ varken iki tasarım da tamdır; fark ağ gidince açılır. `FeedRemote @ offline` kapalıdır, çünkü eksik `network` yeteneğinin yedeği yoktur. `FeedLocal @ offline` kısıtlıdır, çünkü aynı eksik için özellik bir yedek tanımlamıştır: yerel veriyle çalışmaya devam eder. İki özellik aynı eksiği taşır ve yedek yüzünden farklı düzeye düşer.

**CE1.** Model, ağı tek bir yetenek olarak kurar: ya vardır ya yoktur. Gerçek bir bağlantı kesintili, yavaş ya da yarı açık olabilir; bu durumlar dördüncü derste ayrıca ele alınır. Kural yeteneği ve yedeği görür; yerel depoda kaç kayıt bulunduğunu, kayıtların ne kadar eski olduğunu ve kullanıcının ne beklediğini görmez. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

#### Okuma Yolu ve Tazeleme

İşlerlik kaydı ekranın ne gösterdiğini söylemez. Bunu görmek için iki akışın okuma yolu ayrıca modellenir. Depoda üç yerel kayıt vardır; sunucuda dört kayıt vardır ve ikisi yerel kayıtlarla ortaktır. Ağ öncelikli akış yalnız sunucudan okur. Çevrimdışı öncelikli akış yerelden okur ve ağ varsa sunucudaki yeni kayıtları ekler.

```javascript
// reader.mjs — iki tasarimin gonderi okuma yolunu kurar
export function readFeed(design, store, link) {
  if (design === "remote") {
    return link.network ? { shown: store.remote.length, fresh: true } : { shown: 0, fresh: false };
  }
  const merged = link.network ? [...new Set([...store.local, ...store.remote])] : store.local;
  return { shown: merged.length, fresh: link.network };
}

export function printReads(label, design, store, links) {
  for (const link of links) {
    const read = readFeed(design, store, link);
    console.log(`${label} @ ${link.name}: ${read.shown} gonderi, taze=${read.fresh ? "evet" : "hayir"}`);
  }
}
```

Ölçüm iki okumayı ve `offline` koşulunun işlerlik kayıtlarını birlikte basar.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { remoteFeed, localFeed, stateOf } from "./feeds.mjs";
import { printReads } from "./reader.mjs";

const store = { local: ["p1", "p2", "p3"], remote: ["p2", "p3", "p4", "p5"] };
const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
];
printReads("FeedRemote", "remote", store, links);
printReads("FeedLocal", "local", store, links);
const records = assess([remoteFeed, localFeed], links.map(stateOf));
printLevels(records.filter((r) => r.condition === "offline"));
```

```text
FeedRemote @ online: 4 gonderi, taze=evet
FeedRemote @ offline: 0 gonderi, taze=hayir
FeedLocal @ online: 5 gonderi, taze=evet
FeedLocal @ offline: 3 gonderi, taze=hayir
kapali: FeedRemote @ offline eksik=network
kisitli: FeedLocal @ offline eksik=network
```

Ağ varken çevrimdışı öncelikli akış beş gönderi gösterir: üç yerel ve sunucudan gelen iki yeni kayıt. Ağ yokken üç yerel gönderiyi göstermeye devam eder ve tazelik düşer. Ağ öncelikli akış ağ yokken sıfır gönderi gösterir. İki tasarımın işlerlik kayıtları, okuma yolunun bu farkını sınıfa çevirir: sıfır gönderi kapalı, üç gönderi kısıtlı.

#### Depolama Alanı Doğruluk Kaynağıdır

Yerel veriyi doğruluk kaynağı yapan tasarım ağa olan bağımlılığı azaltır ve depolama alanına olan bağımlılığı artırır. Depolama alanı dolduğunda yerel okuma da yazma da çalışmaz. Üçüncü ölçüm bunu `storage-full` koşuluyla gösterir: ağ vardır, depolama alanı yoktur.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { localFeed, bookmarks, stateOf } from "./feeds.mjs";

const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
  { name: "storage-full", network: true, storage: false },
];
printLevels(assess([localFeed, bookmarks], links.map(stateOf)));
```

```text
tam: FeedLocal @ online eksik=-
tam: Bookmarks @ online eksik=-
kisitli: FeedLocal @ offline eksik=network
tam: Bookmarks @ offline eksik=-
kapali: FeedLocal @ storage-full eksik=storage
kapali: Bookmarks @ storage-full eksik=storage
```

`Bookmarks` yalnız yerel depoya dayanır, bu yüzden ağ gidince etkilenmez ve `storage-full` koşulunda kapanır. `FeedLocal` ağ eksikliğine yedekle dayanır ama depolama eksikliğine dayanamaz: `storage` için bir yedek tanımlanmamıştır, bu yüzden aynı özellik `storage-full` koşulunda kapalıdır. Çevrimdışı öncelikli tasarım ağ bağımlılığını yedekle karşılar; bağımlılığı ortadan kaldırmaz, yerel depoya taşır.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `FeedLocal @ offline`'dır. Son ölçüm üç özelliği iki durumda koşar, `offline` koşulunun kayıtlarını ve sayımı basar, ardından ekranın yerel modelle uyuşup uyuşmadığını sorar.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { remoteFeed, localFeed, bookmarks, stateOf } from "./feeds.mjs";
import { readFeed } from "./reader.mjs";

const store = { local: ["p1", "p2", "p3"], remote: ["p2", "p3", "p4", "p5"] };
const offline = { name: "offline", network: false };
const links = [{ name: "online", network: true }, offline];
const records = assess([remoteFeed, localFeed, bookmarks], links.map(stateOf));
printLevels(records.filter((r) => r.condition === "offline"));
countLevels(records);
const shown = readFeed("local", store, offline).shown;
console.log(`ekran yerel modelle uyusur: ${shown === store.local.length ? "evet" : "hayir"}`);
```

```text
kapali: FeedRemote @ offline eksik=network
kisitli: FeedLocal @ offline eksik=network
tam: Bookmarks @ offline eksik=-
tam: 4
kisitli: 1
ertelenen: 0
kapali: 1
etkilenen: 2
ekran yerel modelle uyusur: evet
```

İlk iki satır dersin işlerliğini taşır: aynı akış ağ öncelikli kurulduğunda kapalı, çevrimdışı öncelikli kurulduğunda kısıtlı çıktı. Altı kaydın dördü tam, biri kısıtlı, biri kapalıdır; etkilenen iki.

Son satır işlerliğin neyi sınıflamadığını gösterir. M30/K04'te bir ekranın modelle uyuşup uyuşmadığı soruldu. Burada ekran yerel modelle tam uyuşur: gösterdiği üç gönderi yerel depodaki üç gönderidir ve ekran ile model arasında bir ayrılık yoktur. Yine de `FeedLocal @ offline` tam değil, kısıtlıdır. Uyum ile işlerlik ayrı eksenlerdir. Uyum ekranın modelin söylediğini gösterip göstermediğini sorar; işlerlik özelliğin o koşulda vaat ettiğinin ne kadarını yaptığını sınıflar. Yeni içerik gelmeyen bir akış, modeliyle tam uyuşsa da, ağ varken olduğundan daha azını yapar.

Bu fark tasarım kararını da etkiler. Çevrimdışı öncelikli bir ekran için "çevrimdışı çalışıyor" demek yetmez; hangi işlerin yedekle sürdüğünü ve hangilerinin durduğunu özellik özellik söylemek gerekir.

#### Özet

- Bu dersin işlerliği: kısıtlı. `FeedLocal @ offline`, `network` eksik olduğu ve `kisitli` yedeği tanımlandığı için kısıtlı çıktı; ağ öncelikli `FeedRemote` aynı koşulda yedeksiz olduğundan kapalı kaldı.
- Çevrimdışı öncelikli tasarımda doğruluk kaynağı yerel depodur; ağ okumanın yolu değil, yerel depoyu tazelemenin yoludur.
- Ağ varken iki tasarım da tamdı. Ağ yokken okuma yolu farkı gösterdi: ağ öncelikli akış sıfır gönderi, çevrimdışı öncelikli akış üç gönderi gösterdi.
- Yerel veri ağ bağımlılığını yedekle karşılar ama depolama bağımlılığını kaldırmaz: `storage-full` koşulunda `FeedLocal` kapalıdır.
- Ekran yerel modelle tam uyuşsa da özellik kısıtlı kalır; uyum ve işlerlik ayrı eksenlerdir.

#### Sonraki Adım

Bu derste ağ yokken yalnız okuma ele alındı: yerel veri gösterildi, kullanıcının yaptığı değişiklik ise hiç konuşulmadı. Sıradaki ders, `Eşitleme Stratejileri`, çevrimdışıyken yapılan bir yazmanın kuyruğa alınmasını, ağ dönünce sunucuya gönderilmesini ve iki tarafın aynı kaydı değiştirdiği çakışmanın nasıl çözüldüğünü ele alır. Sonra çevrimdışı not düzenlemesinin ne kadar çalıştığını ölçer.

### Eşitleme Stratejileri

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/esitleme-stratejileri

Eşitleme stratejileri, çevrimdışıyken yapılan değişikliği kuyruğa alır, ağ dönünce sunucuya gönderir ve çakışmayı çözer. Ders çevrimdışı not düzenlemesinin ertelenen kaldığını ve depolama alanı dolunca kapandığını ölçer.

Önceki derste ağ yokken yalnız okuma ele alındı. Yerel veri gösterildi, ama kullanıcının yaptığı değişiklik hiç konuşulmadı. Gerçek bir uygulamada kullanıcı çevrimdışıyken de yazar: bir notu düzenler, bir görevi tamamlar, bir formu doldurur. Bu yazma kaybolmamalı ve ağ döndüğünde sunucuya ulaşmalıdır. Bu ders o yolculuğu üç parçaya böler: değişikliği kuyruğa almak, kuyruğu gönderirken aynı değişikliği iki kez uygulamamak ve iki tarafın aynı kaydı değiştirdiği çakışmayı çözmek.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Açılış dersindeki dokuz kayıt beş tam, bir kısıtlı, iki ertelenen ve bir kapalı çıkmıştı. Bu ders o sayıyı değiştirmez; yalnız kendi kesitini ölçer.

#### Değişiklik Kuyruğu

Çevrimdışıyken yapılan bir yazma doğrudan sunucuya gidemez. Uygulama onu yerel depoya, ayrı bir **değişiklik kuyruğuna** yazar. Kuyruktaki her kayıt bir değişikliği taşır: hangi kaydın hangi alanının hangi değere geçtiği ve değişikliğin kimliği. Ekran yerel veriyi hemen günceller; kullanıcı kendi değişikliğini görür. Sunucu henüz bilmez.

Kuyruk kalıcı olmalıdır. Bellekte tutulan bir kuyruk uygulama kapanınca kaybolur ve kullanıcının çevrimdışıyken yaptığı iş gider. Bu yüzden kuyruk yerel veritabanında bir tablodur. Aşağıdaki Android parçası kuyruğa kayıt eklemeyi gösterir. Parça ölçülmez.

```kotlin
// Platform kodu: Android degisiklik kuyrugu
class ChangeQueue(private val db: SQLiteDatabase) {
    fun enqueue(change: Change) {
        val row = ContentValues().apply {
            put("change_id", change.id)
            put("note_id", change.noteId)
            put("field", change.field)
            put("value", change.value)
        }
        db.insertOrThrow("pending_changes", null, row)
    }
}
```

`change.id` istemcide üretilir ve değişiklik gönderilene kadar sabit kalır. Bu kimliğin neden önemli olduğu birazdan görülecek. iOS tarafında aynı tablo ve aynı kimlik kuralı Swift'te kurulur; kuyruk iki platformda aynı sözleşmeyi taşır.

#### Notları Ölçmek

Ölçüm için kursun çekirdeği gerekir. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Dersin yardımcı dosyası üç özellik kurar. `EditNote` not düzenlemesidir ve `storage` ile `network` ister; ağ eksik olduğunda düzenleme kuyruğa alınır, bu yüzden `network` için yedek `ertelenen`dir. `ReadNote` notu okur ve yalnız `storage` ister. `ShareNote` notu paylaşır ve yalnız `network` ister; yedeği yoktur. `stateOf(link)` bağlantı durumundan koşul üretir.

```javascript
// notes.mjs — not ozelliklerini ve baglanti durumlarini kurar
import { feature, condition } from "./operability.mjs";

export const editNote = feature("EditNote", ["storage", "network"], { network: "ertelenen" });
export const readNote = feature("ReadNote", ["storage"]);
export const shareNote = feature("ShareNote", ["network"]);

export function stateOf(link) {
  const has = [];
  if (link.network) has.push("network");
  if (link.storage !== false) has.push("storage");
  return condition(link.name, has);
}
```

Kuyruğun modeli ikinci yardımcı dosyada durur. `submit` bir kaydın işlerliğine bakar ve değişikliğin akıbetini söyler: tamsa gönderilir, ertelenense kuyruğa girer, kapalıysa reddedilir. Kuyruk bir dizidir ve uzunluğu iş birimidir.

```javascript
// sync.mjs — degisiklik kuyrugunu ve gonderimi kurar
export function submit(record, queue, change) {
  if (record.level === "ertelenen") {
    queue.push(change);
    return "kuyrukta";
  }
  return record.level === "tam" ? "gonderildi" : "reddedildi";
}

export function newServer(mode) {
  return { mode, applied: new Map(), log: [] };
}

export function flush(queue, server) {
  for (const change of queue) {
    if (server.mode === "anahtarli") server.applied.set(change.id, change.value);
    else server.log.push(change);
  }
  return queue.length;
}

export function stored(server) {
  return server.mode === "anahtarli" ? server.applied.size : server.log.length;
}
```

İlk ölçüm çevrimdışı üç düzenleme yapar ve bağlantı varken bir düzenleme yapar.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { editNote, readNote, stateOf } from "./notes.mjs";
import { submit } from "./sync.mjs";

const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
];
const records = assess([editNote, readNote], links.map(stateOf));
printLevels(records);
const edits = records.filter((r) => r.feature === "EditNote");
const queue = [];
console.log(`c0: ${submit(edits[0], queue, { id: "c0", value: "v-c0" })}`);
for (const id of ["c1", "c2", "c3"]) {
  console.log(`${id}: ${submit(edits[1], queue, { id, value: `v-${id}` })}`);
}
console.log(`kuyruk: ${queue.length} degisiklik`);
```

```text
tam: EditNote @ online eksik=-
tam: ReadNote @ online eksik=-
ertelenen: EditNote @ offline eksik=network
tam: ReadNote @ offline eksik=-
c0: gonderildi
c1: kuyrukta
c2: kuyrukta
c3: kuyrukta
kuyruk: 3 degisiklik
```

Bağlantı varken düzenleme doğrudan gönderilir. Bağlantı yokken `EditNote` ertelenendir: üç düzenleme kuyruğa girer ve kuyruk üç değişiklik taşır. Okuma iki durumda da tamdır, çünkü yalnız yerel depoya dayanır.

**CE2.** Model, kuyruğu bellekteki bir dizi olarak kurar. Gerçek bir kuyruk yerel veritabanında durur, uygulama kapanıp açıldığında yaşar ve sırasını korumak zorundadır. Kuyruğun kaç değişiklik taşıdığı modelde iş birimidir; her değişikliğin ne kadar yer tuttuğu, ne kadar beklediği ve ne zaman gönderileceği modelde yoktur. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

#### Yinelemeye Dayanıklı Gönderim

Ağ döndüğünde kuyruk sunucuya gönderilir. Bu adımın sorunu, gönderimin başarısını bilmenin zor olmasıdır. İstemci değişikliği gönderir, sunucu uygular, ama yanıt yolda kaybolur. İstemci değişikliğin ulaşıp ulaşmadığını bilmez ve aynı değişikliği yeniden gönderir. Sunucu ikinci kopyayı ilk kez görüyormuş gibi uygularsa değişiklik iki kez işlenir.

Çözüm değişikliği **yinelemeye dayanıklı** yapmaktır. Her değişiklik istemcide üretilen bir kimlikle gelir. Sunucu kimliği zaten uyguladıysa ikinci kopyayı yok sayar. Kimliğin istemcide üretilmesi şarttır: sunucu üretseydi, yanıtı kaybolan bir gönderim yeni bir kimlikle tekrarlanır ve sunucu iki ayrı değişiklik görürdü.

Aşağıdaki ölçüm aynı kuyruğu iki sunucuya iki kez gönderir. `anahtarsiz` sunucu her gelen değişikliği sıraya ekler. `anahtarli` sunucu değişikliği kimliğiyle saklar.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { editNote, readNote, stateOf } from "./notes.mjs";
import { submit, newServer, flush, stored } from "./sync.mjs";

const links = [
  { name: "offline", network: false },
  { name: "online", network: true },
];
const records = assess([editNote, readNote], links.map(stateOf));
const edits = records.filter((r) => r.feature === "EditNote");
printLevels(edits);
const queue = [];
for (const id of ["c1", "c2", "c3"]) submit(edits[0], queue, { id, value: `v-${id}` });
for (const mode of ["anahtarsiz", "anahtarli"]) {
  const server = newServer(mode);
  const sent = flush(queue, server) + flush(queue, server);
  console.log(`${mode}: ${sent} gonderim, sunucuda ${stored(server)} kayit`);
}
```

```text
ertelenen: EditNote @ offline eksik=network
tam: EditNote @ online eksik=-
anahtarsiz: 6 gonderim, sunucuda 6 kayit
anahtarli: 6 gonderim, sunucuda 3 kayit
```

İki sunucuya da altı gönderim yapıldı: üç değişiklik, iki kez. `anahtarsiz` sunucu altı kayıt tutar; her değişiklik iki kez uygulanmıştır. `anahtarli` sunucu üç kayıt tutar; ikinci gönderim aynı kimlikleri bulur ve sonucu değiştirmez. Yinelemeye dayanıklılık gönderimi güvenle tekrarlanabilir kılar ve bu, yeniden denemenin bir sonraki derslerdeki dayanağıdır.

#### Çakışma Çözümü

Kullanıcı çevrimdışıyken sunucudaki kayıt da değişmiş olabilir; örneğin aynı not başka bir aygıttan düzenlenmiştir. Ağ dönünce iki sürüm elde bulunur ve sunucu hangisini kabul edeceğine karar vermek zorundadır. Bu karara **çakışma çözümü** denir ve iki yaygın strateji vardır.

**Son yazan kazanır** stratejisi kaydın tamamını karşılaştırır. Zaman damgası büyük olan sürüm kazanır ve ötekinin değişikliği düşer. Strateji basittir, ama farklı alanları değiştiren iki düzenlemeden birini tümüyle kaybeder. **Alan düzeyinde birleştirme** alanları tek tek karşılaştırır. Yalnız bir taraf bir alanı değiştirdiyse o değişiklik korunur; iki taraf da aynı alanı değiştirdiyse zaman damgası büyük olan kazanır. Aşağıdaki ölçüm bir notun başlığını sunucuda, gövdesini istemcide değiştirir ve iki stratejiyi yan yana koyar.

```javascript
// merge.mjs — cakisan iki surumu iki stratejiyle birlestirir
export function lastWriterWins(local, remote) {
  return local.stamp > remote.stamp ? local.fields : remote.fields;
}

export function mergeFields(base, local, remote) {
  const merged = {};
  for (const key of Object.keys(base).sort()) {
    const localChanged = local.fields[key] !== base[key];
    const remoteChanged = remote.fields[key] !== base[key];
    if (localChanged && remoteChanged) {
      merged[key] = local.stamp > remote.stamp ? local.fields[key] : remote.fields[key];
    } else {
      merged[key] = localChanged ? local.fields[key] : remote.fields[key];
    }
  }
  return merged;
}

export function joinFields(fields) {
  return Object.keys(fields).sort().map((key) => `${key}=${fields[key]}`).join(" ");
}
```

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { editNote, readNote, stateOf } from "./notes.mjs";
import { lastWriterWins, mergeFields, joinFields } from "./merge.mjs";

const links = [
  { name: "offline", network: false },
  { name: "online", network: true },
];
const records = assess([editNote, readNote], links.map(stateOf));
printLevels(records.filter((r) => r.feature === "EditNote"));
const base = { title: "Liste", body: "Sut al" };
const local = { stamp: 5, fields: { title: "Liste", body: "Sut ve Cay al" } };
const remote = { stamp: 7, fields: { title: "Alisveris", body: "Sut al" } };
console.log(`son yazan kazanir: ${joinFields(lastWriterWins(local, remote))}`);
console.log(`alan duzeyi: ${joinFields(mergeFields(base, local, remote))}`);
```

```text
ertelenen: EditNote @ offline eksik=network
tam: EditNote @ online eksik=-
son yazan kazanir: body=Sut al title=Alisveris
alan duzeyi: body=Sut ve Cay al title=Alisveris
```

Son yazan kazanır stratejisinde sunucunun sürümü kazanır ve istemcinin gövde düzenlemesi kaybolur. Alan düzeyinde birleştirmede iki değişiklik de korunur. İşlerlik kayıtları iki stratejide de aynıdır: `EditNote @ offline` ertelenen, `EditNote @ online` tamdır. Çakışmanın nasıl çözüldüğü kaydı değiştirmez, çünkü kural yetenek ve yedeği görür, iki sürümün içeriğini görmez. Strateji seçimi bir veri kaybı kararıdır ve işlerlik tablosundan değil, ürünün neyi kaybetmeyi göze aldığından çıkar.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `EditNote @ offline`'dır. Sınırlayıcı ölçüm üç özelliği üç durumda koşar. Üçüncü durum `storage-full`'dur: ağ vardır, depolama alanı yoktur.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { editNote, readNote, shareNote, stateOf } from "./notes.mjs";

const features = [editNote, readNote, shareNote];
const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
  { name: "storage-full", network: true, storage: false },
];
const records = assess(features, links.map(stateOf));
printLevels(records.filter((r) => r.feature === "EditNote"));
countLevels(records);

const withServer = (mode) =>
  assess(features, [{ name: "offline", network: false, server: mode }].map(stateOf));
const same = JSON.stringify(withServer("anahtarli")) === JSON.stringify(withServer("anahtarsiz"));
console.log(`yinelenme kayda girmez: ${same ? "evet" : "hayir"}`);
```

```text
tam: EditNote @ online eksik=-
ertelenen: EditNote @ offline eksik=network
kapali: EditNote @ storage-full eksik=storage
tam: 5
kisitli: 0
ertelenen: 1
kapali: 3
etkilenen: 4
yinelenme kayda girmez: evet
```

İlk üç satır dersin işlerliğini taşır. Aynı özellik, aynı kod: ağ varken tam, ağ yokken ertelenen. Düzenleme kaybolmaz; kuyrukta bekler ve ağ döndüğünde gönderilir. `storage-full` koşulunda ise kapalıdır, çünkü kuyruğun kendisi de yerel depoya yazılır. Ertelemenin yeri olan depo yoksa erteleme de yoktur. Dokuz kaydın beşi tam, biri ertelenen, üçü kapalıdır; etkilenen dört.

Son satır M30/K05'in sorusundan farkı gösterir. O ders bir işlemin yinelenirse ya da sırası değişirse sonucunun değişip değişmediğini sordu. Yinelemeye dayanıklı gönderim ile dayanıksız gönderim aynı işlerlik kaydını üretir; ama bu bir gözlem değil, modelin tanımıdır: koşulu kuran `stateOf` işlevi `server` alanını hiç okumaz, bu yüzden iki çağrı baştan aynı girdiyi görür. İşlerlik gönderimin dayanıklılığını sınıflamaz; yazmanın sonraya bırakıldığını sınıflar. Dayanıksız bir sunucu kayıtları iki kez uygular ve bu bir veri hatasıdır, ama `EditNote @ offline` yine ertelenen kalır.

#### Özet

- Bu dersin işlerliği: ertelenen. `EditNote @ offline`, `network` eksik olduğu ve `ertelenen` yedeği tanımlandığı için ertelenen çıktı; aynı koşulda `ReadNote` tam, `ShareNote` kapalı kaldı.
- Çevrimdışı yazma kalıcı bir değişiklik kuyruğuna girer; üç düzenleme üç kuyruk kaydı oldu.
- İstemcide üretilen kimlik gönderimi yinelemeye dayanıklı yapar: altı gönderimde kimlikli sunucu üç kayıt, kimliksiz sunucu altı kayıt tuttu.
- Son yazan kazanır kayıttaki bir düzenlemeyi kaybeder, alan düzeyinde birleştirme ikisini korur; strateji işlerlik kaydını değiştirmez.
- `storage-full` koşulunda kuyruk da yazılamaz, `EditNote` kapanır; dokuz kaydın dördü etkilenen.

#### Sonraki Adım

Bu derste kuyruğun gönderimi ağ döndüğünde başlıyordu; kimin başlattığı sorulmadı. Uygulama kapalıyken ya da arka planda iken işletim sistemi bu işi çalıştırmayabilir. Sıradaki ders, `Arka Plan İşleme Sınırları`, iki platformun arka plan görevi düzeneğini, işletim sisteminin getirdiği sınırları ve düşük güç kipini ele alır. Sonra arka plan yeteneği kısıldığında medya yedeklemenin ne kadar çalıştığını ölçer.

### Arka Plan İşleme Sınırları

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/arka-plan-isleme-sinirlari

Arka plan işleme sınırları, işletim sisteminin uygulama görünmezken iş çalıştırmaya koyduğu kuralları anlatır. Ders arka plan yeteneği kısıldığında medya yedeklemenin ertelendiğini, canlı konumun kapandığını ölçer.

Önceki derste çevrimdışıyken yapılan değişiklikler kuyruğa girdi ve ağ dönünce gönderilecekti. Gönderimi kimin başlatacağı sorulmadı. Kullanıcı uygulamayı kapatmış olabilir, telefon cebinde olabilir. Uygulamanın görünmezken iş çalıştırabilmesi için işletim sisteminin izni gerekir ve işletim sistemi bu izni cömert vermez. Bu ders iki platformun arka plan işleme düzeneğini ve getirdikleri sınırları anlatır, sonra bu sınırların bir özelliği ne kadar etkilediğini ölçer.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Açılış dersindeki dokuz kayıt beş tam, bir kısıtlı, iki ertelenen ve bir kapalı çıkmıştı. Bu ders o sayıyı değiştirmez; yalnız kendi kesitini ölçer.

#### İşletim Sistemi Karar Verir

Arka planda çalışmak uygulamanın değil işletim sisteminin kararıdır. Uygulama bir iş ister; işletim sistemi pil, ağ, kullanıcının alışkanlıkları ve diğer uygulamaların yükü gibi koşullara bakar ve işin ne zaman, hatta çalışıp çalışmayacağını belirler. Bu yüzden arka plan işi hiçbir zaman "şu an koş" demek değildir; "uygun bir anda koş" demektir.

iOS'ta uygulama bir görev isteği kaydeder ve sistem uygun bulduğunda uygulamayı kısa bir süre için uyandırır. Uyanma zamanını uygulama belirlemez. Android'de iş, çalışması için gereken koşullarla birlikte bir iş yöneticisine verilir; yönetici koşullar sağlandığında ve aygıtın güç yönetimi izin verdiğinde işi çalıştırır. İki düzenekte de uygulamanın elinde kesin bir saat yoktur. Süre ve sıklık sınırlarını sistemin sürümü ve kullanıcının ayarları belirler; bu derste bu sınırlar sayıyla yazılmaz, çünkü her sürümde ve her aygıtta değişirler.

Aşağıdaki iOS parçası bir yedekleme görevinin istenmesini gösterir. Parça ölçülmez.

```swift
// Platform kodu: iOS arka plan gorevi
func scheduleBackup() {
    let request = BGProcessingTaskRequest(identifier: "app.media-backup")
    request.requiresNetworkConnectivity = true
    request.requiresExternalPower = false
    try? BGTaskScheduler.shared.submit(request)
}
```

Android'de aynı istek koşullarla birlikte iş yöneticisine verilir. Parça ölçülmez.

```kotlin
// Platform kodu: Android arka plan gorevi
fun scheduleBackup(context: Context) {
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()
    val request = OneTimeWorkRequestBuilder<BackupWorker>()
        .setConstraints(constraints)
        .build()
    WorkManager.getInstance(context).enqueue(request)
}
```

İki parça aynı şeyi ister: ağ varken çalışacak bir yedekleme işi. İkisi de bir kayıt oluşturur, iş hemen koşmaz. Çalışıp çalışmayacağı sistemin cevabına kalır.

#### Düşük Güç Kipi

Kullanıcı ya da sistem pil tasarrufu için **düşük güç kipini** açabilir. Kip açıkken sistem arka plan çalışmasını keser ya da seyrekleştirir: yenileme durur, planlanan işler daha geç çalışır. Uygulama açıkken ağ ve depolama alanı yine vardır; kaybolan, arka planda çalışma izni olur.

Bu ders düşük güç kipini bir **koşul** olarak ele alır. Koşul, o anda var olan yeteneklerin kümesidir: düşük güç kipinde `network` ve `storage` vardır, `background` yoktur. Aynı koşulda bir özellik tam, bir başkası ertelenen, üçüncüsü kapalı çıkabilir.

#### Özellikleri Kurmak

Ölçüm için kursun çekirdeği gerekir. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Dersin yardımcı dosyası üç özellik kurar. `MediaBackup` fotoğrafları yedekler; `network` ve `background` ister ve arka plan eksikse işini sonraya bırakır, yani yedeği `ertelenen`dir. `LiveLocation` konumu sürekli paylaşır; aynı iki yeteneği ister ve yedeği yoktur, çünkü eski bir konum paylaşmanın anlamı yoktur. `Catalog` ürün listesidir ve yalnız `network` ister. `stateOf(link)` bağlantı ve arka plan durumundan koşul üretir.

```javascript
// background.mjs — arka plan ozelliklerini ve kosullarini kurar
import { feature, condition } from "./operability.mjs";

export const mediaBackup = feature("MediaBackup", ["network", "background"], { background: "ertelenen" });
export const liveLocation = feature("LiveLocation", ["network", "background"]);
export const catalog = feature("Catalog", ["network"]);

export function stateOf(link) {
  const has = [];
  if (link.network) has.push("network");
  if (link.background) has.push("background");
  return condition(link.name, has);
}
```

Zamanlayıcının modeli ikinci yardımcı dosyadadır. `schedule` bir kaydın işlerliğine bakar: tamsa iş koşar, ertelenense bekleyenlere eklenir, kapalıysa koşmaz ve bekleyenlere girmez. `runWindow` sistemin verdiği bir pencereyi taklit eder: pencere, bekleyenlerden belli sayıda işi çalıştırır.

```javascript
// scheduler.mjs — arka plan isini isletim sisteminin penceresine gore kurar
export function schedule(record, pending, job) {
  if (record.level === "ertelenen") {
    pending.push({ feature: record.feature, job });
    return "bekliyor";
  }
  return record.level === "tam" ? "kosuyor" : "kosmaz";
}

export function runWindow(pending, slots) {
  return pending.splice(0, slots).length;
}
```

İlk ölçüm yedekleme ve kataloğu iki durumda koşar: uygulama önde ve arka plan kısıtlı.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { mediaBackup, catalog, stateOf } from "./background.mjs";
import { schedule } from "./scheduler.mjs";

const states = [
  { name: "foreground", network: true, background: true },
  { name: "background-restricted", network: true, background: false },
];
const records = assess([mediaBackup, catalog], states.map(stateOf));
printLevels(records);
const pending = [];
for (const r of records.filter((x) => x.feature === "MediaBackup")) {
  console.log(`${r.condition}: ${schedule(r, pending, "foto-1")}`);
}
console.log(`bekleyen: ${pending.length}`);
```

```text
tam: MediaBackup @ foreground eksik=-
tam: Catalog @ foreground eksik=-
ertelenen: MediaBackup @ background-restricted eksik=background
tam: Catalog @ background-restricted eksik=-
foreground: kosuyor
background-restricted: bekliyor
bekleyen: 1
```

Uygulama öndeyken yedekleme tamdır ve iş hemen koşar. Arka plan kısıtlıyken yedekleme ertelenendir ve iş bekleyenlere girer. `Catalog` iki durumda da tamdır, çünkü yalnız ağa dayanır.

**CE3.** Model, arka planı tek bir yetenek olarak kurar: ya vardır ya yoktur. Gerçek bir sistem arka plan çalışmasına kısa ve öngörülemeyen pencereler verir; işin ne zaman koştuğu, kaç pencerede bittiği ve hiç koşup koşmadığı sistemin kararıdır. Modelde `runWindow` pencerenin büyüklüğünü ders için seçilmiş bir iş sayısı olarak alır; bu sayı bir sistemin davranışını söylemez. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

#### Ertelenen İş Kaybolmaz

Ertelenen bir iş koşmamıştır, ama kaybolmamıştır. Bekleyenler listesinde durur ve sistem bir pencere verdiğinde çalışır. İkinci ölçüm üç yedekleme işini kısıtlı koşulda sıraya alır ve iki pencere verir. Aynı koşulda `LiveLocation` üç konum güncellemesi ister.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { mediaBackup, liveLocation, stateOf } from "./background.mjs";
import { schedule, runWindow } from "./scheduler.mjs";

const states = [
  { name: "background-restricted", network: true, background: false },
  { name: "foreground", network: true, background: true },
];
const records = assess([mediaBackup, liveLocation], states.map(stateOf));
const restricted = records.filter((r) => r.condition === "background-restricted");
printLevels(restricted);
const pending = [];
for (const job of ["foto-1", "foto-2", "foto-3"]) schedule(restricted[0], pending, job);
for (const job of ["konum-1", "konum-2", "konum-3"]) schedule(restricted[1], pending, job);
console.log(`birinci pencere: ${runWindow(pending, 2)} kosti, ${pending.length} bekliyor`);
console.log(`ikinci pencere: ${runWindow(pending, 2)} kosti, ${pending.length} bekliyor`);
```

```text
ertelenen: MediaBackup @ background-restricted eksik=background
kapali: LiveLocation @ background-restricted eksik=background
birinci pencere: 2 kosti, 1 bekliyor
ikinci pencere: 1 kosti, 0 bekliyor
```

Üç yedekleme işi iki pencerede bitti: birincide ikisi koştu ve biri bekledi, ikincide kalan koştu. Konum güncellemeleri hiçbir pencerede görünmedi, çünkü `schedule` kapalı bir kaydın işini bekleyenlere eklemez. Kapalı özellik de koşmaz, ama işi sonraya kalmaz; kullanıcı arka planda konumunu paylaşamaz ve bu paylaşım bir sonraki fırsatta yapılmaz.

#### Düşük Güç Kipi Düzey Değildir

Üçüncü ölçüm düşük güç kipini uygulama önde koşuluyla yan yana koyar. Üç özellik iki koşulda koşar ve yalnız düşük güç kayıtları basılır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { catalog, mediaBackup, liveLocation, stateOf } from "./background.mjs";

const states = [
  { name: "foreground", network: true, background: true },
  { name: "low-power", network: true, background: false },
];
const records = assess([catalog, mediaBackup, liveLocation], states.map(stateOf));
printLevels(records.filter((r) => r.condition === "low-power"));
```

```text
tam: Catalog @ low-power eksik=-
ertelenen: MediaBackup @ low-power eksik=background
kapali: LiveLocation @ low-power eksik=background
```

Düşük güç kipi açıkken üç özellik üç ayrı düzeye düştü. Düşük güç kipi bir koşuldur; kısıtlı ise bir işlerlik düzeyidir ve iki sözcük aynı şey değildir. Kip, hangi yeteneğin eksildiğini söyler; düzeyi, özelliğin o eksik için tanımladığı yedek belirler.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `MediaBackup @ background-restricted`'dır. Son ölçüm üç özelliği üç koşulda koşar ve arka plan kısıtlı koşulunun kayıtlarını, sayımı ve bekleyen işleri basar.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { mediaBackup, liveLocation, catalog, stateOf } from "./background.mjs";
import { schedule } from "./scheduler.mjs";

const states = [
  { name: "foreground", network: true, background: true },
  { name: "background-restricted", network: true, background: false },
  { name: "low-power", network: true, background: false },
];
const records = assess([mediaBackup, liveLocation, catalog], states.map(stateOf));
const restricted = records.filter((r) => r.condition === "background-restricted");
printLevels(restricted);
countLevels(records);
const pending = [];
for (const r of restricted) {
  for (const job of ["is-1", "is-2", "is-3"]) schedule(r, pending, job);
}
const waiting = (name) => pending.filter((p) => p.feature === name).length;
console.log(`bekleyen: MediaBackup ${waiting("MediaBackup")}, LiveLocation ${waiting("LiveLocation")}`);
```

```text
ertelenen: MediaBackup @ background-restricted eksik=background
kapali: LiveLocation @ background-restricted eksik=background
tam: Catalog @ background-restricted eksik=-
tam: 5
kisitli: 0
ertelenen: 2
kapali: 2
etkilenen: 4
bekleyen: MediaBackup 3, LiveLocation 0
```

İlk satır dersin işlerliğini taşır: arka plan yeteneği kısıldığında yedekleme ertelenen çıktı. Dokuz kaydın beşi tam, ikisi ertelenen, ikisi kapalıdır; etkilenen dört. İki ertelenen kayıt aynı özelliğin iki koşuludur: kısıtlı ve düşük güç.

Son satır M12/K04'ün sorusundan farkı gösterir. O ders bir parçanın bu istekte koşup koşmadığını sordu. Burada yedekleme de konum paylaşımı da arka planda koşmadı, ama durumları ayrıdır. Ertelenen yedekleme koşmamıştır ve kaybolmamıştır: üç iş bekler. Kapalı konum paylaşımı da koşmamıştır, ama bekleyen işi yoktur. İşlerlik bir işin koşup koşmadığını değil, koşmadığında işin ne olduğunu sınıflar.

#### Özet

- Bu dersin işlerliği: ertelenen. `MediaBackup @ background-restricted`, `background` eksik olduğu ve `ertelenen` yedeği tanımlandığı için ertelenen çıktı; `LiveLocation` aynı koşulda yedeksiz olduğundan kapalı kaldı.
- Arka planda çalışmak işletim sisteminin kararıdır. iOS'ta sistem görev isteğini uygun bulduğunda uygulamayı uyandırır, Android'de iş yöneticisi koşullar sağlandığında çalıştırır; süre ve sıklık sınırları sürüme ve ayara göre değişir.
- Düşük güç kipi bir koşuldur, işlerlik düzeyi değil: aynı kipte `Catalog` tam, `MediaBackup` ertelenen, `LiveLocation` kapalı çıktı.
- Ertelenen iş koşmamıştır ama kaybolmamıştır: üç yedekleme işi iki pencerede bitti. Kapalı özelliğin işi bekleyenlere girmez.
- Dokuz kaydın beşi tam, ikisi ertelenen, ikisi kapalıdır; etkilenen dört.

#### Sonraki Adım

Bu derste arka plan yeteneği vardı ya da yoktu; bağlantı ise hep vardı. Gerçekte bağlantı da kesilir, yavaşlar, geri gelir. Sıradaki ders, `Bağlantı Durumu Yönetimi`, bağlantının izlenmesini, zayıf bağlantıyı, yeniden deneme ile üstel geri çekilmeyi ve bağlantı durumunun kullanıcıya gösterilmesini ele alır. Sonra zayıf bağlantıda bir görsel galerisinin ne kadar çalıştığını ölçer.

### Bağlantı Durumu Yönetimi

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/baglanti-durumu-yonetimi

Bağlantı durumu yönetimi, bağlantıyı izlemeyi, zayıf bağlantıya uyum sağlamayı, yeniden denemeyi ve durumu kullanıcıya göstermeyi anlatır. Ders görsel galerisinin zayıf bağlantıda kısıtlı, mesajlaşmanın ertelenen kaldığını ölçer.

Önceki derste arka plan yeteneği vardı ya da yoktu, bağlantı ise hep vardı. Gerçekte bağlantı da kesilir, yavaşlar, geri gelir. Metroda sinyal gidip gelir, kalabalık bir salonda bağlantı görünür ama veri akmaz, bir tünelden çıkınca ağ bir anda döner. Bu ders bağlantıyı izlemeyi, zayıf bağlantıya uyum sağlamayı, başarısız bir isteği yeniden denemeyi ve durumu kullanıcıya göstermeyi anlatır.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Bu ders soruyu üç bağlantı durumunda üç özellik için sorar.

#### Bağlantıyı İzlemek

Her iki platform da bağlantıdaki değişimi uygulamaya bildirir. Uygulama bir izleyici kaydeder ve sistem bağlantı kurulduğunda, koptuğunda ya da niteliği değiştiğinde izleyiciyi çağırır. Aşağıdaki iOS parçası bunu gösterir. Parça ölçülmez.

```swift
// Platform kodu: iOS baglanti izleme
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
    let isOnline = path.status == .satisfied
    let isWeak = path.isConstrained || path.isExpensive
    connectionState.update(online: isOnline, weak: isWeak)
}
monitor.start(queue: DispatchQueue(label: "connectivity"))
```

Android'de aynı iş, sistem hizmetinden alınan bir yöneticiye geri çağrı kaydederek yapılır. Parça ölçülmez.

```kotlin
// Platform kodu: Android baglanti izleme
val manager = context.getSystemService(ConnectivityManager::class.java)
manager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) = connectionState.update(online = true)
    override fun onLost(network: Network) = connectionState.update(online = false)
})
```

İzleyicinin söylediği şey sınırlıdır. Sistem "bir ağ yolu var" der; "sunucuya istek ulaşır" demez. Bir ağa bağlı olup internete çıkamayan, giriş sayfası bekleyen ya da çok yavaş bir bağlantı izleyicide "bağlı" görünebilir. İzleyici bu yüzden bir ipucudur, kesinlik değil: gerçek bir isteğin başarısı ya da başarısızlığı son sözü söyler.

#### Zayıf Bağlantı

Bağlantının iki ucu, tam bağlı ve hiç bağlı değil, kolaydır. Zor olan aradır: bağlantı vardır ama veri yavaş akar, sık kesilir ya da kullanıcı veri tasarrufu açmıştır. Bu durumda uygulamanın iki seçeneği vardır: büyük içeriği istemeye devam edip beklemek ya da daha küçük bir içerikle yetinmek. İyi bir uygulama ikincisini seçer; görsel yüksek çözünürlük yerine düşük çözünürlükle gelir, büyük bir liste kısa bir listeye iner, canlı güncelleme yerine kullanıcının isteğiyle yenileme gelir.

Model zayıf bağlantıyı bir yetenek eksiği olarak kurar: ağ vardır ama `bandwidth` yoktur. `bandwidth`, büyük içeriği makul bir sürede almak için yeterli veri hızı anlamına gelir. Eksik olduğunda özellik tanımlı yedeğine geçer.

#### Özellikleri Kurmak

Ölçüm için kursun çekirdeği gerekir. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Dersin yardımcı dosyası üç özellik kurar. `Gallery` görsel galerisidir; `network` ve `bandwidth` ister, `bandwidth` eksikse düşük çözünürlüklü önizlemeye düşer, yani yedeği `kisitli`dir. `network` için yedeği yoktur. `Chat` mesajlaşmadır; aynı iki yeteneği ister ve her ikisi eksik olduğunda mesajı sıraya alır, yani iki yedeği de `ertelenen`dir. `Settings` ayarlardır ve yalnız `storage` ister. `conditionsFor(link)` bir bağlantı durumunu koşula çevirir: iyi bağlantıda `online`, zayıf bağlantıda `weak-network`, bağlantı yokken `offline`.

```javascript
// connectivity.mjs — baglanti ozelliklerini ve baglanti kosullarini kurar
import { feature, condition } from "./operability.mjs";

export const gallery = feature("Gallery", ["network", "bandwidth"], { bandwidth: "kisitli" });
export const chat = feature("Chat", ["network", "bandwidth"], { network: "ertelenen", bandwidth: "ertelenen" });
export const settings = feature("Settings", ["storage"]);

export function conditionsFor(link) {
  if (link.quality === "good") return condition("online", ["network", "bandwidth", "storage"]);
  if (link.quality === "weak") return condition("weak-network", ["network", "storage"]);
  return condition("offline", ["storage"]);
}
```

İlk ölçüm üç bağlantı durumunu koşula çevirir, her koşulun yeteneklerini basar ve iki özelliği üç koşulda sınıflar.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { gallery, settings, conditionsFor } from "./connectivity.mjs";

const links = [{ quality: "good" }, { quality: "weak" }, { quality: "none" }];
const conds = links.map(conditionsFor);
for (const c of conds) console.log(`${c.name}: ${c.has.join(",")}`);
printLevels(assess([gallery, settings], conds));
```

```text
online: network,bandwidth,storage
weak-network: network,storage
offline: storage
tam: Gallery @ online eksik=-
tam: Settings @ online eksik=-
kisitli: Gallery @ weak-network eksik=bandwidth
tam: Settings @ weak-network eksik=-
kapali: Gallery @ offline eksik=bandwidth,network
tam: Settings @ offline eksik=-
```

Zayıf bağlantıda `Gallery` kısıtlıdır: ağ vardır, ama `bandwidth` yoktur ve galeri düşük çözünürlüklü önizlemeye düşer. Bağlantı yokken aynı galeri kapalıdır, çünkü `network` eksiğinin yedeği yoktur ve galeri bir önbellek tutmaz. `Settings` yalnız yerel depoya dayandığı için üç koşulda da tamdır.

**CE4.** Model, bağlantıyı üç kesin duruma indirir: iyi, zayıf, yok. Gerçek bir bağlantı bu üç durum arasında sürekli kayar ve izleyici bu kaymayı gecikmeyle bildirir. Zayıflığın nerede başladığı modelde bir eşik olarak değil, `bandwidth` yeteneğinin yokluğu olarak kurulur. Kural yeteneği ve yedeği görür; veri hızını, kaybolan paketleri ve kullanıcının sabrını görmez. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

#### Yeniden Deneme ve Üstel Geri Çekilme

Bağlantı yokken ya da zayıfken gönderilen bir mesaj başarısız olabilir. Uygulama isteği yeniden dener. Sorun, ne zaman deneyeceğidir. **Sabit aralıklı** yeniden denemede her deneme bir öncekinden aynı aralıkla sonra gelir. Bağlantı kısa süre sonra dönecekse ilk fırsatta yakalar, ama dönmeyecekse boşuna pil ve ağ harcar; binlerce istemci aynı anda aynı sunucuya yüklenirse sunucuyu da yorar.

**Üstel geri çekilmede** her başarısız denemeden sonra bekleme iki katına çıkar ve bir üst sınırda durur. İlk denemeler hızlıdır, sonrakiler seyrekleşir. Gerçek uygulamalarda bekleme süresine küçük bir rastgele pay da eklenir ki binlerce istemci aynı anda denemesin; model rastgelelik içermediği için bu payı kurmaz. Yeniden denemenin güvenle yapılabilmesi, önceki dersteki yinelemeye dayanıklı gönderime bağlıdır: aynı mesajın iki kez ulaşması sonucu bozmaz.

Modelde zaman gerçek süreyle değil, **tur** ile ifade edilir. Tur bir sıra birimidir; bir saniyeyi ya da bir dakikayı temsil etmez. Aşağıdaki işlev bağlantının belli bir turda döndüğü bir senaryoda her stratejinin kaç deneme yaptığını ve işin hangi turda ulaştığını hesaplar.

```javascript
// retry.mjs — yeniden deneme stratejilerini tur sayisiyla kurar
export function attemptsUntil(strategy, returnTurn) {
  let turn = 0;
  let wait = 1;
  let attempts = 0;
  while (true) {
    attempts += 1;
    if (turn >= returnTurn) return { attempts, turn };
    turn += wait;
    if (strategy === "ustel") wait = Math.min(wait * 2, 8);
  }
}
```

İkinci ölçüm bağlantı yokken `Chat`'in kaydını ve iki stratejinin sonucunu basar. Bağlantı onuncu turda döner.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { chat, settings, conditionsFor } from "./connectivity.mjs";
import { attemptsUntil } from "./retry.mjs";

const conds = [{ quality: "none" }, { quality: "good" }].map(conditionsFor);
printLevels(assess([chat, settings], conds).filter((r) => r.feature === "Chat"));
for (const strategy of ["sabit", "ustel"]) {
  const result = attemptsUntil(strategy, 10);
  console.log(`${strategy}: ${result.attempts} deneme, ${result.turn}. turda ulasti`);
}
```

```text
ertelenen: Chat @ offline eksik=bandwidth,network
tam: Chat @ online eksik=-
sabit: 11 deneme, 10. turda ulasti
ustel: 5 deneme, 15. turda ulasti
```

Sabit aralıklı strateji işi onuncu turda, bağlantı döner dönmez ulaştırdı, ama on bir deneme yaptı. Üstel strateji beş deneme yaptı ve işi on beşinci turda ulaştırdı: bağlantı dönmüştü, ama bir sonraki deneme henüz gelmemişti. Seyrek denemenin bedeli gecikmedir, sıkı denemenin bedeli boşa giden denemelerdir. Hangisinin seçileceği ürünün neyi önemsediğine bağlıdır. İkisinde de `Chat @ offline` ertelenendir, çünkü mesaj kaybolmaz; sıraya alınır ve bağlantı dönünce gönderilir.

#### Durumu Kullanıcıya Göstermek

Bağlantı yok ya da zayıf olduğunda kullanıcıya tek bir "çevrimdışısınız" bildirimi göstermek kolaydır ve yanıltıcıdır. Aynı anda galeri düşük çözünürlüğe düşmüş, mesajlar sıraya girmiş, ayarlar normal çalışıyordur. Kullanıcı için anlamlı olan, her özelliğin durumudur. İşlerlik kaydı bu mesajı doğrudan taşır: kısıtlı özellik "sınırlı", ertelenen özellik "sırada", kapalı özellik "kullanılamıyor" der; tam özellik hiçbir şey demez.

```javascript
// banner.mjs — bir kayittan kullaniciya gosterilecek durumu uretir
const LABELS = { tam: "-", kisitli: "sinirli", ertelenen: "sirada", kapali: "kullanilamiyor" };

export function labelFor(record) {
  return LABELS[record.level];
}

export function printBanners(records) {
  for (const r of records) console.log(`${r.feature} @ ${r.condition}: ${labelFor(r)}`);
}
```

```javascript
// main.mjs
import { assess } from "./operability.mjs";
import { gallery, chat, settings, conditionsFor } from "./connectivity.mjs";
import { printBanners } from "./banner.mjs";

const conds = [{ quality: "weak" }, { quality: "none" }].map(conditionsFor);
printBanners(assess([gallery, chat, settings], conds));
```

```text
Gallery @ weak-network: sinirli
Chat @ weak-network: sirada
Settings @ weak-network: -
Gallery @ offline: kullanilamiyor
Chat @ offline: sirada
Settings @ offline: -
```

Zayıf bağlantıda ve bağlantı yokken `Settings` hiçbir bildirim taşımaz. `Chat` iki koşulda da "sırada" der: mesaj gönderilmedi, ama kaybolmadı. `Gallery` zayıf bağlantıda "sınırlı", bağlantı yokken "kullanılamıyor" der. Aynı bağlantı durumu üç özellikte üç ayrı mesaj üretti.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `Gallery @ weak-network`'tür. Son ölçüm üç özelliği üç bağlantı durumunda koşar, zayıf bağlantının kayıtlarını ve sayımı basar.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { gallery, chat, settings, conditionsFor } from "./connectivity.mjs";

const features = [gallery, chat, settings];
const links = [{ quality: "good" }, { quality: "weak" }, { quality: "none" }];
const records = assess(features, links.map(conditionsFor));
printLevels(records.filter((r) => r.condition === "weak-network"));
countLevels(records);

const withCopies = (copies) =>
  assess(features, [{ quality: "weak", copies }].map(conditionsFor));
const same = JSON.stringify(withCopies(1)) === JSON.stringify(withCopies(3));
console.log(`kopya sayisi kayda girmez: ${same ? "evet" : "hayir"}`);
```

```text
kisitli: Gallery @ weak-network eksik=bandwidth
ertelenen: Chat @ weak-network eksik=bandwidth
tam: Settings @ weak-network eksik=-
tam: 5
kisitli: 1
ertelenen: 2
kapali: 1
etkilenen: 4
kopya sayisi kayda girmez: evet
```

İlk satır dersin işlerliğini taşır: zayıf bağlantıda galeri kısıtlı çıktı. Aynı koşulda `Chat` ertelenen, `Settings` tamdır. Dokuz kaydın beşi tam, biri kısıtlı, ikisi ertelenen, biri kapalıdır; etkilenen dört.

Son satır M31/K05'in sorusundan farkı gösterir. O ders gönderilen bir olayın alıcıya kaç kopya olarak ulaştığını sordu. Bağlantı dönünce sıradaki mesajlar teslim edilir ve yeniden denemeler yüzünden aynı mesaj birden çok kez ulaşabilir. İşlerlik bunu sınıflamaz: bir kopya da üç kopya da aynı kaydı üretir. Bu bir gözlem değil, modelin tanımıdır; koşulu kuran `conditionsFor` işlevi `copies` alanını hiç okumaz. İşlerlik teslimin kaç kopya olduğunu değil, zayıf bağlantıda özelliğin ne yaptığını sınıflar. Mesaj sıraya girer ve bu, kaç kez gönderildiğinden bağımsız olarak ertelenendir.

#### Özet

- Bu dersin işlerliği: kısıtlı. `Gallery @ weak-network`, `bandwidth` eksik olduğu ve `kisitli` yedeği tanımlandığı için kısıtlı çıktı; aynı koşulda `Chat` ertelenen, `Settings` tam kaldı.
- Platform izleyicileri bir ağ yolunun varlığını bildirir, isteğin ulaşacağını değil; gerçek isteğin sonucu son sözü söyler.
- Zayıf bağlantıda ağ vardır ama `bandwidth` yoktur; özellik tanımlı yedeğe, düşük çözünürlüklü önizlemeye ya da sıraya geçer.
- Aynı senaryoda sabit aralıklı strateji on bir deneme yaptı ve onuncu turda, üstel strateji beş deneme yaptı ve on beşinci turda ulaştı; seyrek denemenin bedeli gecikmedir.
- Kullanıcıya özellik bazında durum gösterilir: sınırlı, sırada, kullanılamıyor.

#### Sonraki Adım

Bu konuda bir özelliğin kapalı ya da kısıtlı kalma nedeni hep koşuldu: ağ, depolama alanı, arka plan, veri hızı. Üretimde bir özelliğin kapalı kalması için koşulun bozulması gerekmez; aşamanın yapılandırması özelliği kapalı tutabilir. Sıradaki ders, `Mobil Sürekli Teslimat`, uygulamanın derlenip imzalanarak mağazaya ve test kullanıcılarına ulaştığı hattı ele alır. Sonra hazırlık aşamasında açık, üretim aşamasında kapalı bir özelliğin işlerliğini ölçer.

## Yayın ve İşletim

İmzalama, derleme ve yükleme otomasyonu, sürüm numaralama, aşamalı yayın ve mobilde geri almanın sınırları, zorunlu güncelleme, uygulama boyutu, iki platformdan birleşik izleme.

### Mobil Sürekli Teslimat

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/mobil-surekli-teslimat

Mobil sürekli teslimat, kod değişikliğini imzalı bir yapıya çevirip test kanalından ve mağaza incelemesinden geçirir. Ders, aynı imzalı yapının üretimde kapalı bir bayrakla çıktığında özelliğin ne kadar çalıştığını ölçer.

Önceki derslerde bir özelliğin işlerliğini aygıtın durumu belirledi: ağ vardı ya da yoktu, arka plan işi kısılmıştı ya da kısılmamıştı. Bu ders koşulun kaynağını aygıttan aşamaya taşır. Uygulama kullanıcıya ulaşmadan önce bir hattan geçer; hat yapıyı derler, imzalar, yükler ve bir kanaldan dağıtır. Aynı yapı farklı aşamalarda farklı bayrak değerleriyle çalışabilir; bayrağın değeri o aşamada hangi yeteneğin var olduğunu söyler.

Kursun sorusu değişmez: bu koşulda bu özellik ne kadar çalışıyor? Açılış dersindeki dokuz kayıt beş tam, bir kısıtlı, iki ertelenen ve bir kapalı çıkmıştı. Bu ders o sayıları değiştirmez; yalnız kendi kesitini, yapının koşulunu ölçer.

#### Mobilde Sürekli Teslimat

Sürekli teslimat, kod değişikliğinin her seferinde aynı adımlarla ve elle müdahale olmadan yayına hazır bir yapıya dönüşmesidir. Web uygulamasında bu hattın sonunda sunucu güncellenir ve herkes yeni sürümü görür. Mobilde hattın sonunda bir yapı vardır ve o yapı kullanıcının aygıtına kurulmayı bekler. Kullanıcı güncellemeyi kabul edene kadar eski sürüm aygıtta çalışmaya devam eder.

Bir sınır daha vardır. Hat yapıyı yayına hazır hâle getirir, ama yayının son adımı uygulama mağazasının ya da Android mağazasının mağaza incelemesinden geçer ve o aşama hattın elinde değildir. Bu yüzden mobil hatlar iki parçalıdır: mağazaya girmeden önce ekibin denetlediği aşamalar, mağazaya girdikten sonra bekleyen aşamalar. Ekip ilk parçayı otomatikleştirir, ikinci parçayı planlar.

M12/K05'te bir davranışın üretime çıkıp çıkmadığı soruldu. İşlerlik başka bir şey sorar. Üretime çıkan bir yapıda da bir özellik bayrağı kapalı olabilir; işlerlik yapının nereye çıktığını değil, çıkan yapıda özelliğin ne kadar çalıştığını sınıflar.

#### İmzalama, Sertifika ve Sağlama Profili

Bir yapı imzalanmadan mağazaya girmez. İmzalama, yapının kimin tarafından üretildiğini ve üretildikten sonra değişmediğini kanıtlar. iOS'ta imza bir sertifikayla atılır; yapı ayrıca hangi uygulama kimliğine, hangi yetkilere ve hangi dağıtım türüne ait olduğunu söyleyen bir sağlama profiliyle eşlenir. Sertifikanın bir geçerlilik süresi vardır ve süresi dolan sertifika yeni yapının imzalanmasını engeller. Android'de imza bir anahtara bağlıdır; imzalama kimliği uygulamanın kimliğinin parçasıdır, bu yüzden imza anahtarını ekip kendisi saklıyorsa anahtarın yitirilmesi geri alınamaz, sızması da yalnız bir yapıyı değil uygulamanın bütün sürüm geçmişini ilgilendirir. İmza anahtarını mağaza saklıyorsa ekibin elindeki yükleme anahtarı yitirildiğinde mağaza üzerinden yenilenebilir.

İki platformda da kural aynıdır: imzalama gizli değerle yapılır ve gizli değer depoya konmaz. Hat, sertifikayı, sağlama profilini ve anahtarı gizli değer deposundan okur ve yalnız imzalama adımı süresince kullanır. İmzalama bilgisi bir geliştiricinin bilgisayarında kalırsa hat o kişiye bağımlı olur; bu, sürekli teslimatın çözmeye çalıştığı sorunun kendisidir.

#### Derleme, Yükleme ve Test Dağıtımı

Hat genellikle üç aşamaya bölünür. Derleme aşaması temiz bir ortamda koddan yapıyı üretir ve her çalışmada derleme numarasını bir artırır. İmzalama aşaması yapıyı imzalar. Yükleme aşaması imzalı yapıyı mağazanın yayın konsoluna ya da bir test dağıtım kanalına gönderir. Test dağıtım kanalı, yapıyı tam mağaza incelemesinden geçirmeden belirli kullanıcılara ulaştırır. iOS'ta ekip dışından test kullanıcıları çağrıldığında ilk yapı ayrı bir test incelemesine girer; ekip, üretime çıkacak yapıyı önce kendi aygıtlarında ve test kullanıcılarında dener.

Aşağıdaki parça bu aşamaların bir yapılandırmadaki biçimini gösterir. Parça ölçülmez; alan adları örnektir.

```yaml
# Platform kodu: teslimat hatti asamalari
pipeline:
  - stage: build
    steps: [compile, sign]
    signing:
      certificate: from-secret-store
      profile: store-distribution
  - stage: upload
    steps: [upload-to-test-channel]
  - stage: release
    needs-approval: true
    steps: [upload-to-store, submit-for-review]
```

Son aşamadaki `needs-approval` satırı hattın bilerek durduğu yeri gösterir. Yapı test kanalında dolaşırken sorun çıkmadıysa bir insan onay verir ve yapı mağaza incelemesine gönderilir. Otomasyon yükü alır, karar insanda kalır.

#### Hattı Güvenilir Kılan Alışkanlıklar

Hattın değeri hızından çok tekrarlanabilirliğinden gelir. Birkaç alışkanlık bunu korur.

Birincisi, test kanalında denenen yapı ile mağazaya giden yapı aynı olmalıdır. Hazırlık için bir yapı, üretim için ikinci bir yapı derlenirse, denenen şey ile yayımlanan şey arasında bir fark doğabilir. Bu yüzden hat yapıyı bir kez üretir ve aynı dosyayı bir aşamadan ötekine taşır. Aşamalar arasındaki fark yapının içeriğinde değil, aşamanın yapılandırmasında olur.

İkincisi, derleme numarası hiçbir zaman geri gitmez ve iki yapıda aynı olmaz. Mağazalar aynı derleme numarasıyla ikinci bir yüklemeyi kabul etmez; numarayı elle yönetmek, hattın en sık takıldığı yerlerden biridir. Numarayı hat artırır, ve insan yalnız sürüm numarasına karar verir. İkisinin farkı sonraki dersin konusudur.

Üçüncüsü, bir aşama başarısız olursa hat durur ve yarım bir yapı yüklenmez. İmzalama adımı hata verdiyse yükleme adımı çalışmaz; test kanalına yalnız imzalı ve bütün bir yapı çıkar. Başarısız bir çalışmanın günlüğü ekibin ilk baktığı yerdir, ve o günlükte gizli değer görünmemelidir.

Dördüncüsü, mağaza incelemesinin reddi planın içindedir. Reddedilen bir yapı için düzeltme yeni bir derleme numarası alır ve hat baştan koşar. Hattın otomatik olması bu döngüyü kısaltır, ama mağazanın kararını beklemek ekibin yönetebileceği bir şey değildir.

#### Yapının Koşulunu Ölçmek

Hattın ürettiği yapı işlerlik açısından tek bir bilgiyi aşamanın yapılandırmasından alır: hangi özellik bayraklarının hangi değerle bulunduğu. Hazırlık aşamasının yapılandırmasında yeni ödeme akışının bayrağı açıktır, çünkü ekip onu denemek ister. Üretim aşamasının yapılandırmasında kapalıdır, çünkü akış henüz kullanıcıya verilmeyecektir. Aşağıdaki çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. `configFor(stage)` bir hat aşamasından koşul üretir: ağ her aşamada vardır, bayrak yeteneği ise yalnız aşamanın açık bayrakları için eklenir. Yetenek dizgisi `flag:` önekiyle yazılır. Özellikler üçtür. `NewCheckout` yeni ödeme akışıdır ve ağı ve `flag:new-checkout` yeteneğini ister; bayrağın yedeği yoktur. `LegacyCheckout` eski ödeme akışıdır ve yalnız ağı ister. `Catalog` ürün listesidir ve o da yalnız ağı ister.

```javascript
// delivery.mjs — hat asamasindan kosul uretir
import { feature, condition } from "./operability.mjs";

export function configFor(stage) {
  const flags = stage.flags.map((name) => `flag:${name}`);
  return condition(stage.name, ["network", ...flags]);
}

export const checkoutFeatures = [
  feature("NewCheckout", ["network", "flag:new-checkout"]),
  feature("LegacyCheckout", ["network"]),
  feature("Catalog", ["network"]),
];
```

**YI1.** Model, bayrağı bir yetenek olarak kurar: bayrak aşamanın yapılandırmasında ya vardır ya yoktur. Gerçek bir uygulamada bayrağın değeri yapıya gömülü olabilir ya da uzaktan yapılandırmadan gelebilir; kural bu ayrımı görmez, yalnız `has` listesine bakar. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

İlk ölçüm aynı derleme numarasıyla iki aşamayı yan yana koyar. Yapı bir kez derlenmiş ve imzalanmıştır; hazırlık ile üretim arasındaki fark yalnız açık bayrak listesidir. Derleme satırları dersin yerel çıktısıdır; kayıt satırları `assess` çıktısıdır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { configFor, checkoutFeatures } from "./delivery.mjs";

const build = { number: 42, signed: true, uploaded: true };
const stages = [
  { name: "staging", flags: ["new-checkout"] },
  { name: "production", flags: [] },
];
for (const stage of stages) {
  console.log(`derleme: ${build.number} asama=${stage.name} imzali=${build.signed ? "evet" : "hayir"}`);
}
printLevels(assess(checkoutFeatures, stages.map(configFor)));
```

```text
derleme: 42 asama=staging imzali=evet
derleme: 42 asama=production imzali=evet
tam: NewCheckout @ staging eksik=-
tam: LegacyCheckout @ staging eksik=-
tam: Catalog @ staging eksik=-
kapali: NewCheckout @ production eksik=flag:new-checkout
tam: LegacyCheckout @ production eksik=-
tam: Catalog @ production eksik=-
```

Hazırlıkta üç özellik de tamdır. Üretimde ise `NewCheckout` kapalı çıkar: eksik olan `flag:new-checkout` yeteneğidir ve onun yedeği tanımlanmamıştır. `LegacyCheckout` ve `Catalog` aynı üretim yapısında tam kalır. Kapalı olan özelliğin işi sonraya da kalmaz; kullanıcı ödeme için eski akışı kullanır. Eski akışın tam olması yeni akışın yedeği olduğu anlamına gelmez. İkisi ayrı özelliktir ve ayrı kayıt alır.

#### Yapı Sabit, Bayrak Değişir

Bayrak üretimde açılabilir, ve bunun için yeni bir yapı gerekmeyebilir. Bayrağın değeri uzaktan yapılandırmadan okunuyorsa ekip değeri değiştirir, uygulama bir sonraki okumada yeni değeri alır. Bu, mobilde sürekli teslimatın en değerli ayrımlarından biridir: yapıyı mağazaya göndermek ile davranışı kullanıcıya açmak iki ayrı iştir. Birincisi mağaza incelemesine tabidir, ikincisi değildir.

İkinci ölçüm aynı üretim yapısını bayrak kapalıyken ve açıkken karşılaştırır. Koşul adları farklıdır, çünkü iki koşulun adı aynı olamaz; yapı ve derleme numarası aynıdır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { configFor, checkoutFeatures } from "./delivery.mjs";

const closed = { name: "production", flags: [] };
const opened = { name: "production-flag-on", flags: ["new-checkout"] };
printLevels(assess(checkoutFeatures, [closed, opened].map(configFor)));
```

```text
kapali: NewCheckout @ production eksik=flag:new-checkout
tam: LegacyCheckout @ production eksik=-
tam: Catalog @ production eksik=-
tam: NewCheckout @ production-flag-on eksik=-
tam: LegacyCheckout @ production-flag-on eksik=-
tam: Catalog @ production-flag-on eksik=-
```

Bayrak açılınca `NewCheckout` kapalıdan tama geçer; öteki iki özellik hiç değişmez. Kural bayrağın açılışının nasıl yapıldığını görmez: uzaktan yapılandırma, yeni bir yapı ya da elle değiştirilen bir değer aynı yeteneği verir. Kuralın baktığı şey, açılışın sonunda yeteneğin var olup olmadığıdır.

**YI2.** Model bayrağın açılışının yaygınlaşmasını göstermez. Gerçek bir uygulamada uzaktan yapılandırma her aygıta aynı anda ulaşmaz; aygıtlar değeri farklı zamanlarda okur. Bu dağılım bir sonraki dersin konusudur ve bu derste tek bir uygulama örneğinin koşulu ölçülür.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `NewCheckout @ production`'dır. Sınırlayıcı ölçüm iki şeyi birlikte gösterir. Birincisi, odak kayıt hazırlık aşamasında tam, üretim aşamasında kapalıdır ve altı kaydın yalnız biri etkilenir. İkincisi, modelde yapının imzalanıp yüklenmesi kayda girmez: imzalı ve yüklenmiş bir yapı ile henüz imzalanmamış bir yapı aynı üretim koşulunda aynı kaydı üretir. Bu bir gözlem değil, modelin tanımıdır; koşulu kuran `configFor` işlevi `build` alanını hiç okumaz.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { configFor, checkoutFeatures } from "./delivery.mjs";

const stages = [
  { name: "staging", flags: ["new-checkout"] },
  { name: "production", flags: [] },
];
const records = assess(checkoutFeatures, stages.map(configFor));
printLevels(records.filter((r) => r.feature === "NewCheckout"));
countLevels(records);

const signed = { number: 42, signed: true, uploaded: true };
const unsigned = { number: 43, signed: false, uploaded: false };
const withBuild = (build) =>
  assess(checkoutFeatures, [{ name: "production", flags: [], build }].map(configFor));
const same = JSON.stringify(withBuild(signed)) === JSON.stringify(withBuild(unsigned));
console.log(`imza kayda girmez: ${same ? "evet" : "hayir"}`);
```

```text
tam: NewCheckout @ staging eksik=-
kapali: NewCheckout @ production eksik=flag:new-checkout
tam: 5
kisitli: 0
ertelenen: 0
kapali: 1
etkilenen: 1
imza kayda girmez: evet
```

İlk iki satır dersin işlerliğini taşır: aynı özellik, aynı kod, aynı derleme numarası; yalnız bayrağın aşamanın yapılandırmasında olup olmaması değişti ve kayıt tamdan kapalıya geçti. Sayım satırları altı kayıttan beşinin tam, birinin kapalı olduğunu söyler; etkilenen birdir.

Son satır modelin sınırını açıkça gösterir. İmza, sertifika ve yükleme, bir yapının mağazaya girebilmesi için gerekli şartlardır ama özelliğin ihtiyaç duyduğu yetenekler değildir. İmzalanmamış bir yapı mağazaya hiç girmeyecektir; ama kural, mağazaya girmiş bir yapının içindeki özelliği sınıflar. Hattın başarısı ile özelliğin çalışması iki ayrı soruya cevap verir. Hat yeşil olabilir, imza geçerli olabilir, yükleme tamamlanabilir ve kullanıcı yine de yeni ödeme akışını göremez.

Bu ayrım iş akışını da değiştirir. Üretime çıkışta bir özelliğin çalışmadığı görüldüğünde ilk bakılacak yer hattın günlüğü değildir; aşamanın yapılandırmasındaki bayrak değeridir.

#### Özet

- Bu dersin işlerliği: kapalı. `NewCheckout @ production`, üretim aşamasının yapılandırmasında `flag:new-checkout` yeteneği bulunmadığı ve bayrağın yedeği olmadığı için kapalı çıktı; aynı aşamada `LegacyCheckout` ve `Catalog` tam kaldı.
- Mobil hat yapıyı derler, imzalar, yükler ve test dağıtım kanalına verir; son aşama mağaza incelemesidir ve hattın elinde değildir.
- İmzalama sertifika, sağlama profili ve anahtar gibi gizli değerlere dayanır; bu değerler depoda değil, hattın gizli değer deposunda durur.
- Hazırlıkta üç özellik tam, üretimde biri kapalıydı; altı kayıtta etkilenen bir çıktı. Bayrak açılınca aynı üretim yapısında kayıt tama döndü.
- Modelde imzalı ve yüklenmiş bir yapı ile imzalanmamış bir yapı aynı kaydı verir; bu eşitlik ölçülmüş bir bulgu değildir. Yapının hazır olması işlerliği belirlemez.

#### Sonraki Adım

Bu derste yapı tek bir derleme numarasıyla anıldı ve ona hiçbir anlam yüklenmedi. Sıradaki ders, `Sürüm Yönetimi`, sürüm numarası ile derleme numarasının farkını, anlamlı sürümlemeyi ve sürüm notunu ele alır. Sonra istemci ile sunucu arasındaki sözleşmeyi sürümle ilişkilendirir ve yalnız eski sözleşmeyi sunan bir sunucuda yeni sözleşmeyi isteyen sipariş geçmişinin ne kadar çalıştığını ölçer.

### Sürüm Yönetimi

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/surum-yonetimi

Sürüm yönetimi, bir uygulamanın hangi yapısının hangi numarayla yayımlandığını ve istemcinin sunucuyla nasıl konuştuğunu düzenler. Ders, yalnız eski sözleşmeyi sunan sunucuda yeni sözleşmeyi isteyen bir özelliğin ne kadar çalıştığını ölçer.

Önceki ders yapıyı tek bir derleme numarasıyla andı ve numaraya anlam yüklemedi. Gerçek bir uygulamada numara iki iş görür: kullanıcıya hangi sürümü kullandığını söyler ve ekibe hangi yapının hangi hatayı taşıdığını. Bu ders numaraların ne olduğunu, kim tarafından artırıldığını ve bir sürüm numarasının sunucuyla ilişkisini ele alır.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Bu derste koşulun kaynağı sunucudur. İstemci bir sözleşmeyi ister, sunucu ya o sözleşmeyi sunar ya da sunmaz.

#### Sürüm Numarası ve Derleme Numarası

Mobil bir uygulamada iki ayrı numara vardır ve ikisi sık karıştırılır.

Sürüm numarası insanın okuduğu numaradır. Mağaza sayfasında görünür, ayarlar ekranında yazar, destek ekibine "hangi sürümdesiniz" diye sorulduğunda söylenir. Sürüm numarasına ekip karar verir, çünkü numara bir anlam taşır: bu yayın önceki yayına göre ne kadar değişti.

Derleme numarası makinenin okuduğu numaradır. Mağaza, aynı sürüm numarasıyla yüklenmiş birden çok yapıyı birbirinden bu numarayla ayırır. Reddedilen bir yapının düzeltmesi aynı sürüm numarasını koruyabilir, ama derleme numarası artmak zorundadır. Bu numarayı önceki derste anlatılan teslimat hattı artırır; elle yönetilen bir derleme numarası, hattın en sık takıldığı yerlerden biridir.

İki platform bu iki numarayı farklı alan adlarıyla tutar. Aşağıdaki iki parça yalnız alanların yerini gösterir; parçalar ölçülmez.

```kotlin
// Platform kodu: Android surum alanlari
android {
    defaultConfig {
        versionCode = 142
        versionName = "4.2.1"
    }
}
```

```yaml
# Platform kodu: iOS surum alanlari
CFBundleShortVersionString: "4.2.1"
CFBundleVersion: "142"
```

Android'de `versionName` kullanıcıya görünen sürümdür ve `versionCode` makinenin sıraladığı tam sayıdır. iOS'ta `CFBundleShortVersionString` kullanıcıya görünen sürümü, `CFBundleVersion` derleme numarasını taşır. Köprüyle yerel görünümlere bağlanan bir çerçevede de, kendi çizim motoruyla çalışan bir çerçevede de bu iki alan sonunda aynı yere, platformun kendi yapılandırma dosyasına yazılır; çerçeve yalnız bu dosyaları besleyen bir katman ekler.

#### Anlamlı Sürümleme

Sürüm numarasını üç parçaya bölmek yaygın bir düzendir: ana, ara ve yama. `4.2.1` numarasında ana sürüm dört, ara sürüm iki, yama bir demektir. Her parçanın bir sözü vardır.

- Yama, mevcut davranışı bozmadan bir hatayı düzeltir. Bir kullanıcı yamayı kurduğunda yeni bir şey öğrenmesi gerekmez.
- Ara sürüm, mevcut davranışı bozmadan yeni bir yetenek ekler. Yama sıfırlanır.
- Ana sürüm, mevcut davranışı bozabilecek bir değişiklik getirir. Ara ve yama sıfırlanır.

Bu düzen mobilde bir web servisine göre daha önemlidir, çünkü eski sürüm kullanıcının aygıtında yaşamaya devam eder. Sunucu güncellenir ve herkese birden ulaşır; istemci güncellenmez, kullanıcı karar verene kadar eski kalır. Ana sürüm numarası bu yüzden yalnız bir etiket değil, sunucunun hangi istemcilerle hâlâ konuşması gerektiğini planlayan ekibe bir işarettir.

Numara üretimi mekaniktir ve elle yapıldığında hata verir; bu yüzden küçük bir işleve bırakılması iyi olur. Dersin yardımcı dosyasındaki `nextVersion` işlevi bu kuralı yazar.

#### Sürüm Notu

Her yayın bir sürüm notuyla çıkar. Not iki okuyucuya hitap eder: mağaza sayfasını okuyan kullanıcıya ve yayını izleyen ekibe. Kullanıcıya dönük not kısa ve davranış odaklıdır; "ödeme ekranı yenilendi" gibi. Ekibe dönük not hangi değişikliklerin yayına girdiğini, hangilerinin bayrağın arkasında kapalı gittiğini ve hangi sunucu sürümünü gerektirdiğini söyler.

Son madde ihmal edilir ve sonradan pahalıya mal olur. Bir yayın yeni bir sunucu sözleşmesini gerektiriyorsa not bunu açıkça yazmalıdır; çünkü sunucu o sözleşmeyi sunmadan istemciyi yayımlamak, özelliği sessizce eksik çalıştırır. Aşağıdaki bölüm bu eksikliği ölçer.

#### İstemci ile Sunucu Arasındaki Sözleşme

İstemci ve sunucu bir sözleşmeyle konuşur: hangi alanlar var, hangi biçimde, hangi adlarla. Sözleşmenin de bir sürümü vardır ve sunucu birden çok sürümü aynı anda sunabilir. Eski bir istemci eski sözleşmeyi, yeni bir istemci yeni sözleşmeyi ister.

Uyumsuz bir değişiklikte sunucu eski sözleşmeyi bir süre daha sunar, çünkü eski istemciler hâlâ kullanımdadır. Bunun tersi de vardır: yeni istemci yayımlanmış, ama sunucu henüz yeni sözleşmeye geçmemiş olabilir. Mağazaya giren istemci ile canlıya alınan sunucu aynı anda güncellenmez; ikisinin arasındaki aralıkta yeni istemci eski sözleşmeyle karşılaşır.

İşlerlik açısından sorunun şekli yeniden bellidir. Sözleşmenin sürümü bir yetenektir: `api:v2` ya da `api:v3`. İstemcideki bir özellik yeni sözleşmeyi istiyorsa ve sunucu onu sunmuyorsa özellik eksik bir yetenekle çalışır. Bu eksiklik bazen özelliği tamamen engeller, bazen özellik eski alanlarla daha az bilgi gösterir. İkincisi yedeği olan bir eksikliktir.

#### Sözleşmenin Koşulunu Ölçmek

Ölçüm için sipariş geçmişini ele alalım. `OrderHistory` yeni sözleşmeyi, yani `api:v3` yeteneğini ister; bu sözleşme her siparişin kalem ayrıntısını da getirir. Yalnız `api:v2` sunan sunucuda ekran açılır, ama siparişleri eski alanlarla, kalem ayrıntısı olmadan gösterir. Bu bir yedektir ve `kisitli` düzeyindedir. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. `conditionsFor(servers)` her sunucu tanımından bir koşul üretir: ağ her sunucuda vardır, sözleşme yeteneği ise sunucunun sunduğu sürümden gelir ve `api:` önekiyle yazılır. `nextVersion(version, change)` üç parçalı bir numarayı değişikliğin türüne göre artırır. Özellikler üçtür. `OrderHistory` ağı ve `api:v3` yeteneğini ister; `api:v3` eksikse kısıtlı çalışır. `Catalog` ürün listesidir ve `Profile` kullanıcı profilidir; ikisi de yalnız ağı ister.

```javascript
// versions.mjs — sunucunun sundugu sozlesmeden kosul uretir, surumu artirir
import { feature, condition } from "./operability.mjs";

export function conditionsFor(servers) {
  return servers.map((server) => condition(server.name, ["network", `api:${server.api}`]));
}

export function nextVersion(version, change) {
  const [major, minor, patch] = version.split(".").map(Number);
  if (change === "major") return `${major + 1}.0.0`;
  if (change === "minor") return `${major}.${minor + 1}.0`;
  return `${major}.${minor}.${patch + 1}`;
}

export const historyFeatures = [
  feature("OrderHistory", ["network", "api:v3"], { "api:v3": "kisitli" }),
  feature("Catalog", ["network"]),
  feature("Profile", ["network"]),
];
```

**YI3.** Model, sunucunun sunduğu sözleşmeyi tek bir yetenek olarak kurar: bir sunucu `v2` ya da `v3` sunar. Gerçek bir sunucu birden çok sürümü birlikte sunabilir ve istemci hangisini kullanacağını pazarlıkla belirleyebilir; kural bu pazarlığı görmez, yalnız koşulun `has` listesine bakar. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

İlk ölçüm iki sunucuyu aynı istemci için yan yana koyar. Önce üç numara artışı basılır; bunlar dersin yerel çıktısıdır. Sonra altı kayıt gelir.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, nextVersion, historyFeatures } from "./versions.mjs";

let version = "4.2.0";
for (const change of ["patch", "minor", "major"]) {
  const next = nextVersion(version, change);
  console.log(`surum: ${version} + ${change} = ${next}`);
  version = next;
}
const servers = [
  { name: "api-v2", api: "v2" },
  { name: "api-v3", api: "v3" },
];
printLevels(assess(historyFeatures, conditionsFor(servers)));
```

```text
surum: 4.2.0 + patch = 4.2.1
surum: 4.2.1 + minor = 4.3.0
surum: 4.3.0 + major = 5.0.0
kisitli: OrderHistory @ api-v2 eksik=api:v3
tam: Catalog @ api-v2 eksik=-
tam: Profile @ api-v2 eksik=-
tam: OrderHistory @ api-v3 eksik=-
tam: Catalog @ api-v3 eksik=-
tam: Profile @ api-v3 eksik=-
```

Numara artışları sürüm kuralını gösterir: yama yalnız son parçayı artırır, ara sürüm yamayı sıfırlar, ana sürüm ikisini birden. Kayıtlarda ise `OrderHistory` yalnız `api-v2` sunucusunda kısıtlıdır; eksik olan `api:v3` yeteneğidir ve yedeği kısıtlı olarak tanımlanmıştır. Aynı sunucuda `Catalog` ve `Profile` tamdır, çünkü ikisi sözleşme sürümünü istemez. `api-v3` sunucusunda üç özellik de tamdır.

Kısıtlı sözcüğü burada somut bir şey söyler: sipariş geçmişi açılır, siparişler listelenir, ama kalem ayrıntısı yoktur. Kapalı olsaydı ekran hiç açılmayacaktı. Yedeğin tanımlı olması, özelliğin eksik yetenekle de bir şey sunabilmesi demektir.

#### Yama Yükseltmesi Yeteneği Değiştirmez

Kullanıcı bir yama kurduğunda sürüm numarası değişir. Numaranın değişmesi, özelliğin ihtiyaç duyduğu yeteneklerden birini getirmiş ya da götürmüş olmak anlamına gelmez. Yama hatayı düzeltir, sözleşmeyi değiştirmez.

İkinci ölçüm aynı sunucuda iki istemci sürümünü karşılaştırır. Koşul adları sürümü içerir, çünkü iki koşulun adı aynı olamaz; sunulan sözleşme ikisinde de aynıdır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, historyFeatures } from "./versions.mjs";

const installs = [
  { name: "4.2.0-api-v2", api: "v2" },
  { name: "4.2.1-api-v2", api: "v2" },
];
const records = assess(historyFeatures, conditionsFor(installs));
printLevels(records);

const key = (list) => list.map((r) => `${r.level}:${r.feature}:${r.missing.join(",")}`).join("|");
const before = key(records.filter((r) => r.condition === "4.2.0-api-v2"));
const after = key(records.filter((r) => r.condition === "4.2.1-api-v2"));
console.log(`yama kaydi degistirir mi: ${before === after ? "hayir" : "evet"}`);
```

```text
kisitli: OrderHistory @ 4.2.0-api-v2 eksik=api:v3
tam: Catalog @ 4.2.0-api-v2 eksik=-
tam: Profile @ 4.2.0-api-v2 eksik=-
kisitli: OrderHistory @ 4.2.1-api-v2 eksik=api:v3
tam: Catalog @ 4.2.1-api-v2 eksik=-
tam: Profile @ 4.2.1-api-v2 eksik=-
yama kaydi degistirir mi: hayir
```

İki istemci sürümü altı kayıtta aynı üç sonucu verir ve son satır bunu söyler. Karşılaştırma koşul adını dışarıda bırakır, çünkü ad zaten farklıdır; karşılaştırılan şey düzeyler, özellikler ve eksik yeteneklerdir.

**YI4.** Model, yamanın yetenek değiştirmediğini varsayar ve bu varsayım bir sözleşmeye dayanır: yama davranışı bozmaz. Gerçekte bir yama, yedek bir yolu devre dışı bırakabilir ya da bir yeteneği yanlışlıkla kaldırabilir. Bu durumda yama bir hatayı düzeltirken yenisini getirmiştir ve sürüm numarası bunu söylemez. Kural bu durumu yakalamaz; yakalayan, yamadan sonra koşulun yeniden kurulup ölçülmesidir.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `OrderHistory @ api-v2`'dir. Sınırlayıcı ölçüm iki şeyi birlikte gösterir. Birincisi, odak kayıt `api-v2` sunucusunda kısıtlı, `api-v3` sunucusunda tamdır ve altı kaydın yalnız biri etkilenir. İkincisi, istemcinin sürüm numarası kayda girmez: ana sürümü yükseltilmiş bir istemci, aynı eski sunucuda aynı kaydı verir.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { conditionsFor, historyFeatures } from "./versions.mjs";

const servers = [
  { name: "api-v2", api: "v2" },
  { name: "api-v3", api: "v3" },
];
const records = assess(historyFeatures, conditionsFor(servers));
printLevels(records.filter((r) => r.feature === "OrderHistory"));
countLevels(records);

const key = (list) => list.map((r) => `${r.level}:${r.feature}:${r.missing.join(",")}`).join("|");
const oldClient = assess(historyFeatures, conditionsFor([{ name: "4.2.0-api-v2", api: "v2" }]));
const newClient = assess(historyFeatures, conditionsFor([{ name: "5.0.0-api-v2", api: "v2" }]));
console.log(`istemci surumu kayda girmez: ${key(oldClient) === key(newClient) ? "evet" : "hayir"}`);
```

```text
kisitli: OrderHistory @ api-v2 eksik=api:v3
tam: OrderHistory @ api-v3 eksik=-
tam: 5
kisitli: 1
ertelenen: 0
kapali: 0
etkilenen: 1
istemci surumu kayda girmez: evet
```

İlk iki satır dersin işlerliğini taşır: aynı özellik, aynı kod; yalnız sunucunun sunduğu sözleşme değişti ve kayıt tamdan kısıtlıya geçti. Sayım satırları altı kayıttan beşinin tam, birinin kısıtlı olduğunu söyler; etkilenen birdir.

Son satır modelin sınırını gösterir. Sürüm numarası bir etikettir; özelliğin ihtiyaç duyduğu şey sürüm numarası değil, sunucunun sunduğu sözleşmedir. `5.0.0` numaralı bir istemci, `api:v3` sunmayan bir sunucuda `4.2.0` numaralı istemci kadar kısıtlıdır. Numara bu yüzden bir ipucudur ama kanıt değildir: ekip hangi istemcinin hangi sözleşmeyi istediğini sürüm notuna yazar, işlerliği ise sunucunun gerçekten sunduğu sözleşme belirler.

Bu ayrım yayın sırasını da belirler. Yeni sözleşmeyi isteyen bir istemci, sunucu o sözleşmeye geçmeden yayımlanırsa kullanıcıların bir kısmı özelliği kısıtlı görür. Sunucunun önce, istemcinin sonra yayımlanması bu aralığı kapatır.

#### Özet

- Bu dersin işlerliği: kısıtlı. `OrderHistory @ api-v2`, sunucu yalnız eski sözleşmeyi sunduğu ve `api:v3` yeteneğinin yedeği kısıtlı olduğu için kısıtlı çıktı; aynı sunucuda `Catalog` ve `Profile` tam kaldı.
- Sürüm numarasına ekip karar verir ve kullanıcıya görünür; derleme numarasını hat artırır ve her yüklemede yeni olmak zorundadır.
- Anlamlı sürümlemede yama hatayı düzeltir, ara sürüm yetenek ekler ve yamayı sıfırlar, ana sürüm uyumsuz değişiklik getirir ve ikisini de sıfırlar.
- Altı kayıttan beşi tam, biri kısıtlıydı; etkilenen bir. Yama yükseltmesi aynı sunucuda kaydı değiştirmedi.
- Ana sürümü yükseltilmiş istemci eski sunucuda aynı kaydı verdi; işlerliği sürüm numarası değil, sunucunun sunduğu sözleşme belirler.

#### Sonraki Adım

Bu derste sunucu sabit bir koşuldu ve istemci tek bir yapıydı. Sıradaki ders, `Aşamalı Yayın ve Geri Alma`, yeni bir sürümün kullanıcıların yalnız bir kısmına ulaştığı durumu ele alır. Yayını durdurmayı, geri almayı ve ileri doğru düzeltmeyi karşılaştırır, ve her kovanın koşulunu ayrı ayrı ölçer.

### Aşamalı Yayın ve Geri Alma

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/asamali-yayin-ve-geri-alma

Aşamalı yayın, yeni bir sürümü kullanıcıların yalnız bir kısmına açar; sorun çıkarsa yayın durdurulur ya da özellik kapatılır. Ders, yayının ulaştığı her kovada özelliğin ne kadar çalıştığını ayrı ayrı ölçer.

Önceki derste yeni sözleşmeyi isteyen istemci, sunucu henüz hazır değilken yayımlanırsa kullanıcıların bir kısmının özelliği kısıtlı gördüğünü ölçtük. Bu ders o kısmı bilerek ve denetimle belirlemenin yolunu anlatır. Yeni sürüm bütün kullanıcılara birden açılmaz; önce küçük bir gruba açılır, sorun görünmezse grup büyür.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Bu derste koşul, aygıtın yayının hangi kovasında olduğudur.

#### Aşamalı Yayın

Aşamalı yayında bir sürüm mağazada yayımlanır, ama yalnız kullanıcıların bir bölümüne sunulur. Mağaza bölümü kendisi seçer. Android'de ekip yayının hangi aşamaya geçeceğini belirler; iOS'ta aşamalar sabit bir takvimle kendiliğinden ilerler, ekip yalnız yayını duraklatır ya da herkese açar. Aşama genişledikçe sürümü alabilen kullanıcı sayısı artar; son aşamada herkes alır.

Bu düzenin değeri, bir hatanın etkisini sınırlamasıdır. Yeni sürüm beklenmedik bir hata taşıyorsa, hata önce küçük bir grupta görünür. Çökme raporları ve kullanıcı bildirimleri izlenir; işler yolundaysa yayın bir sonraki aşamaya geçer. İzleme ayrı bir dersin konusudur ve bu kursun kapanışında ele alınır.

Aşamalı yayın tek bir mekanizma değildir. Mağazanın kendi aşamalı yayın özelliği sürümü aygıtlara dağıtır. Uygulamanın içindeki bayrak ise sürümü alan aygıtlarda özelliği açıp kapatır. İkisi birlikte kullanılabilir: sürüm mağaza düzeyinde bir kısım aygıta ulaşır, özellik bayrak düzeyinde onların da yalnız bir kısmında açılır.

M12/K02'de bir değişikliğin üretime nasıl ulaştığı anlatıldı. Aşamalı yayında yeni sürüm yalnız bazı kovalara ulaşır; işlerlik ise ulaşılan aygıt sayısını değil, her kovadaki işlerliği sınıflar. Sürümün kaç aygıta ulaştığı yayın planının sorusudur. Ulaştığı kovada özelliğin ne kadar çalıştığı işlerliğin sorusudur.

#### Kova ve Yayının Durdurulması

Kova, aygıtların kararlı bir kurala göre ayrıldığı gruptur. Kural aygıt kimliğinden bir sayı türetir ve o sayıyı kova sayısına böler; kalan, aygıtın kovasıdır. Aynı aygıt her zaman aynı kovaya düşer. Bu kararlılık önemlidir: yayın genişlediğinde ilk kovadaki aygıt kovasından çıkmaz, açık kovalar listesine yenileri eklenir.

Yayın aşamalarını bir yapılandırma dosyası tutabilir. Aşağıdaki parça yalnız şekli gösterir; ölçülmez.

```yaml
# Platform kodu: asamali yayin asamalari
rollout:
  - stage: first
    buckets: [2]
  - stage: wider
    buckets: [2, 3]
  - stage: full
    buckets: [0, 1, 2, 3]
```

Yayını durdurmak, bir sonraki aşamaya geçmemek demektir. Durdurulan yayında sürümü almış aygıtlar onu kullanmaya devam eder. Android'de yeni aygıtlar sürümü almaz; iOS'ta durdurma yalnız otomatik güncellemeyi keser, sürümü mağazadan kendisi indiren kullanıcı onu yine alabilir. Durdurma yeni etkilenen kullanıcı sayısını büyük ölçüde dondurur, ama etkilenmiş olanları iyileştirmez.

#### Geri Alma ve İleri Düzeltme

Durdurma yetmediğinde iki yol kalır.

Geri alma, kullanıcıyı önceki davranışa döndürmektir. Mobilde kurulmuş bir sürüm geri çekilemez. Mağaza, kullanıcının aygıtındaki uygulamayı eski bir sürüme indirmez; en fazla eski sürümü yeniden sunar, o da bir güncelleme olarak ve yeni bir sürüm numarasıyla. Bu yüzden mobilde geri alma çoğunlukla sürümün değil, özelliğin geri alınması anlamına gelir.

Özelliği geri almanın aracı uzaktan kapatma bayrağıdır: özellik bayrağın arkasında gönderilir, sorun çıkarsa bayrak çekilir ve uygulama eski davranışa döner. Bayrak uzaktan okunuyorsa bunun için yeni bir sürüm gerekmez; bu, mağaza incelemesini beklemeden etkiyi kesmenin tek yoludur. Aşağıdaki iki parça bayrağın okunuşunu gösterir.

```kotlin
// Platform kodu: uzaktan kapatma bayragi okuma
val enabled = config.getBoolean("new_feed_enabled")
if (enabled) showNewFeed() else showLegacyFeed()
```

```swift
// Platform kodu: uzaktan kapatma bayragi okuma
let enabled = config.bool(forKey: "new_feed_enabled")
if enabled { showNewFeed() } else { showLegacyFeed() }
```

İleri düzeltme, hatayı düzelten yeni bir sürümü yayımlamaktır. Geri alma kadar hızlı değildir, çünkü yeni sürüm mağaza incelemesinden geçer ve kullanıcının onu kurmasını bekler. Ama bayrağın arkasına alınmamış bir özellik için tek yol odur.

İkisinin seçimi, özelliğin bayrağın arkasında olup olmamasına bağlıdır. Bayrağın arkasındaki özellik için önce geri alma, ardından acele etmeden ileri düzeltme gelir. Bayraksız bir özellikte yalnız ileri düzeltme vardır ve etkilenen kullanıcılar düzeltilmiş sürümü kurana kadar sorunla yaşar.

#### Yayının Koşulunu Ölçmek

Ölçüm için yeni haber akışını ele alalım. `NewFeed` ağı ve `flag:new-feed` yeteneğini ister; bayrağın yedeği yoktur. `LegacyFeed` eski akıştır ve yalnız ağı ister. Yayın açık kovalarda bayrağı sağlar, kapalı kovalarda sağlamaz. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. `bucketOf(deviceId, bucketCount)` aygıt kimliğinin karakter kodlarını toplar ve toplamı kova sayısına böler; kalan kovadır. `rolloutConditions(fleet, openBuckets, bucketCount)` her aygıttan bir koşul üretir: ağ her aygıtta vardır, bayrak yeteneği yalnız kovası açık olanlara eklenir.

```javascript
// rollout.mjs — aygiti kovasina ayirir, acik kovalardan kosul uretir
import { feature, condition } from "./operability.mjs";

export function bucketOf(deviceId, bucketCount) {
  let sum = 0;
  for (const ch of deviceId) sum += ch.charCodeAt(0);
  return sum % bucketCount;
}

export function rolloutConditions(fleet, openBuckets, bucketCount) {
  return fleet.map((deviceId) => {
    const open = openBuckets.includes(bucketOf(deviceId, bucketCount));
    return condition(deviceId, open ? ["network", "flag:new-feed"] : ["network"]);
  });
}

export const rolloutFeatures = [
  feature("NewFeed", ["network", "flag:new-feed"]),
  feature("LegacyFeed", ["network"]),
];
```

**YI5.** Model, yayını sabit bir aygıt listesi ve dört kovayla kurar. Gerçek bir yayında aygıt sayısı sürekli değişir ve kova sayısı çok daha büyüktür; ayrıca mağazanın aşamalı yayını ile uygulamanın bayrağı iki ayrı mekanizmadır ve her biri kendi kovalama kuralını kullanabilir. Model yalnız tek bir kuralı gösterir ve bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

İlk ölçüm yayının ilk aşamasını gösterir: yalnız `2` numaralı kova açıktır. Önce dört aygıtın kovası basılır, sonra sekiz kayıt gelir.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { bucketOf, rolloutConditions, rolloutFeatures } from "./rollout.mjs";

const fleet = ["device-a", "device-b", "device-c", "device-d"];
for (const deviceId of fleet) {
  console.log(`kova: ${deviceId} ${bucketOf(deviceId, 4)}`);
}
printLevels(assess(rolloutFeatures, rolloutConditions(fleet, [2], 4)));
```

```text
kova: device-a 2
kova: device-b 3
kova: device-c 0
kova: device-d 1
tam: NewFeed @ device-a eksik=-
tam: LegacyFeed @ device-a eksik=-
kapali: NewFeed @ device-b eksik=flag:new-feed
tam: LegacyFeed @ device-b eksik=-
kapali: NewFeed @ device-c eksik=flag:new-feed
tam: LegacyFeed @ device-c eksik=-
kapali: NewFeed @ device-d eksik=flag:new-feed
tam: LegacyFeed @ device-d eksik=-
```

Dört aygıt dört ayrı kovaya düşer. Yalnız `device-a` açık kovadadır ve orada `NewFeed` tamdır. Öteki üç aygıtta bayrak yoktur; `NewFeed` kapalıdır, çünkü bayrağın yedeği tanımlı değildir. `LegacyFeed` dört aygıtta da tamdır. Kapalı olan aygıtın kullanıcısı akışsız kalmaz; eski akışı görür. Yeni akışın kapalı olması ile eski akışın tam olması iki ayrı kayıttır.

#### Genişletme ve Geri Alma

Yayın genişlediğinde açık kovalar listesine yeni kova eklenir. Geri alındığında liste boşalır. Kural her iki durumda da aynı biçimde çalışır; değişen yalnız koşuldaki yetenektir.

İkinci ölçüm iki aşamayı art arda koyar. Önce `2` ve `3` numaralı kovalar açılır; sonra bayrak çekilir ve açık kova kalmaz. Yalnız `NewFeed` kayıtları basılır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { rolloutConditions, rolloutFeatures } from "./rollout.mjs";

const fleet = ["device-a", "device-b", "device-c", "device-d"];
const stages = [
  { name: "genisletme", open: [2, 3] },
  { name: "geri-alma", open: [] },
];
for (const stage of stages) {
  console.log(`asama: ${stage.name} acik-kova=${stage.open.length === 0 ? "-" : stage.open.join(",")}`);
  const records = assess(rolloutFeatures, rolloutConditions(fleet, stage.open, 4));
  printLevels(records.filter((r) => r.feature === "NewFeed"));
}
```

```text
asama: genisletme acik-kova=2,3
tam: NewFeed @ device-a eksik=-
tam: NewFeed @ device-b eksik=-
kapali: NewFeed @ device-c eksik=flag:new-feed
kapali: NewFeed @ device-d eksik=flag:new-feed
asama: geri-alma acik-kova=-
kapali: NewFeed @ device-a eksik=flag:new-feed
kapali: NewFeed @ device-b eksik=flag:new-feed
kapali: NewFeed @ device-c eksik=flag:new-feed
kapali: NewFeed @ device-d eksik=flag:new-feed
```

Genişletmede `device-a` ve `device-b` açık kovalardadır ve `NewFeed` orada tamdır; `device-c` ve `device-d` hâlâ kapalıdır. Geri almada dört aygıtın dördü de kapalıya döner. Bayrağın çekilmesi bir kayıt düzeyinde şunu söyler: yeni akışın ihtiyaç duyduğu yetenek artık hiçbir aygıtta yoktur.

Geri alma bir işlerlik düzeyi değildir. Geri alma, ekibin yaptığı bir iştir; işlerlik ise o işin sonunda koşulun ne olduğunu sınıflar. Kural bayrağı kimin çektiğini, ne zaman çektiğini ya da neden çekildiğini görmez; yalnız çekildikten sonra `flag:new-feed` yeteneğinin yokluğunu görür.

**YI6.** Model, bayrağın çekilmesinin her aygıta anında yansıdığını varsayar. Gerçekte aygıtlar yapılandırmayı farklı zamanlarda okur ve çevrimdışı kalmış bir aygıt eski değeri bir süre daha kullanabilir. Geri alma bu yüzden bir anda değil bir dağılım olarak gerçekleşir. Kural bu dağılımı göstermez; her aygıtın okuduğu değere göre ayrı bir koşul kurulmasını ister.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `NewFeed @ device-a`'dır; yayının ilk aşamasında açık kovadaki ilk aygıt. Sınırlayıcı ölçüm iki şeyi birlikte gösterir. Birincisi, odak kayıt açık kovada tamdır ve sekiz kaydın üçü etkilenir. İkincisi, yayının ulaştığı aygıt sayısı odak kaydı belirlemez: yayın bir kova daha genişlediğinde `device-a`'nın kaydı değişmez.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { rolloutConditions, rolloutFeatures } from "./rollout.mjs";

const fleet = ["device-a", "device-b", "device-c", "device-d"];
const records = assess(rolloutFeatures, rolloutConditions(fleet, [2], 4));
printLevels(records.filter((r) => r.feature === "NewFeed" && r.condition === "device-a"));
countLevels(records);
const reached = records.filter((r) => r.feature === "NewFeed" && r.level === "tam").length;
console.log(`ulasilan aygit: ${reached} toplam: ${fleet.length}`);

const wider = assess(rolloutFeatures, rolloutConditions(fleet, [2, 3], 4));
const pick = (list) => list.find((r) => r.feature === "NewFeed" && r.condition === "device-a");
const same = JSON.stringify(pick(records)) === JSON.stringify(pick(wider));
console.log(`odak kaydi pay degisince ayni: ${same ? "evet" : "hayir"}`);
```

```text
tam: NewFeed @ device-a eksik=-
tam: 5
kisitli: 0
ertelenen: 0
kapali: 3
etkilenen: 3
ulasilan aygit: 1 toplam: 4
odak kaydi pay degisince ayni: evet
```

İlk satır dersin işlerliğini taşır: açık kovadaki aygıtta yeni akış tamdır. Sayım satırları sekiz kayıttan beşinin tam, üçünün kapalı olduğunu söyler; etkilenen üçtür. Ulaşılan aygıt sayısı dörtte birdir.

Son iki satır modelin sınırını gösterir. Ulaşılan aygıt sayısı yayın planının sayısıdır; özelliğin her aygıtta ne kadar çalıştığını söylemez. Ulaşılmış aygıtta yeni akışın tam olması, ulaşılmamış aygıtların hiçbirinde bir şeyin bozuk olduğu anlamına gelmez; orada yeni akış hiç sunulmamıştır ve eski akış tam çalışır. Yayın genişleyip ulaşılan sayı ikiye çıksa da `device-a`'nın kaydı aynı kalır. Pay, hangi aygıtların hangi kayda düştüğünü değiştirir, bir kaydın kendisini değil.

Bu ayrım yayın kararlarında kullanılır. Genişletmeden önce sorulacak soru, ulaşılan aygıt sayısının yeterince büyük olup olmadığı kadar, ulaşılan kovalarda özelliğin tam çalışıp çalışmadığıdır. İkisine de ayrı ayrı bakılır.

#### Özet

- Bu dersin işlerliği: tam. `NewFeed @ device-a`, aygıtın kovası açık olduğu için bayrak yeteneğini sağlar ve yeni akış tam çalışır; kapalı kovalardaki üç aygıtta aynı özellik kapalı kaldı.
- Aşamalı yayın sürümü kovalara açar; durdurma yayının genişlemesini keser (iOS'ta yalnız otomatik güncellemeyi), ama kurulmuş sürümü geri çekmez.
- Mobilde geri alma çoğunlukla özelliğin geri alınmasıdır: uzaktan kapatma bayrağı mağaza incelemesini beklemeden etkiyi keser, bayraksız özellikte yalnız ileri düzeltme kalır.
- Sekiz kayıttan beşi tam, üçü kapalıydı; etkilenen üç. Bayrak çekilince dört aygıtın dördünde yeni akış kapalıya döndü.
- Yayın bir kova genişleyince odak kayıt değişmedi; işlerlik ulaşılan aygıt sayısını değil, her kovadaki koşulu sınıflar.

#### Sonraki Adım

Bu derste yeni sürüm bazı kullanıcılara ulaşmadı ve eski sürüm sorunsuz yaşadı. Her zaman böyle olmaz: bazı sürümleri artık desteklemek mümkün değildir. Sıradaki ders, `Zorunlu Güncelleme Stratejisi`, en düşük desteklenen sürümü, yumuşak ve zorunlu güncelleme istemini ve eski sürümün desteğinin sona ermesini ele alır. Sonra sunucunun istediği en düşük sürümün altında kalan bir istemcide ödeme özelliğinin ne kadar çalıştığını ölçer.

### Zorunlu Güncelleme Stratejisi

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/zorunlu-guncelleme-stratejisi

Zorunlu güncelleme stratejisi, sunucunun artık desteklemediği eski istemcileri ne zaman, hangi istemle ve hangi gerekçeyle yeni sürüme yönlendireceğini belirler. Ders, en düşük sürümün altında kalan bir istemcide ödeme özelliğinin ne kadar çalıştığını ölçer.

Önceki derste yayın aşamalı ilerledi ve eski sürüm bir süre daha sorunsuz yaşadı. Her eski sürüm sonsuza dek yaşayamaz. Sunucu bir noktada eski bir sözleşmeyi sunmayı bırakır, bir güvenlik düzeltmesi eski sürümün çalışmasını imkânsız kılar ya da bir ödeme sağlayıcısı eski bir istemciyle konuşmayı reddeder. Bu ders, o noktaya nasıl hazırlanılacağını anlatır.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Bu derste koşul, istemcinin sürümüdür.

#### En Düşük Desteklenen Sürüm

Her sunucunun fiilen konuşabildiği bir istemci aralığı vardır. Aralığın alt sınırı en düşük desteklenen sürümdür: bunun altındaki bir istemci sunucuya bağlanabilir, ama sunucu onun isteklerinin bir kısmını kabul etmez. Alt sınır bir tercihle belirlenir. Ekip eski sözleşmeyi sunmanın bakım yükünü taşımak istemez, ya da bir güvenlik gerekçesiyle eski istemciyi kabul edemez.

Alt sınırın belirlenmesi bir veri kararıdır, bir kod kararı değil. Ekip sahadaki sürüm dağılımına bakar: hangi sürümler hâlâ kullanımda, her birinde kaç kullanıcı var. Dağılımı bilmeden alt sınırı yükseltmek, kullanıcıların bir bölümünü uyarısız bir engelle karşılamak olur.

Alt sınır sunucuda tutulur ve istemci onu uzaktan okur. Alt sınır uygulamanın içine gömülürse, o sınırı değiştirmek için yeni bir sürüm gerekir ve alt sınırın amacı olan eski istemcilerin yönlendirilmesi gerçekleşmez, çünkü eski istemci yeni sınırı hiç görmez.

#### Yumuşak ve Zorunlu Güncelleme İstemi

Güncelleme istemi iki biçimde olur.

Yumuşak istem kullanıcıya yeni bir sürümün çıktığını söyler ve kullanıcı bunu erteleyebilir. İstem bir şerit, bir iletişim kutusu ya da ayarlar ekranında bir satır olarak görünür. Yumuşak istemin amacı, alt sınırın altına henüz düşmemiş istemcileri yeni sürüme yöneltmektir.

Zorunlu istem uygulamayı kullanılamaz kılar ve yalnız güncelleme ekranını gösterir. Kullanıcı güncellemeden uygulamayı kullanamaz. Zorunlu istem yalnız istemci alt sınırın altındayken, yani sunucu onunla gerçekten konuşamazken gösterilir. Daha geniş kullanılırsa, kullanıcıyı kendisine zararı olmayan bir sürüm için engellemiş olur.

Aşağıdaki iki parça politikanın sunucuda durduğu biçimi ve istemin kararını gösterir. Parçalar ölçülmez.

```yaml
# Platform kodu: guncelleme politikasi
update-policy:
  hard-minimum: 5
  latest: 6
```

```swift
// Platform kodu: guncelleme istemi
if installed < policy.hardMinimum {
    showBlockingUpdateScreen()
} else if installed < policy.latest {
    showUpdateBanner()
}
```

Karar iki karşılaştırmaya iner. Kurulu sürüm en düşük sürümün altındaysa istem zorunludur, ama en son sürümün altındaysa yumuşaktır. Bu iki sınırın arasındaki sürümler uygulamayı kullanmaya devam eder.

#### Eski Sürümün Desteğinin Sonu

Destek bir günde kesilmez. Ekip önce duyurur, sonra yumuşak istemle yönlendirir, en son zorunlu istemi açar. Her adım, bir öncekinin yeterince etkili olmadığını görmeye bağlıdır. Yumuşak istem yeterli sayıda kullanıcıyı yeni sürüme taşıdıysa zorunlu adıma gerek kalmayabilir.

Destek sonu iletişimi de bir iştir. Zorunlu ekranın metni kullanıcıya neden güncellemesi gerektiğini söylemelidir; "yeni sürüm gerekli" yetmez, çünkü kullanıcı bunu bir pazarlama dili olarak okur. Gerekçe somut olduğunda, örneğin "ödeme sağlayıcısı bu sürümü artık desteklemiyor", kullanıcı güncellemeyi bir engel olarak değil bir gereklilik olarak görür.

#### Mağaza İncelemesinin Gecikmesi

Zorunlu güncelleme, yeni sürümün mağazada hazır olmasına bağlıdır. Alt sınır yükseltildiği anda yeni sürüm kullanıcıya sunulabilir olmalıdır; aksi hâlde zorunlu ekran, güncellenecek bir şey bulamayan kullanıcıyı çıkmaza sokar.

Mağaza incelemesinin ne kadar süreceği önceden bilinmez ve ekip onu belirleyemez. Bu yüzden sıra şöyle olmalıdır: önce yeni sürüm mağazada yayımlanır ve kullanıcılara sunulur; alt sınır ancak ondan sonra yükseltilir. Alt sınırın yükseltilmesi uzaktan bir değer değişikliğidir ve anında etkili olur. Bu hız tehlikelidir: yanlış sırada yapılan bir değişiklik, aygıtında güncelleme bulunmayan kullanıcıyı uygulamasız bırakır.

İncelemenin reddedilme ihtimali de plana girer. Güvenlik gerekçesiyle acil bir alt sınır yükseltmesi gerekiyorsa ve yeni sürüm henüz onaylanmamışsa, ekibin elinde alt sınırı yükseltmek ile beklemek arasında bir seçim kalır. Bu seçim kod değil, risk kararıdır.

#### Güncelleme Koşulunu Ölçmek

Ölçüm için üç özelliği ele alalım. `Payments` ödemedir; ağı ve `min:5` yeteneğini ister, yani istemcinin beşinci ana sürümde ya da üstünde olmasını. Ödeme sağlayıcısı eski istemciyi kabul etmez, bu yüzden yedeği yoktur. `Catalog` ürün listesidir; o da `min:5` ister, ama eski istemcide eski alanlarla çalışabilir ve yedeği kısıtlıdır. `Settings` ayarlardır ve yalnız ağı ister. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. `minimumFor(client)` istemcinin ana sürümünden `min:` yetenekleri üretir: dördüncü sürümdeki bir istemci `min:1`den `min:4`e kadar olanları taşır, `min:5`i taşımaz. `conditionsFor(clients)` bu yeteneklere ağı ekleyip her istemciden bir koşul kurar. `promptFor(client, policy)` istemcinin alacağı istemi söyler; bu dersin yerel işlevidir ve kuralın girdisi değildir.

```javascript
// updates.mjs — istemci surumunden kosul uretir, guncelleme istemini soyler
import { feature, condition } from "./operability.mjs";

export function minimumFor(client) {
  const levels = [];
  for (let level = 1; level <= client.major; level++) levels.push(`min:${level}`);
  return levels;
}

export function conditionsFor(clients) {
  return clients.map((client) => condition(client.name, ["network", ...minimumFor(client)]));
}

export function promptFor(client, policy) {
  if (client.major < policy.hardMinimum) return "zorunlu";
  if (client.major < policy.latest) return "yumusak";
  return "yok";
}

export const updateFeatures = [
  feature("Payments", ["network", "min:5"]),
  feature("Catalog", ["network", "min:5"], { "min:5": "kisitli" }),
  feature("Settings", ["network"]),
];
```

**YI7.** Model, en düşük sürümü tek bir ana sürüm numarasına indirger: istemci ya beşinci ana sürümde ve üstündedir ya değildir. Gerçek bir alt sınır ara ve yama sürümünü de içerebilir ve ödeme sağlayıcısının isteği başka bir değişkene, örneğin sunucu tarafında tutulan bir uyumluluk tablosuna bağlı olabilir. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

İlk ölçüm üç istemciyi yan yana koyar: dördüncü, beşinci ve altıncı ana sürümdeki istemciler. Önce her istemcinin alacağı istem basılır, sonra dokuz kayıt gelir. Politika alt sınırı beş, en son sürümü altı olarak tanımlar.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, promptFor, updateFeatures } from "./updates.mjs";

const clients = [
  { name: "client-v4", major: 4 },
  { name: "client-v5", major: 5 },
  { name: "client-v6", major: 6 },
];
const policy = { hardMinimum: 5, latest: 6 };
for (const client of clients) {
  console.log(`istem: ${client.name} ${promptFor(client, policy)}`);
}
printLevels(assess(updateFeatures, conditionsFor(clients)));
```

```text
istem: client-v4 zorunlu
istem: client-v5 yumusak
istem: client-v6 yok
kapali: Payments @ client-v4 eksik=min:5
kisitli: Catalog @ client-v4 eksik=min:5
tam: Settings @ client-v4 eksik=-
tam: Payments @ client-v5 eksik=-
tam: Catalog @ client-v5 eksik=-
tam: Settings @ client-v5 eksik=-
tam: Payments @ client-v6 eksik=-
tam: Catalog @ client-v6 eksik=-
tam: Settings @ client-v6 eksik=-
```

Dördüncü sürümdeki istemci zorunlu istem alır ve ödemesi kapalıdır; eksik olan `min:5` yeteneğidir ve ödemenin yedeği yoktur. Aynı istemcide katalog kısıtlı sürer, çünkü kataloğun yedeği tanımlıdır, ayarlar ise tamdır. Beşinci sürümdeki istemci yumuşak istem alır ve üç özelliği de tamdır; altıncı sürümdeki istem almaz ve o da tamdır.

Zorunlu istemin ne zaman gösterildiğine bakalım: yalnız ödemenin kapalı olduğu istemcide. Bu tesadüf değildir. Zorunlu istem, istemcide bir özelliğin artık çalışmadığı noktada anlam kazanır; hiçbir şey kapalı değilken zorunlu istem göstermek, kullanıcıyı gereksiz yere engeller.

#### Güncellemenin Etkisi

Kullanıcı güncelleyince istemcinin ana sürümü değişir ve koşul yeniden kurulur. Güncelleme, kapalı özelliği açmanın tek yoludur; ekip uzaktan bir değer değiştirerek ödemeyi eski istemcide açamaz, çünkü eksik olan şey sunucu tarafında değil, istemcinin kendisindedir.

İkinci ölçüm aynı aygıtı güncellemeden önce ve sonra koşar. Koşul adları farklıdır, çünkü iki koşulun adı aynı olamaz. Yalnız ödeme ve katalog kayıtları basılır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, updateFeatures } from "./updates.mjs";

const before = { name: "before-update", major: 4 };
const after = { name: "after-update", major: 5 };
console.log(`guncelleme: ${before.major} -> ${after.major}`);
const records = assess(updateFeatures, conditionsFor([before, after]));
printLevels(records.filter((r) => r.feature !== "Settings"));
```

```text
guncelleme: 4 -> 5
kapali: Payments @ before-update eksik=min:5
kisitli: Catalog @ before-update eksik=min:5
tam: Payments @ after-update eksik=-
tam: Catalog @ after-update eksik=-
```

Güncellemeden önce ödeme kapalı, katalog kısıtlıdır. Güncellemeden sonra ikisi de tamdır. Ayarlar kaydı değişmediği için süzgeçle dışarıda bırakıldı.

**YI8.** Model, güncellemeyi tek adımlı ve başarılı varsayar. Gerçekte güncelleme yarıda kalabilir, aygıtın yeterli yeri olmayabilir, ya da kullanıcı güncelleme ekranını kapatıp uygulamayı terk edebilir. Kural bu ara durumları görmez; yalnız güncellemeden önceki ve sonraki iki koşulu ölçer.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `Payments @ client-v4`'tür. Sınırlayıcı ölçüm iki şeyi birlikte gösterir. Birincisi, odak kayıt kapalıdır ve aynı istemcide katalog kısıtlı, ayarlar tam sürer; altı kaydın ikisi etkilenir. İkincisi, güncelleme isteminin sertliği kayda girmez: aynı istemci zorunlu ya da yumuşak istem alsa da kaydı aynıdır.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { conditionsFor, promptFor, updateFeatures } from "./updates.mjs";

const clients = [
  { name: "client-v4", major: 4 },
  { name: "client-v5", major: 5 },
];
const records = assess(updateFeatures, conditionsFor(clients));
printLevels(records.filter((r) => r.condition === "client-v4"));
countLevels(records);

const strict = promptFor(clients[0], { hardMinimum: 5, latest: 6 });
const lenient = promptFor(clients[0], { hardMinimum: 3, latest: 6 });
console.log(`istem: ${strict} ${lenient}`);
const key = (list) => list.map((r) => `${r.level}:${r.feature}:${r.missing.join(",")}`).join("|");
const again = assess(updateFeatures, conditionsFor(clients));
console.log(`istem kayda girmez: ${key(records) === key(again) ? "evet" : "hayir"}`);
```

```text
kapali: Payments @ client-v4 eksik=min:5
kisitli: Catalog @ client-v4 eksik=min:5
tam: Settings @ client-v4 eksik=-
tam: 4
kisitli: 1
ertelenen: 0
kapali: 1
etkilenen: 2
istem: zorunlu yumusak
istem kayda girmez: evet
```

İlk üç satır dersin işlerliğini taşır: aynı istemcide ödeme kapalı, katalog kısıtlı, ayarlar tamdır. Sayım satırları altı kayıttan dördünün tam, birinin kısıtlı, birinin kapalı olduğunu söyler; etkilenen ikidir.

Son iki satır modelin sınırını gösterir. Aynı istemci için iki politika iki ayrı istem üretir: biri zorunlu, biri yumuşak. Ama koşulu kuran işlev politikayı hiç almaz ve kayıtlar iki durumda da aynıdır. İstem, ekibin kullanıcıyla konuşma biçimidir; işlerlik ise istemcide hangi yeteneğin bulunduğunu sınıflar. Zorunlu istem ödemeyi açmaz, yumuşak istem ödemeyi kapatmaz. Ödemenin açılması için istemcinin güncellenmesi gerekir ve istem yalnız bu güncellemeye yönlendirir.

Bu ayrım karar sırasını belirler. Önce hangi özelliğin hangi sürümde kapalı olduğu ölçülür, ardından istemin sertliği buna göre seçilir. Hiçbir özelliğin kapanmadığı bir sürüm için zorunlu istem kurmak, kaydın desteklemediği bir sertliktir.

#### Özet

- Bu dersin işlerliği: kapalı. `Payments @ client-v4`, istemci `min:5` yeteneğini taşımadığı ve ödemenin yedeği olmadığı için kapalı çıktı; aynı istemcide `Catalog` kısıtlı, `Settings` tam sürdü.
- En düşük desteklenen sürüm sunucuda tutulur ve uzaktan okunur; uygulamaya gömülürse eski istemci yeni sınırı hiç görmez.
- Yumuşak istem ertelenebilir ve alt sınırın üstündeki eski sürümleri yöneltir; zorunlu istem yalnız alt sınırın altındaki istemciyi durdurur.
- Altı kayıttan dördü tam, biri kısıtlı, biri kapalıydı; etkilenen iki. Güncelleme aynı aygıtta ödemeyi kapalıdan tama, kataloğu kısıtlıdan tama taşıdı.
- İstemin sertliği kayda girmedi; alt sınırı yükseltmeden önce yeni sürümün mağazada hazır olması gerekir.

#### Sonraki Adım

Bu derste istemcinin sürümü bir koşuldu ve güncelleme onu değiştirdi. Güncellemenin de bir bedeli vardır: indirilen ve kurulan uygulama aygıtta yer kaplar. Sıradaki ders, `Uygulama Boyutu Yönetimi`, uygulamanın boyutunun indirme kararına etkisini, isteğe bağlı varlık paketlerini ve platforma göre bölünmüş paketleri ele alır. Sonra ilk indirmede gelmeyen bir varlık paketine bağlı özelliğin ne kadar çalıştığını ölçer.

### Uygulama Boyutu Yönetimi

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/uygulama-boyutu-yonetimi

Uygulama boyutu yönetimi, ilk indirmenin neleri içereceğini ve hangi parçaların sonradan, isteğe bağlı olarak indiğini belirler. Ders, ilk indirmede gelmeyen bir varlık paketine bağlı özelliğin ne kadar çalıştığını ölçer.

Önceki ders eski sürümleri yeni sürüme yöneltmeyi anlattı. Yönelten her güncelleme aygıta bir şey indirir ve indirilen şey aygıtta yer kaplar. Bu ders, o indirmenin ne kadar büyük olması gerektiğini ve büyük olan parçanın nasıl sonraya bırakılabileceğini ele alır.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Bu derste koşul, aygıtta hangi varlık paketlerinin kurulu olduğudur.

#### Uygulama Boyutu ve İndirme Kararı

Kullanıcı bir uygulamayı indirmeden önce boyutuna bakar ve bu bakış her zaman bilinçli değildir. Bağlantısı sınırlı olan kullanıcı büyük bir uygulamayı ertelemeyi seçer. Aygıtında az yer kalan kullanıcı yeni bir uygulama için eskilerinden birini silmek zorunda kalır. Güncelleme de aynı kararı yeniden verdirir: büyük bir güncelleme, kullanıcının eski sürümde kalmasına yol açabilir ve bu, önceki dersin sorununu doğurur.

Burada oran ya da bayt vermiyoruz. Çünkü boyutun kullanıcıyı vazgeçirdiği eşik hem bağlantıya hem pazara hem de uygulamanın türüne göre değişir ve ekip bunu kendi sahasında ölçmelidir. Dersin söyleyebileceği şey yönü gösterir: ilk indirmeyi küçültmek, indirme kararını hafifletir.

Boyuta üç şey katkı yapar: uygulamanın kodu, uygulamanın içinde taşınan varlıklar ve çerçevenin çalışma zamanı. Kendi çizim motoruyla çalışan bir çerçeve motoru uygulamanın içinde taşır; yerel görünümlere köprüyle bağlanan bir çerçeve ise köprü katmanını. İkisinin payı ekibin kontrolünde değildir. Kontrolün olduğu yer varlıklardır: görüntüler, yazı tipleri, sesler, ek veriler.

#### İsteğe Bağlı Varlık Paketleri

Bütün varlıkların ilk indirmede gelmesi gerekmez. Her kullanıcı her özelliği kullanmaz. Fotoğraf filtreleri, ek dil verileri ya da ileri düzey bir oyun seviyesi, yalnız onu isteyen kullanıcıda gerekir. Bu varlıklar isteğe bağlı bir pakete ayrılır ve kullanıcı özelliği ilk kullandığında indirilir.

İsteğe bağlı paketin ilk karşılaşması bir indirmedir ve indirme ağa bağlıdır. Bağlantı yoksa paket inmez; paket inmediyse ona bağlı özellik çalışmaz. İşlerlik açısından bu durumun adı ertelenendir: özellik paket gelince çalışacak şekilde bekler. M31/K06'da bir indirme bütçesinin kaç örnekte aşıldığı soruldu. İsteğe bağlı varlık paketi indirilmemişse özellik ertelenendir; bu bir bütçe aşımı değildir. Aşım bir boyut sorusudur, ertelenen ise paketin henüz aygıtta olmamasıdır.

Aşağıdaki parça bir paketin istenişini gösterir; parça ölçülmez.

```swift
// Platform kodu: istege bagli varlik paketi
let request = NSBundleResourceRequest(tags: ["filters"])
request.beginAccessingResources { error in
    if error == nil { showFilters() } else { keepFiltersPending() }
}
```

Hata durumunda özelliğin kapatılması yerine bekletilmesi bir tasarım tercihidir. Özellik paket gelene kadar kullanıcıya "hazırlanıyor" gösterir ve paket indiğinde açılır. Kapatma, kullanıcıya özelliğin hiç olmadığını söyler; bekletme, özelliğin yakında hazır olacağını.

#### Platforma Göre Bölünmüş Paket

Aynı uygulamanın her aygıta aynı dosyaları götürmesi gerekmez. Bir aygıtın ekran yoğunluğu ve işlemci mimarisi bellidir; başka yoğunluk ve mimariler için hazırlanmış dosyalar o aygıtta işe yaramaz. Platformlar bunu bölünmüş paketle çözer: mağaza, yüklenen tek bir büyük pakette her aygıt için gereken parçayı seçer ve yalnız onu gönderir.

Android'de bölme, uygulama paketi yüklendiğinde varsayılan olarak açıktır; yapılandırma bloğu hangi bölmelerin üretileceğini ayarlar. iOS'ta ise mağaza benzer bir seçimi kendisi yapar ve ekip varlıkları etiketlerle düzenler. Aşağıdaki parça Android tarafını gösterir; parça ölçülmez.

```kotlin
// Platform kodu: platforma gore bolunmus paket
android {
    bundle {
        density { enableSplit = true }
        abi { enableSplit = true }
    }
}
```

Bölünmüş paket bir kaynak kazancıdır ve işlerliği ilk bakışta etkilemez: aygıt yalnız kendi parçasını alır ve özellikler aynı çalışır. Etkilediği yer indirme kararıdır.

#### Kullanılmayan Varlıklar

Bir uygulamanın büyümesinin sık görülen bir nedeni, artık kullanılmayan varlıkların pakette kalmasıdır. Bir ekran kaldırıldığında görselleri gitmeyebilir; bir kütüphane güncellendiğinde eski kopyası pakette kalabilir. Çözüm düzenli bir tarama alışkanlığıdır: hangi varlığın hangi koddan çağrıldığını bulmak ve çağrılmayanları ayıklamak. Her yayın öncesi bu taramayı yinelemek, boyutun sessizce büyümesini önler.

Taramanın bir sınırı vardır. Varlığa kodda bir ad dizgisiyle erişiliyorsa, tarama onu çağrılmıyor sanabilir. Silmeden önce o adın dinamik olarak kurulup kurulmadığı kontrol edilir. Yanlış silinen bir varlık, ancak kullanıcı o ekrana geldiğinde fark edilir.

#### Paketin Koşulunu Ölçmek

Ölçüm için üç özelliği ele alalım. `Gallery` galeridir; ağı ve temel paketi `pack:base` ister ve her kurulumda vardır. `Filters` fotoğraf filtreleridir; ağı ve `pack:filters` yeteneğini ister, paket yoksa ertelenen çalışır. `Stickers` çıkartmalardır; `pack:stickers` ister ve o da paket yoksa ertelenen çalışır. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. `packsFor(install)` kurulumdaki paketlerden bir koşul üretir: ağ her kurulumda vardır, her kurulu paket `pack:` önekiyle bir yetenek olur. `assetsAtInstall(install)` kurulumdaki varlık sayısını toplar; bu sayı dersin yerel sayacıdır ve kuralın girdisi değildir. Boyut bayt olarak değil, varlık sayısı olarak sayılır.

```javascript
// packs.mjs — kurulu paketlerden kosul uretir, varlik sayisini toplar
import { feature, condition } from "./operability.mjs";

const assetCounts = { base: 30, filters: 12, stickers: 8 };

export function packsFor(install) {
  return condition(install.name, ["network", ...install.packs.map((name) => `pack:${name}`)]);
}

export function assetsAtInstall(install) {
  let total = 0;
  for (const name of install.packs) total += assetCounts[name];
  return total;
}

export const packFeatures = [
  feature("Gallery", ["network", "pack:base"]),
  feature("Filters", ["network", "pack:filters"], { "pack:filters": "ertelenen" }),
  feature("Stickers", ["network", "pack:stickers"], { "pack:stickers": "ertelenen" }),
];
```

**YI9.** Model, varlık sayısını boyutun vekili olarak kullanır: otuz, on iki ve sekiz birer varlık sayısıdır, bayt değildir. Bir varlık görüntü de olabilir, uzun bir ses dosyası da; sayı boyutu doğrudan vermez. Model yalnız iki kurulumu karşılaştırmak için yeterli bir sayaç sunar ve bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

İlk ölçüm iki kurulumu yan yana koyar: yalnız temel paketi indiren ve bütün paketleri indiren kurulumlar. Önce her kurulumun varlık sayısı basılır, sonra altı kayıt gelir.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { packsFor, assetsAtInstall, packFeatures } from "./packs.mjs";

const installs = [
  { name: "base-install", packs: ["base"] },
  { name: "full-install", packs: ["base", "filters", "stickers"] },
];
for (const install of installs) {
  console.log(`kurulum: ${install.name} varlik=${assetsAtInstall(install)}`);
}
printLevels(assess(packFeatures, installs.map(packsFor)));
```

```text
kurulum: base-install varlik=30
kurulum: full-install varlik=50
tam: Gallery @ base-install eksik=-
ertelenen: Filters @ base-install eksik=pack:filters
ertelenen: Stickers @ base-install eksik=pack:stickers
tam: Gallery @ full-install eksik=-
tam: Filters @ full-install eksik=-
tam: Stickers @ full-install eksik=-
```

Temel kurulumda galeri tamdır; filtreler ve çıkartmalar ertelenen çıkar. Eksik olan paket yeteneğidir ve yedek tanımı ertelenendir. Tam kurulumda üç özellik de tamdır, ama ilk indirmedeki varlık sayısı otuzdan elliye çıkar. İşlerlik kazancı ile ilk indirmenin büyümesi aynı kararın iki yüzüdür.

#### Paket İndirilince

Kullanıcı filtreleri ilk kez açtığında paket iner ve kurulum değişir. Paket yeteneği koşula eklenir, `Filters` ertelenenden tama geçer. Çıkartma paketi hâlâ inmemiştir, ertelenen kalır.

İkinci ölçüm aynı kurulumu paket inmeden önce ve sonra koşar. Koşul adları farklıdır, çünkü iki koşulun adı aynı olamaz. Galeri iki durumda da tam olduğu için süzgeçle dışarıda bırakıldı.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { packsFor, assetsAtInstall, packFeatures } from "./packs.mjs";

const before = { name: "base-install", packs: ["base"] };
const after = { name: "filters-downloaded", packs: ["base", "filters"] };
console.log(`indirme: varlik ${assetsAtInstall(before)} -> ${assetsAtInstall(after)}`);
const records = assess(packFeatures, [before, after].map(packsFor));
printLevels(records.filter((r) => r.feature !== "Gallery"));
```

```text
indirme: varlik 30 -> 42
ertelenen: Filters @ base-install eksik=pack:filters
ertelenen: Stickers @ base-install eksik=pack:stickers
tam: Filters @ filters-downloaded eksik=-
ertelenen: Stickers @ filters-downloaded eksik=pack:stickers
```

Filtreler indirmeden sonra tamdır, çıkartmalar ertelenen kalır. İndirme yalnız indirilen paketin yeteneğini ekler; öteki paketleri etkilemez. Kurulu varlık sayısı otuzdan kırk ikiye çıkar.

**YI10.** Model, paketin indirmesini tek adımlı ve başarılı varsayar. Gerçekte indirme yarıda kesilebilir, aygıtta yer olmayabilir ya da paketin sürümü uygulamanın sürümüyle uyuşmayabilir. Kural bu ara durumları görmez; yalnız paket yeteneğinin koşulda olup olmadığına bakar.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `Filters @ base-install`'dır. Sınırlayıcı ölçüm iki şeyi birlikte gösterir. Birincisi, odak kayıt ertelenendir ve altı kaydın ikisi etkilenir. İkincisi, ertelenen bir bütçe aşımı değildir: ilk indirmedeki varlık sayısı için bir sınır konursa, sınırı aşan kurulum tam kayıtlı olandır; sınırı aşmayan kurulum ertelenen kayıtlıdır.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { packsFor, assetsAtInstall, packFeatures } from "./packs.mjs";

const installs = [
  { name: "base-install", packs: ["base"] },
  { name: "full-install", packs: ["base", "filters", "stickers"] },
];
const records = assess(packFeatures, installs.map(packsFor));
printLevels(records.filter((r) => r.feature === "Filters"));
countLevels(records);

const allowance = 40;
const within = (install) => (assetsAtInstall(install) <= allowance ? "icinde" : "asti");
console.log(`ilk indirme varlik: ${assetsAtInstall(installs[0])} ${assetsAtInstall(installs[1])}`);
console.log(`butce: ${installs[0].name} ${within(installs[0])} ${installs[1].name} ${within(installs[1])}`);
```

```text
ertelenen: Filters @ base-install eksik=pack:filters
tam: Filters @ full-install eksik=-
tam: 4
kisitli: 0
ertelenen: 2
kapali: 0
etkilenen: 2
ilk indirme varlik: 30 50
butce: base-install icinde full-install asti
```

İlk iki satır dersin işlerliğini taşır: aynı özellik, aynı kod; yalnız paketin kurulu olup olmaması değişti ve kayıt ertelenenden tama geçti. Sayım satırları altı kayıttan dördünün tam, ikisinin ertelenen olduğunu söyler; etkilenen ikidir.

Son iki satır modelin sınırını gösterir. Dersin yerel sınırı kırk varlıktır. Temel kurulum bu sınırın içinde kalır ve filtreleri ertelenen bırakır. Tam kurulum sınırı aşar, ama üç özelliği de tamdır. Yani ertelenen olmak bir aşım belirtisi değildir, tam olmak da bir uyum belirtisi değildir. Kural sınırı görmez; yalnız paket yeteneğinin koşulda olup olmadığına bakar.

Bu ayrım kararı iki soruya böler. İlk indirme küçük mü, sorusu boyutun sorusudur. Filtreler çalışıyor mu, sorusu işlerliğin sorusudur. Boyutu küçültmek için paketi ayırmak ikinci sorunun cevabını tamdan ertelenene çevirir; bu bir bedeldir ve ekip onu bilerek öder.

#### Özet

- Bu dersin işlerliği: ertelenen. `Filters @ base-install`, filtre paketi ilk indirmede gelmediği ve yedek tanımı ertelenen olduğu için ertelenen çıktı; aynı kurulumda `Gallery` tam kaldı.
- İlk indirmeyi küçültmek indirme kararını hafifletir; boyutun kullanıcıyı vazgeçirdiği eşik ekibin kendi sahasında ölçeceği bir değerdir.
- İsteğe bağlı varlık paketi sonradan iner; inmeyen paketin özelliği çalışmaz, ama paket gelince açılır. Platforma göre bölünmüş paket her aygıta yalnız gereken parçayı gönderir.
- Altı kayıttan dördü tam, ikisi ertelenendi; etkilenen iki. Paket inince `Filters` tama geçti, `Stickers` ertelenen kaldı.
- İlk indirme sınırını aşan kurulumun üç özelliği de tamdı; ertelenen bir bütçe aşımı değildir.

#### Sonraki Adım

Bu derste sorun, aygıtta bir şeyin bulunmamasıydı ve ekip bunu önceden bilebildi. Kursun son dersi, `Çökme ve Kullanım İzleme`, bilinmeyeni konu alır: sahada yayımlanmış bir yapı ne yapıyor, nerede çöküyor, hangi özelliği kim kullanıyor. Bağlantı dönene dek bekleyen çökme raporunu ve izlemenin kendi koşulunu ölçer.

### Çökme ve Kullanım İzleme

Source: https://academia.sh/tr/kurslar/capraz-platform-uretim/cokme-ve-kullanim-izleme

Çökme ve kullanım izleme, yayımlanmış bir yapının sahada ne yaptığını iki platformdan birleşik olarak toplamayı anlatır. Ders, çevrimdışı oturumda oluşan çökme raporunun ertelenen kaldığını ve kullanıcı izninin olayları kapattığını ölçer.

Önceki ders ilk indirmede gelmeyen bir paketi ele aldı. Ekip o durumu önceden bilebiliyordu: hangi paket nerede yok, kurulumdan okunur. Yayımlanmış bir yapıda ise bilinmeyen çoktur. Hangi aygıtta çöktü, hangi ekranı kim kullanıyor, hangi özellik sessizce çalışmıyor? Bu ders, sahadan gelen bu bilgiyi toplamayı ve toplamanın kendi koşulunu ölçer.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Bu kez soru izlemenin kendisine sorulur. Çökmeyi bildiren düzenek, çöktüğü anda ağsız kalmış bir aygıtta ne kadar çalışır?

#### İki Platformdan Tek Telemetri

İki platform, iki ayrı çökme düzeneği ve iki ayrı olay kaynağı demektir. iOS'ta sistem tanılama verisini uygulamaya bir sonraki açılışta teslim eder; Android'de uygulama yakalanmamış hata için bir işleyici kaydeder. Verinin biçimi, adı ve zamanı farklıdır. Ekibin işi, bu farkı toplama noktasında kapatmaktır: iki platform da aynı alanlara sahip tek bir olay biçimi üretir, böylece panoda "çökme" tek bir kavram olur.

Birleşik biçim üç şeyi taşımalıdır: olayın türü, oturumun kimliği ve yapının sürümü. Sürüm olmadan bir çökmeyi hangi yayına bağlayacağınız bilinmez. Önceki derslerde gördüğünüz aşamalı yayın ve geri alma kararları bu bağı kullanır: yeni kovaya açılan sürümün çökme raporları artarsa durdurma ve uzaktan kapatma bayrağı o veriyle verilir.

#### Çökme Raporu ve Simgeleştirme

Çökme raporu, çöken anın izini taşır: hangi işlevden hangi işleve gidildiği. Yayına çıkan yapıda bu izdeki adlar kısaltılmış ve adresler sayısaldır; okunur olması için yapıyı üretirken oluşan sembol dosyasına bakılır. Adresleri işlev ve satır adlarına çevirmeye simgeleştirme denir. Sembol dosyası yapıyla aynı sürümden olmalıdır ve her yayında saklanmalıdır; saklanmamış bir yayının çökmesi okunamaz.

Aşağıdaki iki parça, çöken anı yerelde kaydetmenin platform tarafını gösterir. Parçalar ölçülmez.

```swift
// Platform kodu: iOS tanilama abonesi
final class DiagnosticsReceiver: NSObject, MXMetricManagerSubscriber {
    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads { crashStore.save(payload.jsonRepresentation()) }
    }
}
```

```kotlin
// Platform kodu: yakalanmamis hata isleyicisi
Thread.setDefaultUncaughtExceptionHandler { _, error ->
    crashStore.save(error.stackTraceToString())
}
```

İki parçada da ilk iş aynıdır: raporu yerel depoya yazmak. Ağa göndermek çöken süreçte güvenilir değildir; süreç gönderim bitmeden ölebilir. Gönderim, uygulama sonraki açılışında sağlam bir süreçte yapılır.

#### Analitik Olayları ve Kullanıcı İzni

Analitik olayı, kullanıcının uygulamada ne yaptığını kaydeder: bir ekran açıldı, bir düğmeye basıldı. Çökme raporu bir hatanın izidir; analitik olayı ise davranıştır ve kullanıcıya bağlanabilir. Bu yüzden ikisi aynı izne tabi tutulmaz. Kullanıcı davranışın izlenmesini kapattıysa analitik olayı hiç üretilmez ve sonradan gönderilmek üzere saklanmaz. Saklamak da bir toplamadır.

Burada bir ayrım önemlidir: izin bir yetenektir ve yedeği yoktur. Ağ yokken olay beklenebilir, çünkü ağ geri gelir. İzin yokken olay beklemez, çünkü izin kendiliğinden gelmez. İlki ertelenen, ikincisi kapalıdır. Çökmesiz oturum sayısı da buraya bağlanır: bir oturumun çökmediğini söylemek için o oturumdan rapor gelmemiş olması yetmez, raporun gelebilecek durumda olması gerekir. Oran burada yazılmaz; sayı oturum sayısıdır.

#### İzlemenin Koşulunu Ölçmek

Ölçüm üç özelliği ele alır. `Settings` izlemeye hiç ihtiyaç duymaz. `CrashReport` ağa ihtiyaç duyar ve ağ yoksa ertelenen çalışır. `UsageEvent` hem ağa hem kullanıcı iznine ihtiyaç duyar; ağın yedeği ertelenen, iznin yedeği yoktur. Çekirdek önceki derslerdeki gibidir ve her derste aynı biçimde yeniden verilir.

```javascript
// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}
```

Çekirdeğin ardından dersin yardımcı dosyası gelir. `conditionsFor(session)` bir oturumun ağını ve iznini yeteneğe çevirir.

```javascript
// telemetry.mjs — oturumun agini ve kullanici iznini yetenege cevirir
import { feature, condition } from "./operability.mjs";

export function conditionsFor(session) {
  const has = [];
  if (session.network) has.push("network");
  if (session.consent) has.push("consent");
  return condition(session.name, has);
}

export const telemetryFeatures = [
  feature("Settings", []),
  feature("CrashReport", ["network"], { network: "ertelenen" }),
  feature("UsageEvent", ["network", "consent"], { network: "ertelenen" }),
];
```

**YI11.** Model, çökme raporunu ağdan başka hiçbir yeteneğe bağlamaz. Gerçekte raporun gönderimi de kullanıcının tanılama tercihine bağlı olabilir; bu ders raporu yalnız ağ eksikliği için ölçer, tercih ayrıca eklenebilecek bir yetenektir.

İlk ölçüm üç oturumu üç özellikte koşar: ağı ve izni olan, ağı olmayan ve izni olmayan oturum.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, telemetryFeatures } from "./telemetry.mjs";

const sessions = [
  { name: "online", network: true, consent: true },
  { name: "offline", network: false, consent: true },
  { name: "no-consent", network: true, consent: false },
];
printLevels(assess(telemetryFeatures, sessions.map(conditionsFor)));
```

```text
tam: Settings @ online eksik=-
tam: CrashReport @ online eksik=-
tam: UsageEvent @ online eksik=-
tam: Settings @ offline eksik=-
ertelenen: CrashReport @ offline eksik=network
ertelenen: UsageEvent @ offline eksik=network
tam: Settings @ no-consent eksik=-
tam: CrashReport @ no-consent eksik=-
kapali: UsageEvent @ no-consent eksik=consent
```

Ağ ve izin varken üç özellik de tamdır. Ağ yokken rapor ve olay ertelenen olur, ayarlar tam kalır. İzin yokken rapor gider, olay kapalıdır ve eksik olan yetenek `consent`'tir. Aynı `UsageEvent` iki farklı eksikle iki farklı düzeye düştü: eksik ağsa ertelenen, eksik izinse kapalı.

#### Bağlantı Dönene Dek

Çevrimdışı oturumda çöken uygulama raporu yerel kuyruğa yazar. Kuyruk, ders-yerel `eventQueue` dizisidir. Bağlantı döndüğünde kuyruk boşaltılır ve rapor gider. Gönderimin kendisi bir iştir; işlerlik yalnız o anda raporun gidebilir olup olmadığını söyler.

İkinci ölçüm bir çökmeyi iki an için koşar: bağlantı yokken ve bağlantı döndükten sonra. Aynı ölçümde altı oturumluk bir listede kaç oturumun çökmesiz göründüğü, kaçının gerçekten çökmediğiyle karşılaştırılır.

```javascript
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { conditionsFor, telemetryFeatures } from "./telemetry.mjs";

const crashReport = telemetryFeatures.filter((item) => item.name === "CrashReport");
const eventQueue = [];

printLevels(assess(crashReport, [conditionsFor({ name: "offline", network: false, consent: true })]));
eventQueue.push({ kind: "crash" });
console.log(`kuyruk: ${eventQueue.length}`);
printLevels(assess(crashReport, [conditionsFor({ name: "reconnected", network: true, consent: true })]));
console.log(`kuyruk bosaltildi: ${eventQueue.splice(0).length}`);

const sessions = [
  { crashed: false, delivered: false },
  { crashed: false, delivered: false },
  { crashed: true, delivered: true },
  { crashed: false, delivered: false },
  { crashed: true, delivered: false },
  { crashed: false, delivered: false },
];
const seen = sessions.filter((item) => !item.delivered).length;
const actual = sessions.filter((item) => !item.crashed).length;
console.log(`oturum: ${sessions.length} cokmesiz gorunen: ${seen} gercek: ${actual}`);
```

```text
ertelenen: CrashReport @ offline eksik=network
kuyruk: 1
tam: CrashReport @ reconnected eksik=-
kuyruk bosaltildi: 1
oturum: 6 cokmesiz gorunen: 5 gercek: 4
```

Rapor çevrimdışıyken ertelenen, bağlantı döndükten sonra tamdır. Kuyruktaki tek rapor gitti. Altı oturumdan ikisi çökmüştü, ama biri henüz teslim edilmemişti; bu yüzden panoda beş oturum çökmesiz görünüyor, gerçekte dördü çökmedi. Bekleyen rapor, çökmesiz oturum sayısını olduğundan iyi gösterir.

**YI12.** Model, kuyruğu tek bir olay için ve kayıpsız varsayar. Gerçekte kuyruk dolabilir, uygulama yeniden kurulduğunda silinebilir ya da gönderim yarıda kesilebilir. Kural bunları görmez; yalnız ağın koşulda olup olmadığına bakar.

#### Sınırlayıcı Ölçüm

Bu dersin odak kaydı `CrashReport @ offline`'dır. Sınırlayıcı ölçüm iki şeyi birlikte gösterir. Birincisi, odak kayıt ertelenendir ve dokuz kaydın üçü etkilenir; izin olmayan oturumda `UsageEvent` yedeksiz olduğundan kapalıdır. İkincisi, kuyrukta bekleyen rapor sayısı kayda girmez: kuyrukta hiç rapor olmasa da üç rapor olsa da kayıt aynıdır.

```javascript
// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { conditionsFor, telemetryFeatures } from "./telemetry.mjs";

const sessions = [
  { name: "online", network: true, consent: true },
  { name: "offline", network: false, consent: true },
  { name: "no-consent", network: true, consent: false },
];
const records = assess(telemetryFeatures, sessions.map(conditionsFor));
printLevels(records.filter((r) => `${r.feature} @ ${r.condition}` === "CrashReport @ offline"));
printLevels(records.filter((r) => `${r.feature} @ ${r.condition}` === "UsageEvent @ no-consent"));
countLevels(records);

const crashReport = telemetryFeatures.filter((item) => item.name === "CrashReport");
const queued = [0, 3].map((count) => ({
  count,
  level: assess(crashReport, [conditionsFor(sessions[1])])[0].level,
}));
const same = queued[0].level === queued[1].level ? "evet" : "hayir";
console.log(`kuyruk ${queued[0].count} ve ${queued[1].count}: kayit ayni: ${same}`);
```

```text
ertelenen: CrashReport @ offline eksik=network
kapali: UsageEvent @ no-consent eksik=consent
tam: 6
kisitli: 0
ertelenen: 2
kapali: 1
etkilenen: 3
kuyruk 0 ve 3: kayit ayni: evet
```

İlk iki satır dersin iki ucunu verir: ağ eksikse ertelenen, izin eksikse kapalı. Sayımda dokuz kaydın altısı tam, ikisi ertelenen, biri kapalıdır; etkilenen üçtür. Son satır modelin sınırını gösterir: kural kuyruğu görmez. Kuyrukta bekleyen çok rapor bir iş yüküdür ve izleme panosunda ayrıca sayılır; işlerlik düzeyini değiştirmez.

#### Kurs Kapanışı

Kurs on dört derste on dört özelliği, her biri kendi koşulunda, aynı dört düzeyle ölçtü. Aşağıdaki tablo her dersin odak kaydını, işlerliğini ve o dersi sınırlayan ölçümü verir. İşlerlik sütunu her dersin `## Özet` bölümünde yazılı olandır.

| Ders | Odak kayıt | İşlerlik | Sınırlayıcı ölçüm |
|---|---|---|---|
| Modüler Kod Paylaşımı | `Scan @ android` | kapalı | `scanner` katmanı yazılmadı, yedek yok |
| Tasarım Sistemi Uyarlaması | `DatePicker @ android` | tam | İki seçici biçimi, aynı `picker` yeteneği |
| Yapılandırma ve Ortam Yönetimi | `Search @ prod-default` | tam | Varsayılanı kapalı bayrakta `prod-default-off` kapalı |
| Erişilebilirlik | `CardSwipe @ screen-reader` | kısıtlı | Yedeksiz `CustomGesture` kapalı |
| Çevrimdışı Öncelikli Tasarım | `FeedLocal @ offline` | kısıtlı | Ağ öncelikli `FeedRemote` kapalı |
| Eşitleme Stratejileri | `EditNote @ offline` | ertelenen | Bağlantı varken `EditNote` tam |
| Arka Plan İşleme Sınırları | `MediaBackup @ background-restricted` | ertelenen | Yedeksiz `LiveLocation` kapalı |
| Bağlantı Durumu Yönetimi | `Gallery @ weak-network` | kısıtlı | Aynı koşulda `Chat` ertelenen |
| Mobil Sürekli Teslimat | `NewCheckout @ production` | kapalı | Modelde imzalanma kayda girmez (tanım gereği) |
| Sürüm Yönetimi | `OrderHistory @ api-v2` | kısıtlı | İstemci sürümü kayda girmez |
| Aşamalı Yayın ve Geri Alma | `NewFeed @ device-a` | tam | Yayın genişleyince kayıt değişmez |
| Zorunlu Güncelleme Stratejisi | `Payments @ client-v4` | kapalı | İstemin sertliği kayda girmez |
| Uygulama Boyutu Yönetimi | `Filters @ base-install` | ertelenen | İlk indirme sınırı kayda girmez |
| Çökme ve Kullanım İzleme | `CrashReport @ offline` | ertelenen | Kuyruktaki rapor sayısı kayda girmez |

Tablodaki on üç satır önceki derslerden alınır; on dördüncü satırın düzeyi bu dersin kendi ölçümünden gelir. Son ölçüm tabloyu sayar ve çekirdeğin ilk derste verdiği açılış sayımını yanına koyar.

```javascript
// main.mjs
import { assess } from "./operability.mjs";
import { conditionsFor, telemetryFeatures } from "./telemetry.mjs";

const earlier = ["kapali", "tam", "tam", "kisitli", "kisitli", "ertelenen", "ertelenen",
  "kisitli", "kapali", "kisitli", "tam", "kapali", "ertelenen"];
const crashReport = telemetryFeatures.filter((item) => item.name === "CrashReport");
const focus = assess(crashReport, [conditionsFor({ name: "offline", network: false, consent: true })])[0];
const lessons = [...earlier, focus.level];

const order = ["tam", "kisitli", "ertelenen", "kapali"];
const spread = order.map((name) => `${name}=${lessons.filter((level) => level === name).length}`);
const opening = { tam: 5, kisitli: 1, ertelenen: 2, kapali: 1 };
const openingTotal = Object.values(opening).reduce((sum, count) => sum + count, 0);

console.log(`ders: ${lessons.length}`);
console.log(`odak: ${focus.feature} @ ${focus.condition} ${focus.level}`);
console.log(`dagilim: ${spread.join(" ")}`);
console.log(`cekirdek acilis: kayit=${openingTotal} etkilenen=${openingTotal - opening.tam}`);
```

```text
ders: 14
odak: CrashReport @ offline ertelenen
dagilim: tam=3 kisitli=4 ertelenen=4 kapali=3
cekirdek acilis: kayit=9 etkilenen=4
```

On dört dersin dağılımı üç tam, dört kısıtlı, dört ertelenen ve üç kapalıdır. Çekirdeğin açılış sayımı ise başka bir şeydir: dokuz kayıttan beşi tam, biri kısıtlı, ikisi ertelenen, biri kapalıydı ve etkilenen dörttü. Çekirdek tek bir uygulamanın dokuz kaydını sayar, tablo on dört dersi sayar. İki sayım aynı şey değildir ve birbirinden türetilemez.

Dağılım bir şeyi daha söyler: bir özelliğin kapalı kalması sık görülen bir sonuç değildir ve tam çalışması da kural değildir. Tablonun her satırı, bir koşulun bir özelliğe ne yaptığını gösteren ayrı bir ölçümdür.

#### Özet

- Bu dersin işlerliği: ertelenen. `CrashReport @ offline`, `network` eksik olduğu ve ertelenen yedeği tanımlandığı için ertelenen çıktı; aynı koşulda `UsageEvent` ertelenen, `Settings` tam kaldı.
- Çökme raporu çöken süreçte değil, yerel depoda saklanır ve bağlantı dönünce sağlam bir süreçten gider; kuyruktaki bir rapor bağlantı dönünce boşaldı.
- Kullanıcı izni yedeksiz bir yetenektir: `UsageEvent @ no-consent` kapalı çıktı, aynı olay ağ yokken yalnız ertelenen oldu.
- Teslim edilmemiş rapor çökmesiz oturum sayısını olduğundan iyi gösterir: altı oturumda beş çökmesiz görünürken dördü gerçekten çökmemişti.
- On dört dersin dağılımı üç tam, dört kısıtlı, dört ertelenen ve üç kapalıdır.

#### Sonraki Adım

Bu ders kursun ve kataloğun son dersidir; sırada ders yok. Elinizde kalan araç kursun sorusudur: bir özelliği, bir koşulu ve eksik yeteneği yazın, yedeğini tanımlayın ve kaydın hangi düzeye düştüğünü kendi uygulamanızda ölçün.

