> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Sık Sorulan Sorular

> Geliştirme ve operasyon sırasında en sık karşılaşılan sorular ve gerçek koda dayalı kısa cevaplar.

<AccordionGroup>
  <Accordion title="Yeni bir özellik nereden başlar?">
    Önce **Domain**: aggregate metodunu (gerekirse yeni value object / domain event) yazın. Sonra **Application**: `Command/Query` + `Validator` + `Handler`. Ardından **API**: ince bir controller action (`_mediator.Send`). Cache/e-posta/SMS gerekiyorsa bir `INotificationHandler<TDomainEvent>` ekleyin.

    Adım adım reçete: [Playbook'lar → Command'ı uçtan uca ekle](/playbooks/overview) ve [Application → Command'lar ve Query'ler](/application/commands-queries).
  </Accordion>

  <Accordion title="Handler'da SaveChanges neden yasak?">
    Çünkü domain event'ler yalnızca `IUnitOfWork.SaveEntitiesAsync` üzerinden dispatch edilir — `SaveEntitiesAsync`, SaveChanges'ten sonra biriken event'leri MediatR'a yollar (`DispatchDomainEventsAsync`). Düz `DbContext.SaveChangesAsync` bunu atlar, event'ler hiç çalışmaz. Bu yüzden `EFRepository.SaveChangesAsync` **bilerek exception fırlatır**.

    Doğru kullanım her zaman: `await _unitOfWork.SaveEntitiesAsync(ct);`. Detay: [Domain Event'ler](/events/domain-events).
  </Accordion>

  <Accordion title="Bir kaydı güncelledim ama cache güncellenmiyor — neden?">
    Birkaç olası neden:

    * Aggregate metodu ilgili **domain event'i yaymıyor** (`AddDomainEvent` eksik).
    * O event için bir **invalidation handler yok** (örn. `InvalidateFaqCacheDomainEventHandler`).
    * Handler farklı bir **tag** düşürüyor; spec'teki `WithTags(...)` ile handler'daki `RemoveByTagAsync(...)` aynı string olmalı (bu yüzden `CacheTags` sabitleri kullanılır).
    * Çoklu-instance'ta broadcast kapalı: `Cache:BroadcastTagInvalidation` ve Redis kayıt sırası (`AddRedisCache → AddCache → AddHybridCache`) doğru olmalı; aksi halde L1-only kalır.

    Reçete: [Cache Invalidation playbook'u](/playbooks/cache-invalidation).
  </Accordion>

  <Accordion title="Keycloak realm metadata unreachable hatası alıyorum.">
    Token doğrulama JWKS metadata'sına erişemiyordur. Kontrol listesi:

    * Keycloak ayakta mı (dev `8080`)?
    * `Keycloak` config'indeki authority/realm URL'i API'den erişilebilir mi (container ağı vs. host)?
    * Realm gerçekten kurulu mu? İlk kurulumda `KeycloakProvisioning` realm/client/user/mapper'ı RunOnce ile oluşturur.
    * JWKS cache 60 dk; yeni kurduysanız anahtar henüz çekilmemiş olabilir.

    Daha fazlası: [Keycloak → Sorun Giderme](/keycloak/troubleshooting).
  </Accordion>

  <Accordion title="Migration nasıl eklenir?">
    ```bash theme={null}
    dotnet ef migrations add <Ad> \
      -p src/DiyanetCleanArchitecture.Infrastructure.EFCore \
      -s src/DiyanetCleanArchitecture.API
    ```

    `-p` projesi DbContext'i (`Infrastructure.EFCore`), `-s` startup projesi (`API`). "No DbContext found" alıyorsanız genelde bu iki parametre eksiktir. Şemalar: `public` ve `messaging` (MassTransit outbox/inbox). Detay: [Veri → Migration'lar](/data/migrations).
  </Accordion>

  <Accordion title="Yeni bir proje nasıl üretilir (template)?">
    Şablon `dotnet new` template'i olarak paketlidir:

    ```bash theme={null}
    dotnet new install .
    dotnet new diyanet-ca -n MyProject
    ```

    `sourceName` `DiyanetCleanArchitecture` olduğundan tüm namespace'ler `MyProject` ile yeniden adlandırılır. Semboller: `Framework` (net10.0), `ProjectKeycloakId` (kebab-case, varsayılan `my-project`), `SkipRestore`. Detay: [Başlarken → Template Kullanımı](/getting-started/template-usage).
  </Accordion>

  <Accordion title="OTP SMS gelmiyor (dev/test).">
    NetGSM yapılandırması `Services:Sms` altındadır. Dev'de gerçek SMS göndermek istemiyorsanız `Phones[]` test numaralarını kullanın. Kontrol:

    * `Username` / `Password` / `MessageHeader` dolu mu?
    * Numara geçerli mobil formatta mı? Gönderim `Phone.ToNetGsmFormat()` ile yapılır.
    * Kayıt başarılı olduysa SMS bir kez gönderilmiştir; gelmezse **yeniden kayıt değil**, `resend-otp` çağrılır (60 sn cooldown, max 3 — bkz. [OTP playbook'u](/playbooks/otp-flow)).
    * `SmsServiceException` → API `503` döner; log'larda (Seq) gateway hatasını arayın.

    Detay: [SMS Servisi](/services/sms).
  </Accordion>

  <Accordion title="Personel token neden bu kadar çabuk düşüyor (5 dk)?">
    Yönetim (personel) realm'inde access token süresi bilerek **5 dakikadır** — yetki değişikliklerinin (rol/permission) hızlı yansıması ve sızan token'ın kısa ömürlü olması için. `keycloak-js` sessiz refresh ile token'ı yeniler; refresh anında `IUserContextProvider.InvalidateAsync(sub)` permission cache'ini düşürür. Vatandaş realm'inde token 2 saattir. Detay: [Personel Girişi playbook'u](/playbooks/staff-login).
  </Accordion>

  <Accordion title="Domain katmanından DbContext'e erişebilir miyim?">
    **Hayır.** Domain hiçbir altyapıya (EF Core, DbContext) bağımlı olmaz; yalnızca `IRepository<T>` / `IReadRepository<T>` / `IUnitOfWork` soyutlamalarını bilir. DbContext bağımlılığı Application/Infrastructure katmanlarında kalır. Aggregate içinde sorgu gerekiyorsa bunu bir domain service'e (örn. `UserRegistrationService`, repository alır) veya Application handler'a taşıyın. Detay: [Mimari → Bağımlılık Akışı](/architecture/dependency-flow).
  </Accordion>

  <Accordion title="Stage ve Prod farkı nedir?">
    * **Stage** (`docker-compose.stage.yml` / `.stage.local.yml`): nginx-proxy + path-based routing; `.stage.local.yml` HTTP-only yereldir, `.stage.yml` Let's Encrypt ile TLS.
    * **Prod** (`docker-compose.prod.yml`): nginx-proxy + acme-companion ile otomatik TLS. Hetzner çoklu-proje için `.platform.yml` + `.shared-infra.yml` + `.app.yml`.
    * Müşteri on-prem'de Let's Encrypt çalışmaz (intranet/kurumsal cert) ve mevcut Postgres kullanılır. Detay: [Operasyon → Docker Prod](/operations/docker-prod).
  </Accordion>

  <Accordion title="Çoklu instance'ta cache tutarlılığı nasıl sağlanıyor?">
    Bir node tag'i düşürdüğünde (`RemoveByTagAsync`) L1+L2 temizlenir ve bir `CacheInvalidationIntegrationEvent` RabbitMQ'ya yayılır. Diğer node'lar bunu tüketip **yalnızca kendi L1**'lerini temizler (`RemoveByTagLocallyAsync` — tekrar broadcast etmez). Kendi yayınını yeniden işlememek için `SourceNodeId == NodeId` olan event'ler atlanır (echo guard). L2 (Redis) ortak olduğundan zaten paylaşımlıdır; broadcast yalnızca L1'leri senkron tutar. Detay: [Çoklu-instance cache](/caching/multi-instance).
  </Accordion>
</AccordionGroup>

## İlgili

<CardGroup cols={2}>
  <Card title="Sözlük" icon="book" href="/reference/glossary">
    Terimlerin Türkçe açıklamaları.
  </Card>

  <Card title="Playbook'lar" icon="map" href="/playbooks/overview">
    Uçtan uca senaryolar.
  </Card>
</CardGroup>
