實現本地一對多的事件處理- EventSourcing

Shawn Hsiao
Akatsuki Taiwan Technology
5 min readAug 2, 2019

在上個章節我們聊了DomainEvent Pattern, 專用來聆聽與廣播重要的業務邏輯事件。由於事件幾乎是Global通知的,有以下需求時使用上就略顯不足:

  • 只想聆聽A類別的指定實例(Instance)發生的某事件B,而不是任一實例的B事件
  • 想把某些事件限於固定領域的控制器才能存取,而非人人皆可註冊

這時候EventSourcing就能達成這個目的

我們直接來看使用面

今天有一群敵人,想知道當某隻敵人受傷時,更新他的血量顯示。

敵人受傷時資料會把HP扣除並發出一個EnemyHPChanged的事件。

由於不確定是哪隻敵人受傷,今天假如是使用DomainEvent的話,就必須在事件內容(DTO)加上敵人的ID,送給掌管敵人的管理者,管理者也不確定是哪隻敵人受傷,於是就比對全部的敵人,ID符合的就更新HP。

這樣既不好管理效能也差。

使用EventSourcing的話,可以令EnemyModel繼承 EventSourcing<IEnemyEvent>類別,從此每隻敵人皆可以獨立註冊事件。

protected EventSourcing<IEnemyEvent> m_eventSource = enemyA;m_eventSource.Register<EnemyHPChanged>(HandleEnemyHPChanged);private void HandleEnemyHPChanged(EnemyHPChanged obj)
{
SetHP(obj.CurrentHP, m_model.MaxHp);
}

而一群敵人也就只要全部跑一次註冊就行了,不會有互相干擾的問題,每隻敵人的事件都是獨立的,但code只要寫一次就行了。

那EventSource具體是怎麼實現的呢,其實跟DomainEvent挺像

public interface IEvent
{
}

public interface IEventRaiser<T> where T : IEvent
{
void Raise<P>(P e) where P : T;
}


public abstract class EventSourcing<T> : IEventRaiser<T> where T : IEvent
{
private Dictionary<Type, IList> m_actionsByType;

public void Register<P>(Action<P> callback) where P : T
{
var eventType = typeof(P);
if (m_actionsByType == null)
{
m_actionsByType = new Dictionary<Type, IList>();
}

IList list;
if (!m_actionsByType.TryGetValue(eventType, out list))
{
list = new List<Action<P>>();
list.Add(callback);
m_actionsByType.Add(eventType, list);
}
else if (!list.Contains(callback))
{
list.Add(callback);
}
}

public void Unregister<P>(Action<P> callback) where P : T
{
if (m_actionsByType == null)
{
return;
}

IList list;
if (!m_actionsByType.TryGetValue(typeof(P), out list))
{
return;
}

list.Remove(callback);
}

public void Raise<P>(P e) where P : T
{
var eventType = typeof(P);

if (m_actionsByType == null)
{
return;
}

IList list;
if (!m_actionsByType.TryGetValue(eventType, out list))
{
return;
}

foreach (var del in ((List<Action<P>>)list).ToArray())
{
del(e);
}
}

public void ClearEvent()
{
m_actionsByType?.Clear();
}
}

最大的差別在於DomainEvent是讓所有事件放在一個靜態的容器裡,任何人都可以註冊監聽。

而EventSourcing的容器只限繼承的物件使用,而由於大多數時候使用這種方法皆是侷限在本地模組裡,透過泛型我們可以指定只能在這物件使用哪種Event,讓非本地模組的事件切開,這樣搜尋方便,終端命名也可以根據需求而重複使用。

那麼使用上有哪些需要注意的地方呢

  • 事件使用完,或物件銷毀時,記得先解除註冊。
  • 盡量避免在Event中放function point型的成員變數
  • 盡量使用值類型做為Event(DTO)的變數
  • 相較於DomainEvent只建議使用在業務邏輯層,ES可以較為靈活的在控制層或表現層的ViewModel中使用。

好了,以上就是EventSouncing的介紹,對於事件驅動的程式設計又多一種方式囉,下回我們來講如何調優DomainEvent的架構避免記憶體洩漏。

--

--