簡單寫一個eventbus

敖毛毛發表於2024-04-15

前言

閒暇之餘,簡單寫一個eventbus。

正文

什麼是eventbus?

eventbus 是一個開源的釋出訂閱模式的框架,用於簡化程式間不同元件的通訊。
它允許不同元件間松耦合通訊,元件之間不透過直接引用的方式,而是事件的方式進行訊息傳遞。

下面進行程式碼演示:

首先是釋出訂閱,那麼就應該有釋出方法和訂閱方法,因為是訊息傳遞,那麼就應該還有啟動消費訊息的方法。

public interface IEventBus : IDisposable
{
    Task Publish<T>(T @event) where T : IntegrationEvent;

    Task Subscribe<T>(IIntegrationEventHandler<T> handler)
        where T : IntegrationEvent;

    Task StartConsume();
}

大體我們要實現上面的功能。

然後我們可以定義事件的基礎資訊:

public class IntegrationEvent
{
    public Guid Id { get; set; }

    public DateTime OccurredOn { get; set; }

    public IntegrationEvent()
    {
        Id = Guid.NewGuid();
        OccurredOn = DateTime.Now;
    }
}

比如說要有唯一的id,同時要有事件發生的時間。

訂閱的話,那麼需要指定處理的物件。

public interface IIntegrationEventHandler
{
}

public interface IIntegrationEventHandler<in TIntegrationEvent> :
    IIntegrationEventHandler where TIntegrationEvent : IntegrationEvent
{
    Task Handler(TIntegrationEvent @event);
}

處理物件設計也很簡單,就是需要建立一個有能夠處理IntegrationEvent的物件即可。

這裡很多人會疑惑,為什麼很多框架的泛型介面類,往往會建立一個非泛型的介面。

這個其實是為了進一步抽象,方便做集合處理,下面將會介紹到。

然後就可以寫一個記憶體型的eventbus。

public class InMemoryEventBus : IDisposable
{
    private Dictionary<string, List<IIntegrationEventHandler>>
        _dictionary = new Dictionary<string, List<IIntegrationEventHandler>>();

    public async Task Publish<T>(T @event) where T : IntegrationEvent
    {
        var fullName = @event.GetType().FullName;
        if (fullName == null)
        {
            return;
        }

        var handlers = _dictionary[fullName];

        foreach (var integrationEventHandler in handlers)
        {
            if (integrationEventHandler is IIntegrationEventHandler<T> handler)
            {
                await handler.Handler(@event);
            }
        }
    }

    public async Task Subscribe<T>(IIntegrationEventHandler<T> handler)
        where T : IntegrationEvent
    {
        var fullname = typeof(T).FullName;
        if (fullname == null)
        {
            return;
        }

        if (_dictionary.ContainsKey(fullname))
        {
            var handlers = _dictionary[fullname];
            handlers.Add(handler);
        }
        else
        {
            _dictionary.Add(fullname, new List<IIntegrationEventHandler>()
            {
                handler
            });
        }
    }

    public void Dispose()
    {
        // 移除相關連線等
    }
}

裡面實現了eventbus的基本功能。可以看到上面的_dictionary,裡面就是IIntegrationEventHandler,
所以泛型介面會繼承一個非泛型的介面,是為了進一步抽象宣告,對一些集合處理是很方便的。

然後這裡為什麼沒有直接繼承Ieventbus呢? 而是實現eventbus的功能。

因為Ieventbus 其實是面向使用者的,繼承ieventbus只是一個門面,相當於介面卡。

而InMemoryEventBus 是為了實現功能。

可以理解為InMemoryEventBus 是我們電腦主機板、cpu等,然後我們只需要一個實現其介面的元件,從而和外部連線。

而不是整個核心系統和外部直連,那麼我們可以使用InMemoryEventBusClient 作為這個元件。

public class InMemoryEventBusClient : IEventBus
{
    private readonly InMemoryEventBus _eventBus;
    
    public InMemoryEventBusClient()
    {
        _eventBus = new InMemoryEventBus();
    }

    public void Dispose()
    {
        _eventBus.Dispose();
    }

    public async Task Publish<T>(T @event) where T : IntegrationEvent
    {
        await _eventBus.Publish(@event);
    }

    public async Task Subscribe<T>(IIntegrationEventHandler<T> handler) where T : IntegrationEvent
    {
        await _eventBus.Subscribe(@handler);
    }

    public Task StartConsume()
    {
        // 執行相關的消費
        return Task.CompletedTask;
    }
}

InMemoryEventBusClient 負責實現外部介面,InMemoryEventBus 負責實現功能。

從而達到解耦的目的。

同樣的例子還有polly,這個框架應該很出名了,其中他裡面就有很多衍生的元件,都是呼叫核心來適配其他框架定義的介面。

上面可以看到StartConsume什麼都沒有做,其功能被Publish給融合了。

只要publish就消費了。

如果我們擴充套件kafka的話,那麼consume其實就是拉取資料然後消費,publish其實就是推向kafka,中間就是序列號和反序列話的過程。

eventbus 完善篇後續再補。

相關文章