用資料結構解釋事件溯源 – {4Comprehension}
在本系列中,我們將透過實現假設資料結構的PoC(基於事件的列表),重新審視事件源的概念,然後在後續文章中透過使其併發且對記憶體友好的方式進一步改進事件源的概念。
事件溯源
多年來,我們已經習慣了這樣一個事實,即大多數業務應用程式將狀態儲存在某些外部儲存中,這在確保可審計性或重建過去的狀態時通常會產生額外的工作。但是,如果我們放棄了需要儲存狀態的假設前提,該怎麼辦?
事件溯源是一個概念,在該概念中,我們儲存狀態更改事件而不是狀態,並僅在需要時匯出實際狀態。在可稽核性和獲取新資訊方面,這提供了很多可能性!
例如,想象一下檢索使用者的帳戶餘額。使用事件源,您不僅可以訪問原始運算元,還可以訪問整個操作歷史記錄。您可以跟蹤所有過去的事件,過去的狀態,這些事件導致導致表示當前狀態的特定值。奇妙!
知道這一點後,讓我們看看是否可以將此想法應用到通用列表中。
將事件源應用於列表
讓我們開始為實現定義:
public class ESList<T> implements List<T> { ... } |
就像上面建立的一樣,為了實現事件源資料結構,我們需要儲存狀態改變事件/操作而不是狀態本身,然後針對當場重新建立的狀態執行方法。
為了實現這一目標,我們需要:
- 為我們的事件定義合同
- 為我們的歷史事件定義一個儲存容器
- 實現事件處理邏輯
- 實現事件重播邏輯
內部事件日誌是我們實施的核心。這是應用於資料結構的所有修改的歷史,可以表示為簡單列表:
private final List<ListOp<T>> opLog = new ArrayList<>(); |
一個操作實際上只是一個函式,它接受一些列表並返回該操作的結果:
interface ListOp<R> { Object apply(List<R> list); } |
因此,代表加法的操作可以實現為:
class AddOp<T> implements ListOp<T> { private final T elem; AddOp(T elem) { this.elem = elem; } @Override public Object apply(List<T> list) { return list.add(elem); } @Override public String toString() { return String.format("add(element = %s)", elem); } } |
現在,無論何時實現任何狀態更改方法,我們都只需要建立該操作的表示形式,將其儲存並應用於重新建立的狀態,以便我們可以返回該操作的結果:
@Override public boolean add(T t){ return(boolean)handle(new AddOp <>(t)); } |
現在,如果我們想在任何時間點重新建立列表的狀態,則需要從時間開始重新執行所有操作。
最好返回包裝在Optional例項中的結果:
public Optional<List<T>> snapshot(int version) { if (version > opLog.size()) { return Optional.empty(); } var snapshot = new ArrayList<T>(); for (int i = 0; i <= version; i++) { try { opLog.get(i).apply(snapshot); } catch (Exception ignored) { } } return Optional.of(snapshot); } public List<T> snapshot() { return snapshot(opLog.size()) .orElseThrow(IllegalStateException::new); } |
空的catch塊可能看起來有爭議,但是對於跟蹤故障至關重要。稍後我們將新增一些日誌記錄。
現在我們可以完成事件處理邏輯。注意,在將操作新增到日誌之前,我們需要首先重新建立當前狀態:
private Object handle(ListOp<T> op) { List<T> snapshot = snapshot(); opLog.add(op); return op.apply(snapshot); } |
現在,所有List介面的查詢方法都需要首先重新建立狀態:
@Override public int indexOf(Object o) { return snapshot().indexOf(o); } @Override public int lastIndexOf(Object o) { return snapshot().lastIndexOf(o); } @Override public ListIterator<T> listIterator() { return snapshot().listIterator(); } // ... |
擁有一種通知我們資料結構存在多少版本的方法也將很方便:
public int version() { return opLog.size(); } |
另外,為了方便我們觀察更改,讓我們新增一種額外的方法來顯示所有操作的歷史記錄:
public void displayLog() { for (int i = 0; i < opLog.size(); i++) { System.out.printf("v%d :: %s%n", i, opLog.get(i).toString()); } } |
現在,讓我們透過執行一些修改並最終清除列表來了解它的作用:
public static void main(String[] args) { ESList<Integer> objects = ESList.newInstance(); objects.add(1); objects.add(2); objects.add(3); objects.addAll(List.of(4, 5)); objects.remove(Integer.valueOf(1)); objects.clear(); objects.displayLog(); System.out.println(); for (int i = 0; i < objects.version(); i++) { System.out.println("v" + i + " :" + objects.snapshot(i).get()); } } |
儘管透過清除列表回到了第一位,我們可以看到整個歷史記錄得以保留,並且我們設法重新建立了資料結構的所有現有版本:
v0 :: init[] v1 :: add(element = 1) v2 :: add(element = 2) v3 :: add(element = 3) v4 :: addAll([4, 5]) v5 :: remove(1) v6 :: clear() v0 :[] v1 :[1] v2 :[1, 2] v3 :[1, 2, 3] v4 :[1, 2, 3, 4, 5] v5 :[2, 3, 4, 5] v6 :[] |
我們到了!這是事件源的基本思想。
自然,此實現有多個缺點,而且肯定還沒有準備好投入生產。它不僅不是執行緒安全的,而且內部事件日誌是記憶體洩漏的根源。
甚至沒有提到這樣一個事實,即在每次操作之前重播事件效率低下,但可以很好地為我們提供教育材料。
可以在GitHub上找到程式碼段
相關文章
- MySQL的事件溯源Event Sourcing表結構MySql事件
- .NET的事件溯源構建庫:Eventuous事件
- 事件溯源在物聯網裝置資料同步中應用案例 - eventstore事件
- 事件協作和事件溯源事件
- 事件流與事件溯源事件
- PHP 事件溯源PHP事件
- 無伺服器與事件溯源結合的演示案例:將事件溯源作為Azure函式的資料持久化機制的庫伺服器事件函式持久化
- 六種機率資料結構的詳細解釋及應用場景資料結構
- 事件溯源與流水賬的結賬模式事件模式
- Oracle事件跟蹤及結構資料dumpOracle事件
- 事件溯源將顛覆關聯式資料庫! - Remy事件資料庫REM
- 事件溯源超越關聯式資料庫 - confluent事件資料庫
- jvm結構解釋JVM
- 剖玄析微聚合 - 事件溯源事件
- 資料結構:棧詳解資料結構
- 事件溯源投影模式:重複資料刪除策略 - domaincentric事件模式AI
- leetcode演算法資料結構題解---資料結構LeetCode演算法資料結構
- 【資料結構】——堆及其應用資料結構
- 用Python解決資料結構與演算法問題(三):線性資料結構之棧Python資料結構演算法
- 事件溯源全指南 - Arkwrite事件
- 事件溯源不是什麼?事件
- 高階資料結構詳解資料結構
- 圖解Java常用資料結構圖解Java資料結構
- 深入瞭解Redis資料結構Redis資料結構
- Redis資料結構詳解(一)Redis資料結構
- 事件消費者之 Projector - 事件溯源事件Project
- 事件消費者之 Reactor - 事件溯源事件React
- 資料結構學習(C++)——佇列應用(事件驅動模擬) (轉)資料結構C++佇列事件
- 結構化資料、半結構化資料和非結構化資料
- 用python講解資料結構之樹的遍歷Python資料結構
- 【資料結構篇】認識資料結構資料結構
- 物理結構和邏輯結構更通俗解釋
- 5分鐘瞭解資料結構資料結構
- 事件消費者之 Saga - 事件溯源事件
- 使用Kafka實現事件溯源Kafka事件
- Rust中的事件溯源 - ariseyhunRust事件
- 基於事件溯源與CDC的事件驅動微服務架構案例原始碼事件微服務架構原始碼
- 如何在Java後端中實現事件驅動架構:從事件匯流排到事件溯源Java後端事件架構