İçeriğe geç

Domain Events & Integration Events

Bir sonraki Facebook’u kurmakla görevlendirildiniz ( şanslısınız! ). 🙂

Domain-driven design  ve Event-driven architecture duydunuz, Ancak bunlarla ilgili deneyiminiz yok. Ayrıca, projenin mahvolmaya mahkum olduğunu da en başından biliyorsunuz.

İlk gereksinimler bunları gerektirir:

  • Uygulama, çok kullanıcılı bir platformdur.
  • Kullanıcılar, diğer kullanıcılara arkadaşlık istekleri göndererek ilişkiler kurabilirler.
  • Bir arkadaşlık isteği gönderildiğinde, isteğin yönlendirildiği kullanıcıya gerçek zamanlı bir bildirim göndermemiz gerekir.
  • Gönderilen her arkadaşlık isteğinin geçmiş kaydını tutmamız gerekiyor.
  • Ayrıca tüm uygulamanın Cuma gününe kadar tamamlanmasına ihtiyacımız var. 🙂

Aggregate, ValueObject, IDomainEvent, … gibi her zaman ki temel sınıflarınızı ve arayüzlerinizi yaratırsınız ve ICommand iki kümeye sahip olduğumuz gereksinimleri temel alarak tanımlarsınız.

Kullanıcıların diğer kullanıcılara arkadaşlık istekleri göndererek ilişkiler kurdukları söyleniyor, Bu yüzden User sınıfın da Befriend adlı bir method oluşturuyoruz. Bu method bir FriendId nesnesini kabul eder ve FriendRequestSentDomainEvent adında bir Domain Event oluşturur.

public abstract class Aggregate
{
    private readonly List<IDomainEvent> domainEvents;
    public IReadOnlyCollection<IDomainEvent> DomainEvents 
      => domainEvents.AsReadOnly();

    protected void AddDomainEvent(IDomainEvent domainEvent)
    {
        domainEvents.Add(domainEvent);
    }
}

public class User : Aggregate
{
    public UserId Id { get; }
    public string Name { get; }

    private readonly List<FriendId> friendIds;
    public IReadOnlyCollection<FriendId> FriendIds => friendIds.AsReadOnly();

    public User(UserId id, string name)
    {
        Id = id;
        Name = name;
    }

    public void Befriend(FriendId friendId)
    {
        friendIds.Add(friendId);
        this.AddDomainEvent(new FriendRequestSentDomainEvent(
            Id, Name, friendId));
    }
}

public interface IDomainEvent
{

}

public class FriendRequestSentDomainEvent : IDomainEvent
{
    public UserId From { get; }
    public string Name { get; }
    public FriendId To { get; }

    public FriendRequestSentDomainEvent(
        UserId @from, string name, FriendId to)
    {
        From = @from;
        Name = name;
        To = to;
    }
}

ve bir command class ı oluşturarak arkadaşlık isteği göndermesini sağlayalım.

public class SendFriendRequestCommand : ICommand
{
    public Guid SenderId { get; set; }
    public Guid ReceiverId { get; set; }

    public class SendFriendRequestCommandHandler : 
        ICommandHandler<SendFriendRequestCommand>
    {
        private readonly IUserRepo userRepo;

        public SendFriendRequestCommandHandler(IUserRepo userRepo)
        {
            this.userRepo = userRepo;
        }

        public void Handle(SendFriendRequestCommand command)
        {
            User sender = userRepo.Get(command.SenderId);
            FriendId receiverId = new FriendId(command.ReceiverId);

            sender.Befriend(receiverId);
            userRepo.Save(sender);
        }
    }
}

IUserRepo.Save, mevcut tüm domain eventler’i kontrol eder.

public class UserRepo : IUserRepo
{
    private readonly IDbStore dbStore;
    private readonly IDomainEventDispatcher dispatcher;

    public UserRepo(IDbStore dbStore, IDomainEventDispatcher dispatcher)
    {
        this.dbStore = dbStore;
        this.dispatcher = dispatcher;
    }

    public void Save(User user)
    {
        foreach (IDomainEvent domainEvent in user.DomainEvents)
        {
            dispatcher.Dispatch(domainEvent);
        }
        
        dbStore.Commit();
    }
}

Ekleme işlemi yapılmadan hemen önce FriendshipHistoryRecord ile arkadaşlık işlemini kaydedelim. Tabi ki Bu işlem sırasında herhangi bir şey başarısız olursa, her şey geri alınacaktır!

public interface IDomainEventHandler<T> where T : IDomainEvent
{
    void Handle(T @event);
}

public class FriendRequestSentDomainEventHandler : 
    IDomainEventHandler<FriendRequestSentDomainEvent>
{
    private readonly IFriendshipHistoryRepo friendshipHistoryRepo;

    public FriendRequestSentDomainEventHandler(
        IFriendshipHistoryRepo friendshipHistoryRepo)
    {
        this.friendshipHistoryRepo = friendshipHistoryRepo;
    }

    public void Handle(FriendRequestSentDomainEvent @event)
    {
        FriendshipHistoryRecord record = 
            new FriendshipHistory(@event.From.Value, @event.To.Value);

        friendshipHistoryRepo.Add(record);
    }
}

Bu arada, Alıcıya arkadaşlık isteğini gerçek zamanlı olarak bildirmemiz gerekiyor. Sorumlulukları ayırmaya ve gerçek zamanlı bildirimler göndermekle ilgilenen özel bir “Notifications” hizmeti oluşturmaya karar veriyoruz. Bu hizmet farklı bir süreçte yaşıyor ve gerekli bilgileri bir olay veri yolu aracılığıyla integration event olarak gönderiyoruz.

Şimdi yakıcı soru… Nasıl? 🙂

Uygulamanın mevcut durumunda, SendFriendRequestCommandHandler bunu bizim için bir entegrasyon olayı ile bildirebilir.

Not: Gerçek dünya uygulamalarında, En az bir kez istek göndermeyi kullanırız. Bu yüzden kalıplar, makalenin kapsamı dışındadır. Ancak Oskar’ın bunlarla Outbox, Inbox pattern’i ile ilgili makalesini şiddetle tavsiye ederim

“Notifications” hizmeti bu olayı alır ve web socket teknolojisi ile gerçek zamanlı bir bildirim gönderir. (SignalR da kullana bilirdik). 🙂

public interface IIntegrationEvent
{
    Guid Id { get; }
    DateTime OccurredOn { get; }
}

public class FriendRequestSentIntegrationEvent : IIntegrationEvent
{
    public Guid Id { get; }
    public DateTime OccurredOn { get; }

    public Guid SenderId { get; }
    public string SenderName { get; }
    public Guid ReceiverId { get; }

    public FriendRequestSentIntegrationEvent(
        Guid senderId, string senderName, Guid receiverId)
    {
        Id = Guid.NewGuid();
        OccurredOn = DateTime.UtcNow;

        SenderId = senderId;
        SenderName = senderName;
        ReceiverId = receiverId;
    }
}

public class SendFriendRequestCommandHandler :
    ICommandHandler<SendFriendRequestCommand>
{
    private readonly IEventBus eventBus;
    private readonly IUserRepo userRepo;

    public SendFriendRequestCommandHandler(
        IEventBus eventBus,
        IUserRepo userRepo)
    {
        this.userRepo = userRepo;
    }

    public void Handle(SendFriendRequestCommand command)
    {
        User sender = userRepo.Get(command.SenderId);
        FriendId receiverId = new FriendId(command.ReceiverId);

        sender.Befriend(receiverId);
        userRepo.Save(sender);

        IIntegrationEvent @event = new FriendRequestSentIntegrationEvent(
            senderId: command.SenderId,
            senderName: sender.Name,
            receiverId: command.ReceiverId
        );
        
        eventBus.Publish(@event);  
    }
}

Hadi biraz daha zorlaştıralım! 🙂

Böylece iş ortaya çıkar ve kullanıcıların arkadaş listelerinde aynı kişiyi birden çok kez gördüklerinden şikayet eder. Aşağıdaki gereksinimlerin uygulanmasını isterler:

  • B kullanıcısı zaten A kullanıcısının arkadaş listesindeyse , A kullanıcısı B kullanıcısı ile arkadaş olamaz .
  • Herhangi bir kullanıcı 5000 arkadaşa kadar sınırlıdır.

Gelecek Makalede 🙂

 31,628 Görüntüleme

Kategori:.Net CoreC#Software Architecture

2 Yorum

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir