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

# Domain Event'ler

> Domain event nedir, aggregate içinde nasıl yayılır, MediatR ile nasıl dispatch edilir ve integration event'ten farkı.

Bir **domain event**, aggregate içinde **olup bitmiş** anlamlı bir gerçeği anlatır: "Kullanıcı oluşturuldu", "OTP üretildi", "Bağış başvurusu onaylandı". Geçmiş zamanlı isimlendirilir; çünkü olay zaten gerçekleşmiştir, iptal edilemez.

Domain event'ler **yan etkileri ana iş akışından ayırır**: aggregate yalnızca kendi durumunu değiştirir ve bir olay yayar; SMS gönderme, cache temizleme, başka servise haber verme gibi işler ayrı handler'lara düşer.

## DomainEvent + INotification

Her event `DomainEvent` tabanından türer (Version 7 GUID kimliği + UTC zaman damgası alır) ve MediatR'ın `INotification` arayüzünü uygular:

```csharp theme={null}
public class UserCreatedDomainEvent : DomainEvent, INotification
{
    public Guid UserId { get; }
    public FullName? FullName { get; }
    public Phone? Phone { get; }
    public Email? Email { get; }

    public UserCreatedDomainEvent(Guid userId, FullName? fullName, Phone? phone, Email? email)
    {
        UserId = userId; FullName = fullName; Phone = phone; Email = email;
    }
}
```

<Note>
  Event payload'u **değişmezdir** (yalnız `get` property'ler). Tüketici verisini değiştiremez; sadece okur. Olay, gerçekleşmiş bir durumun fotoğrafıdır.
</Note>

## AddDomainEvent ile aggregate'ta yayma

Aggregate, durumunu değiştirdiği davranış metodunun sonunda olayı kuyruğa ekler. Olay **hemen** işlenmez — yalnızca kayıt persist edildikten sonra dispatch edilmek üzere biriktirilir.

```csharp theme={null}
public void AddOtpChallenge(OtpCode code, OtpType type)
{
    if (CanRequestOtp == false)
        throw new DomainException("Kullanıcı durumu giriş yapmaya uygun değil.");

    var challenge = new UserOtpChallenge(this, GuidFactory.New(), code, type,
                                         DateTime.UtcNow.AddMinutes(type == OtpType.Sms ? 1 : 2));
    OtpChallenges.Add(challenge);

    AddDomainEvent(new UserOtpGeneratedDomainEvent(this, code, type, this.Phone, this.Email));
}
```

`AddDomainEvent` `EntityBase` üzerinde tanımlıdır ve olayı entity'nin iç `DomainEvents` listesine ekler.

## Dispatch: SaveEntitiesAsync sonrası MediatR

Olaylar, yazma işlemi başarıyla tamamlandıktan **sonra** yayınlanır. Akış `IUnitOfWork.SaveEntitiesAsync` içinde gerçekleşir:

```mermaid theme={null}
flowchart LR
    A[Command Handler] --> B[aggregate metodu<br/>AddDomainEvent]
    B --> C[UnitOfWork.SaveEntitiesAsync]
    C --> D[SaveChanges<br/>DB transaction]
    D --> E[DispatchDomainEventsAsync<br/>MediatR Publish]
    E --> F[INotificationHandler'lar]
```

`SaveChanges` çağrısından sonra change tracker'daki tüm entity'lerin `DomainEvents` listesi toplanır, MediatR `Publish` ile yayınlanır ve ardından `ClearDomainEvents()` ile temizlenir.

<Tip>
  Sıralama önemlidir: olaylar **veritabanı yazımından sonra** dispatch edilir. Böylece bir handler patlarsa bile durum zaten tutarlı şekilde persist edilmiştir; ayrıca handler'lar oluşturulan kaydın gerçekten var olduğuna güvenebilir. Detaylar Veri Katmanı'ndadır.
</Tip>

## Gerçek event listesi

`DiyanetCleanArchitecture.Domain/Events/` klasöründeki olaylardan bir kesit:

<AccordionGroup>
  <Accordion title="User olayları">
    `UserCreatedDomainEvent`, `UserOtpGeneratedDomainEvent`, `UserOtpResentDomainEvent`,
    `UserPhoneNumberVerifiedDomainEvent`, `UserEmailVerifiedDomainEvent`,
    `UserRoleAssignedDomainEvent`, `UserRoleRevokedDomainEvent`,
    `UserTokensInvalidatedDomainEvent`, `UserTotpEnabledDomainEvent`.
  </Accordion>

  <Accordion title="Citizen olayları">
    `CitizenCreatedDomainEvent`, `CitizenOtpGeneratedDomainEvent`, `CitizenOtpResentDomainEvent`,
    `CitizenPhoneNumberVerifiedDomainEvent`, `CitizenEmailVerifiedDomainEvent`,
    `CitizenAccountDeactivatedDomainEvent`, `CitizenTokensInvalidatedDomainEvent`,
    `CitizenRefreshTokenRevokedDomainEvent`.
  </Accordion>

  <Accordion title="Faq olayları">
    `FaqCreatedDomainEvent`, `FaqUpdatedDomainEvent`, `FaqDeletedDomainEvent`, `FaqReorderedDomainEvent`.
  </Accordion>

  <Accordion title="BagisBasvuru olayları (Türkçe isimli)">
    `BagisBasvuruOlusturulduDomainEvent`, `BagisBasvuruIncelemeyeAlindiDomainEvent`,
    `BagisBasvuruBelgeBeklendiDomainEvent`, `BagisBasvuruOnaylandiDomainEvent`,
    `BagisBasvuruReddedildiDomainEvent`, `BagisBasvuruIptalEdildiDomainEvent`,
    `BagisBasvuruOdemesiTamamlandiDomainEvent`.
  </Accordion>

  <Accordion title="EtkinlikBasvuru ve SupportTicket olayları">
    `EtkinlikBasvuruOlusturulduDomainEvent`, `EtkinlikBasvuruOnaylandiDomainEvent`,
    `EtkinlikBasvuruPlanKontenjanDolduDomainEvent`, ... ;
    `SupportTicketCreatedDomainEvent`, `SupportTicketAssignedDomainEvent`,
    `SupportTicketStatusChangedDomainEvent`, `SupportTicketCommentAddedDomainEvent`.
  </Accordion>
</AccordionGroup>

<Info>
  İsimlendirme tutarlıdır: domain dilinde Türkçe kavramlar (Bağış, Etkinlik başvurusu) Türkçe event isimleri taşır — `...OlusturulduDomainEvent`, `...OnaylandiDomainEvent`, `...ReddedildiDomainEvent`. Teknik/altyapı olayları (User, Faq) İngilizce kalır.
</Info>

## Handler örnekleri

Bir domain event'i **birden fazla** handler dinleyebilir; her biri tek bir yan etkiden sorumludur.

### SMS / E-posta gönderimi

```csharp theme={null}
public sealed class SendOtpSmsDomainEventHandler
    : INotificationHandler<UserOtpGeneratedDomainEvent>,
      INotificationHandler<UserOtpResentDomainEvent>
{
    private readonly IOtpSmsService _otpSmsService;

    public Task Handle(UserOtpGeneratedDomainEvent e, CancellationToken ct)
        => SendIfSmsAsync(e.Phone, e.Code, e.Type);

    private async Task SendIfSmsAsync(Phone? phone, OtpCode code, OtpType type)
    {
        if (phone is null || type != OtpType.Sms) return; // guard
        await _otpSmsService.SendAsync(code, phone);
    }
}
```

Aynı olayın e-posta tarafını `SendOtpEmailDomainEventHandler` üstlenir (`type == OtpType.Email` ise).

### Cache invalidation

```csharp theme={null}
public sealed class InvalidateFaqCacheDomainEventHandler : INotificationHandler<FaqCreatedDomainEvent>
{
    private readonly IHybridRequestCache _cache;

    public async Task Handle(FaqCreatedDomainEvent e, CancellationToken ct)
    {
        try { await _cache.RemoveByTagAsync(CacheTags.Faqs, ct); }
        catch (Exception ex) { /* warn log — cache hatası ana akışı durdurmaz */ }
    }
}
```

### Domain event → integration event köprüsü

Bazı handler'lar olayı **integration event**'e çevirip mesaj kuyruğuna basar (sistemler arası iletişim):

```csharp theme={null}
public class UserCreatedDomainEventHandler : INotificationHandler<UserCreatedDomainEvent>
{
    private readonly IEventBus _eventBus;

    public async Task Handle(UserCreatedDomainEvent n, CancellationToken ct)
    {
        var integrationEvent = new UserCreatedIntegrationEvent(
            n.UserId, n.FullName?.Value, n.Phone?.Value ?? "", n.Email?.Value ?? "");

        await _eventBus.PublishAsync(integrationEvent); // → RabbitMQ (MassTransit Outbox)
    }
}
```

## Domain event vs integration event

|              | Domain Event                   | Integration Event                                |
| ------------ | ------------------------------ | ------------------------------------------------ |
| **Kapsam**   | Aynı süreç içi (in-process)    | Süreç/servis dışı                                |
| **Taşıyıcı** | MediatR `INotification`        | `IEventBus` → MassTransit + RabbitMQ Outbox      |
| **Ne zaman** | `SaveEntitiesAsync` sonrası    | Bir domain event handler'ı tarafından yayınlanır |
| **Payload**  | Zengin domain tipleri (VO'lar) | Primitive/serileştirilebilir alanlar             |
| **Örnek**    | `UserCreatedDomainEvent`       | `UserCreatedIntegrationEvent`                    |

<Warning>
  Domain event'ler **asla** doğrudan kuyruğa yayınlanmaz; süreç içi kalır ve domain tiplerini (VO) taşıyabilir. Sistem dışına bir şey duyurmak gerektiğinde, bir handler içinde **ayrı bir integration event** üretip `IEventBus` ile yayınlayın. Bu ayrım, domain'i mesajlaşma altyapısından bağımsız tutar.
</Warning>

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="Aggregate'ler" icon="cubes" href="/domain/aggregates">
    `AddDomainEvent`'in davranış metotlarında kullanımı.
  </Card>

  <Card title="Event-Driven Akış" icon="diagram-project" href="/events/domain-events">
    Dispatch, MassTransit Outbox ve RabbitMQ tüketici zinciri.
  </Card>

  <Card title="Veri Katmanı" icon="database" href="/data/dbcontext">
    `SaveEntitiesAsync` ve `DispatchDomainEventsAsync` detayları.
  </Card>

  <Card title="Yeni domain event ekleme" icon="plus" href="/playbooks/new-domain-event">
    Adım adım playbook.
  </Card>
</CardGroup>
