Skip to main content

DomainEvent base

Domain event’ler Domain.SharedKernel.SeedWork.DomainEvent’ten türer. Her event Guid v7 (time-ordered) Id ve UTC CreatedAt taşır:
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:
// 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:
// 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ışı). 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:
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.
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.

Domain event’ten integration event yayma

Bazı domain event handler’ları, in-process işin yanında bir integration event yayınlar. UserCreatedDomainEventHandler:
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.

İlgili

Integration Events

Cross-service event’ler ve consumer kaydı.

Event Akışı

Domain vs integration, tam dispatch akışı.

Cache Invalidation

Event handler’da tag düşürme.

OTP

OTP üretimi ve gönderim akışı.