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

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

Gerçek event listesi

DiyanetCleanArchitecture.Domain/Events/ klasöründeki olaylardan bir kesit:
UserCreatedDomainEvent, UserOtpGeneratedDomainEvent, UserOtpResentDomainEvent, UserPhoneNumberVerifiedDomainEvent, UserEmailVerifiedDomainEvent, UserRoleAssignedDomainEvent, UserRoleRevokedDomainEvent, UserTokensInvalidatedDomainEvent, UserTotpEnabledDomainEvent.
CitizenCreatedDomainEvent, CitizenOtpGeneratedDomainEvent, CitizenOtpResentDomainEvent, CitizenPhoneNumberVerifiedDomainEvent, CitizenEmailVerifiedDomainEvent, CitizenAccountDeactivatedDomainEvent, CitizenTokensInvalidatedDomainEvent, CitizenRefreshTokenRevokedDomainEvent.
FaqCreatedDomainEvent, FaqUpdatedDomainEvent, FaqDeletedDomainEvent, FaqReorderedDomainEvent.
BagisBasvuruOlusturulduDomainEvent, BagisBasvuruIncelemeyeAlindiDomainEvent, BagisBasvuruBelgeBeklendiDomainEvent, BagisBasvuruOnaylandiDomainEvent, BagisBasvuruReddedildiDomainEvent, BagisBasvuruIptalEdildiDomainEvent, BagisBasvuruOdemesiTamamlandiDomainEvent.
EtkinlikBasvuruOlusturulduDomainEvent, EtkinlikBasvuruOnaylandiDomainEvent, EtkinlikBasvuruPlanKontenjanDolduDomainEvent, … ; SupportTicketCreatedDomainEvent, SupportTicketAssignedDomainEvent, SupportTicketStatusChangedDomainEvent, SupportTicketCommentAddedDomainEvent.
İ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.

Handler örnekleri

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

SMS / E-posta gönderimi

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

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):
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 EventIntegration Event
KapsamAynı süreç içi (in-process)Süreç/servis dışı
TaşıyıcıMediatR INotificationIEventBus → MassTransit + RabbitMQ Outbox
Ne zamanSaveEntitiesAsync sonrasıBir domain event handler’ı tarafından yayınlanır
PayloadZengin domain tipleri (VO’lar)Primitive/serileştirilebilir alanlar
ÖrnekUserCreatedDomainEventUserCreatedIntegrationEvent
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.

Sonraki adımlar

Aggregate'ler

AddDomainEvent’in davranış metotlarında kullanımı.

Event-Driven Akış

Dispatch, MassTransit Outbox ve RabbitMQ tüketici zinciri.

Veri Katmanı

SaveEntitiesAsync ve DispatchDomainEventsAsync detayları.

Yeni domain event ekleme

Adım adım playbook.