Skip to main content
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.
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.

Senaryolar

Vatandaş Kaydı

SignUpUserCommand → OTP SMS → VerifyOtp ile aktivasyon. Domain service, factory, domain event ve SMS gönderiminin tam akışı.

Personel Girişi

Admin SPA → Keycloak (OIDC + PKCE) → JwtBearer doğrulama → davet bazlı provisioning → permission policy → GetAdminMeQuery.

OTP / TOTP Akışı

SignInUser → challenge token → VerifyOtp / VerifyTotp → access + refresh token + oturum. ResendOtp cooldown ve limitleri.

Cache Invalidation

Bir kaydı güncelleyince cache nasıl düşer: domain event → RemoveByTagAsync → çoklu-instance broadcast. Yeni cache’lenebilir query ekleme.

Yeni Domain Event

Sıfırdan domain event ekleme reçetesi: event sınıfı → AddDomainEvent → handler → (opsiyonel) integration event yayını + abonelik.

Senaryo → katman matrisi

Her senaryonun hangi katmanlara dokunduğu:
SenaryoAPI (Controller)Application (Handler)Domain (Aggregate / Event)Infra (Servis)CacheEvent Bus
Vatandaş kaydıAuthControllerSignUpUserCommandHandlerUser + UserOtpGeneratedDomainEventSMS (NetGSM)
Personel girişiAuthController (Keycloak)ProvisionKeycloakUserCommandHandlerUser.ActivateOnExternalLoginKeycloak (JWKS)UserContext
OTP / TOTPAuthControllerVerifyOtpCommandHandler / VerifyTotpCommandHandlerUser.VerifyChallenge / VerifyTotpSMS / Authenticator
Cache invalidation(ilgili admin controller)Komut handler + InvalidateFaqCacheDomainEventHandlerFaqUpdatedDomainEventHybridCache (L1+L2)CacheInvalidationIntegrationEvent
Yeni domain eventXxxDomainEventHandlerXxxDomainEvent(opsiyonel)(opsiyonel)(opsiyonel)

Uçtan uca akışın iskeleti

Tüm senaryolar aynı omurgayı paylaşır:
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
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.

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:
1

Command + DTO tanımla

Application/Features/<Alan>/<Admin|Website>/Commands/<Ad>/ altında:
public class CreateFaqCommand : IRequest<IResponseWrapper<Guid>>
{
    public string Question { get; init; }
    public string Answer   { get; init; }
}
Dönüş tipi her zaman IResponseWrapper<T> (zarf).
2

Validator yaz (FluentValidation)

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

Handler yaz — aggregate metodunu çağır

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);
    }
}
4

Controller'a ince bir action ekle

[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateFaqCommand command)
    => Ok(await _mediator.Send(command));
Controller iş mantığı içermez; yalnızca _mediator.Send yapar.
5

Gerekirse domain event + handler ekle

Cache düşmesi, e-posta vs. gerekiyorsa aggregate’te AddDomainEvent(...) ve bir INotificationHandler<...> ekleyin. Bkz. Yeni Domain Event.
Detaylı CQRS anatomisi için Application — Command’lar ve Query’ler ve Pipeline Behavior’lar sayfalarına bakın.

Sonraki adımlar

İlk senaryo: Vatandaş Kaydı

En basit uçtan uca akışla başlayın.

Domain Event'ler

Event akışının teorik temeli.