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

# Playbook'lar

> Uçtan uca senaryolar — bir isteğin Controller'dan Domain'e, event'lere ve cache'e kadar tüm yolculuğu.

Playbook'lar tek bir katmanı değil **bir senaryonun tamamını** anlatır: HTTP isteği nereye düşer, hangi handler çalışır, aggregate hangi domain event'i yayar, o event'i kim dinler, sonunda kullanıcıya ne döner. Amaç, bu şablonla geliştirme yaparken "bu akış gerçekte nasıl işliyor?" sorusuna **gerçek koddan** cevap vermektir.

<Info>
  Her playbook gerçek dosya/sınıf/metod adlarına dayanır (örn. `SignUpUserCommandHandler`, `UserRegistrationService.RegisterNewUserAsync`, `SendOtpSmsDomainEventHandler`). Snippet'leri kopyalayıp kendi feature'ınıza uyarlayabilirsiniz.
</Info>

## Senaryolar

<CardGroup cols={2}>
  <Card title="Vatandaş Kaydı" icon="user-plus" href="/playbooks/citizen-registration">
    `SignUpUserCommand` → OTP SMS → `VerifyOtp` ile aktivasyon. Domain service, factory, domain event ve SMS gönderiminin tam akışı.
  </Card>

  <Card title="Personel Girişi" icon="shield-halved" href="/playbooks/staff-login">
    Admin SPA → Keycloak (OIDC + PKCE) → JwtBearer doğrulama → davet bazlı provisioning → permission policy → `GetAdminMeQuery`.
  </Card>

  <Card title="OTP / TOTP Akışı" icon="key" href="/playbooks/otp-flow">
    `SignInUser` → challenge token → `VerifyOtp` / `VerifyTotp` → access + refresh token + oturum. `ResendOtp` cooldown ve limitleri.
  </Card>

  <Card title="Cache Invalidation" icon="bolt" href="/playbooks/cache-invalidation">
    Bir kaydı güncelleyince cache nasıl düşer: domain event → `RemoveByTagAsync` → çoklu-instance broadcast. Yeni cache'lenebilir query ekleme.
  </Card>

  <Card title="Yeni Domain Event" icon="diagram-project" href="/playbooks/new-domain-event">
    Sıfırdan domain event ekleme reçetesi: event sınıfı → `AddDomainEvent` → handler → (opsiyonel) integration event yayını + abonelik.
  </Card>
</CardGroup>

## Senaryo → katman matrisi

Her senaryonun hangi katmanlara dokunduğu:

| Senaryo            |       API (Controller)      |                  Application (Handler)                 |       Domain (Aggregate / Event)       |    Infra (Servis)   |        Cache        |              Event Bus              |
| ------------------ | :-------------------------: | :----------------------------------------------------: | :------------------------------------: | :-----------------: | :-----------------: | :---------------------------------: |
| Vatandaş kaydı     |       `AuthController`      |               `SignUpUserCommandHandler`               | `User` + `UserOtpGeneratedDomainEvent` |     SMS (NetGSM)    |          —          |                  —                  |
| Personel girişi    | `AuthController` (Keycloak) |          `ProvisionKeycloakUserCommandHandler`         |     `User.ActivateOnExternalLogin`     |   Keycloak (JWKS)   |     UserContext     |                  —                  |
| OTP / TOTP         |       `AuthController`      | `VerifyOtpCommandHandler` / `VerifyTotpCommandHandler` |  `User.VerifyChallenge` / `VerifyTotp` | SMS / Authenticator |          —          |                  —                  |
| Cache invalidation |  (ilgili admin controller)  | Komut handler + `InvalidateFaqCacheDomainEventHandler` |         `FaqUpdatedDomainEvent`        |          —          | HybridCache (L1+L2) | `CacheInvalidationIntegrationEvent` |
| Yeni domain event  |              —              |                 `XxxDomainEventHandler`                |            `XxxDomainEvent`            |     (opsiyonel)     |     (opsiyonel)     |             (opsiyonel)             |

## Uçtan uca akışın iskeleti

Tüm senaryolar aynı omurgayı paylaşır:

```text theme={null}
Controller (thin)  →  _mediator.Send(command)
        │
        ▼
Pipeline behaviors  (Exception → Authorization → Validation → Permission)
        │
        ▼
Command Handler     (aggregate metodunu çağırır → AddDomainEvent içeride biriker)
        │
        ▼
IUnitOfWork.SaveEntitiesAsync   (SaveChanges → domain event'ler dispatch edilir)
        │
        ├──►  INotificationHandler<TDomainEvent>   (in-process: SMS, e-posta, cache drop)
        │
        └──►  IEventBus.PublishAsync (opsiyonel)   → MassTransit Outbox → RabbitMQ → IntegrationEventHandler
```

<Warning>
  Handler içinde **asla** `DbContext.SaveChangesAsync` çağrılmaz. Yazma akışında her zaman `IUnitOfWork.SaveEntitiesAsync(ct)` kullanılır — yalnızca o, kaydedilen aggregate'lerin domain event'lerini MediatR'a dispatch eder (`MediatorExtension.DispatchDomainEventsAsync`). `EFRepository.SaveChangesAsync` bunu zorlamak için bilerek exception fırlatır.
</Warning>

## Reçete: yeni bir Command'ı uçtan uca eklemek

En sık ihtiyaç, var olmayan bir işlem için yeni bir Command/Query yazmaktır. Sıra:

<Steps>
  <Step title="Command + DTO tanımla">
    `Application/Features/<Alan>/<Admin|Website>/Commands/<Ad>/` altında:

    ```csharp theme={null}
    public class CreateFaqCommand : IRequest<IResponseWrapper<Guid>>
    {
        public string Question { get; init; }
        public string Answer   { get; init; }
    }
    ```

    Dönüş tipi her zaman `IResponseWrapper<T>` (zarf).
  </Step>

  <Step title="Validator yaz (FluentValidation)">
    ```csharp theme={null}
    public class CreateFaqCommandValidator : AbstractValidator<CreateFaqCommand>
    {
        public CreateFaqCommandValidator()
        {
            RuleFor(x => x.Question).NotEmpty().MaximumLength(500);
            RuleFor(x => x.Answer).NotEmpty();
        }
    }
    ```

    `ValidationPipelineBehavior` validator'ı otomatik bulur; başarısızlık → 422 + `errors`.
  </Step>

  <Step title="Handler yaz — aggregate metodunu çağır">
    ```csharp theme={null}
    public class CreateFaqCommandHandler
        : IRequestHandler<CreateFaqCommand, IResponseWrapper<Guid>>
    {
        private readonly IRepository<Faq> _repo;
        private readonly IUnitOfWork _uow;
        // ctor ...

        public async Task<IResponseWrapper<Guid>> Handle(CreateFaqCommand request, CancellationToken ct)
        {
            var faq = Faq.Create(request.Question, request.Answer); // AddDomainEvent içeride
            await _repo.AddAsync(faq);
            await _uow.SaveEntitiesAsync(ct);   // ← domain event'ler burada dispatch
            return ResponseWrapper<Guid>.Success(faq.Id);
        }
    }
    ```
  </Step>

  <Step title="Controller'a ince bir action ekle">
    ```csharp theme={null}
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateFaqCommand command)
        => Ok(await _mediator.Send(command));
    ```

    Controller iş mantığı içermez; yalnızca `_mediator.Send` yapar.
  </Step>

  <Step title="Gerekirse domain event + handler ekle">
    Cache düşmesi, e-posta vs. gerekiyorsa aggregate'te `AddDomainEvent(...)` ve bir `INotificationHandler<...>` ekleyin. Bkz. [Yeni Domain Event](/playbooks/new-domain-event).
  </Step>
</Steps>

Detaylı CQRS anatomisi için [Application — Command'lar ve Query'ler](/application/commands-queries) ve [Pipeline Behavior'lar](/application/pipeline-behaviors) sayfalarına bakın.

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="İlk senaryo: Vatandaş Kaydı" icon="user-plus" href="/playbooks/citizen-registration">
    En basit uçtan uca akışla başlayın.
  </Card>

  <Card title="Domain Event'ler" icon="diagram-project" href="/events/domain-events">
    Event akışının teorik temeli.
  </Card>
</CardGroup>
