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

> DomainEvent base, aggregate'ta AddDomainEvent, MediatorExtension dispatch ve INotificationHandler konvansiyonu

## `DomainEvent` base

Domain event'ler `Domain.SharedKernel.SeedWork.DomainEvent`'ten türer. Her event Guid v7 (time-ordered) `Id` ve UTC `CreatedAt` taşır:

```csharp theme={null}
public abstract class DomainEvent
{
    public string Type => this.GetType().Name;
    public readonly Guid Id;
    public readonly Guid CorrelationID;
    public readonly DateTime CreatedAt;

    public DomainEvent()
    {
        Id = Guid.CreateVersion7(DateTime.UtcNow);
        CreatedAt = DateTime.UtcNow;
    }
    public DomainEvent(Guid correlationID) : this() => this.CorrelationID = correlationID;
}
```

Somut event'ler `INotification`'dır (MediatR ile dispatch için) ve değişmez veri taşıyan POCO'lardır — örn. `UserOtpGeneratedDomainEvent`, `FaqCreatedDomainEvent`, `UserRoleAssignedDomainEvent`.

## Aggregate'ta event yayını

Event aggregate içinden `AddDomainEvent(...)` ile biriktirilir; dış dünyaya değil, change-tracker'a yazılır. `SaveEntitiesAsync` sonrası dispatch edilir:

```csharp theme={null}
// Aggregate metodu (örnek)
public void AssignRole(Role role, Guid by)
{
    // ... domain kuralları, state değişimi ...
    AddDomainEvent(new UserRoleAssignedDomainEvent(Id, role.Id));
}
```

## Dispatch — `MediatorExtension`

`SaveEntitiesAsync`, `base.SaveChangesAsync`'ten sonra (`result > 0` ise) `DispatchDomainEventsAsync`'i çağırır. Yani event'ler **DB commit'ten sonra** in-memory yayınlanır:

```csharp theme={null}
// DiyanetCleanArchitectureDbContext.SaveEntitiesAsync
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
    int result = await base.SaveChangesAsync(cancellationToken);   // AuditInterceptor burada
    if (result > 0)
        await _mediator.DispatchDomainEventsAsync(this);
    return true;
}
```

`DispatchDomainEventsAsync` change-tracker'daki tüm `Entity`'lerin event'lerini toplar, `ClearDomainEvents()` ile temizler ve tek tek dispatch eder. `IBusEvent` (yani integration event) olanları `IEventBus`'a, kalanları `mediator.Publish` ile in-process handler'lara yönlendirir (bkz. [Event Akışı](/events/overview)). Hata olursa log'lanır ama akış kesilmez.

## `INotificationHandler` örnekleri

Domain event'i tüketen handler `INotificationHandler<TEvent>`'i implement eder. **Bir event'in birden çok handler'ı olabilir** — MediatR hepsini çağırır.

### Yan etki: SMS / e-posta

`UserOtpGeneratedDomainEvent` (ve `UserOtpResentDomainEvent`) hem SMS hem e-posta handler'ı tarafından dinlenir; her biri OTP tipine göre kendi kanalını seçer:

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

    public Task Handle(UserOtpGeneratedDomainEvent @event, CancellationToken ct)
        => SendIfSmsAsync(@event.Phone, @event.Code, @event.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);
    }
}
```

`SendOtpEmailDomainEventHandler` aynı event'i dinler ama `type != OtpType.Email` ise atlar — iki handler, tek event, ayrılan sorumluluk.

<Note>
  Bu OTP event'leri bilinçle **in-process** tutulur (cross-service notification ihtiyacı doğarsa ayrı bir integration event eklenir). Karar handler doc'larında açıkça not edilmiştir.
</Note>

### Domain event'ten integration event yayma

Bazı domain event handler'ları, in-process işin yanında bir **integration event** yayınlar. `UserCreatedDomainEventHandler`:

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

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

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

### Cache invalidation

Domain event handler cache tag'i düşürmek için de kullanılır — `InvalidateFaqCacheDomainEventHandler` `IHybridRequestCache.RemoveByTagAsync(CacheTags.Faqs)` çağırır. Ayrıntı: [Cache Invalidation](/caching/invalidation).

## İlgili

<CardGroup cols={2}>
  <Card title="Integration Events" icon="arrows-turn-to-dots" href="/events/integration-events">
    Cross-service event'ler ve consumer kaydı.
  </Card>

  <Card title="Event Akışı" icon="diagram-project" href="/events/overview">
    Domain vs integration, tam dispatch akışı.
  </Card>

  <Card title="Cache Invalidation" icon="trash" href="/caching/invalidation">
    Event handler'da tag düşürme.
  </Card>

  <Card title="OTP" icon="key" href="/building-blocks/otp">
    OTP üretimi ve gönderim akışı.
  </Card>
</CardGroup>
