

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 事件來源模式
<a name="event-sourcing"></a>

## 意圖
<a name="event-sourcing-intent"></a>

在事件驅動架構中，事件來源模式會將導致狀態變更的事件儲存在資料存放區中。這有助於捕獲和維護狀態變化的完整歷史記錄，並提高可稽核性、可追溯性和分析過去狀態的能力。

## 動機
<a name="event-sourcing-motivation"></a>

多個微服務可以協同合作以處理請求，並透過事件進行通訊。這些事件可能會導致狀態 (資料) 發生變更。依照事件物件發生的順序儲存，可提供有關資料實體目前狀態的重要資訊，以及其到達該狀態的其他相關資訊。

## 適用性
<a name="event-sourcing-applicability"></a>

在下列情況下使用事件來源模式：
+ 追蹤需要應用程式中發生之事件的不可變歷程記錄。
+ 必須提供來自單一事實來源 (SSOT) 的多語言資料預測。
+ 需要對應用程式狀態進行指向時間重建。
+ 不需要長期存儲應用程式狀態，但您可能需要根據需要重建它。
+ 工作負載具有不同的讀寫磁碟區。例如，您的寫入密集型工作負載不需要即時處理。
+ 必須具備變更資料擷取 (CDC) 才能分析應用程式效能和其他指標。
+ 系統中發生的所有事件都需要稽核資料，以便進行報告和法規遵循。
+ 您想要在重新執行程序期間變更 (插入、更新或刪除) 事件來衍生假設案例，以判斷可能的結束狀態。

## 問題和考量
<a name="event-sourcing-issues"></a>
+ **樂觀並行控制：**此模式存儲導致系統中狀態更改的每個事件。多個使用者或服務可以嘗試同時更新相同的資料片段，造成事件衝突。這些衝突發生在同一時間建立並套用衝突的事件時，這會導致與實際不符的最終資料狀態。若要解決此問題，您可以實作偵測和解決事件衝突的策略。例如，您可以透過包含版本控制或將時間戳記新增至事件以追蹤更新順序，以實作樂觀的並行控制結構描述。
+ **複雜性：**實施事件採購需要將心態從傳統的 CRUD 操作轉變為事件驅動的思維。用來將系統還原至原始狀態的重新執行程序可能很複雜，以確保資料等冪性。事件儲存、備份和快照也會增加額外的複雜性。
+ **最終一致性**：由於使用命令查詢職責隔離 (CQRS) 模式或具體化視觀表更新資料時，資料預測最終會保持一致。當消費者處理來自事件存放區的資料，而發布者傳送新資料時，資料投影或應用程式物件可能不代表目前的狀態。
+ **查詢**：與傳統資料庫相比，從事件日誌擷取目前或彙總資料可能會更複雜且速度較慢，尤其是複雜的查詢和報告工作。為了緩解此問題，事件來源通常會使用 CQRS 模式來實作。
+ **事件存放區的大小和成本：**隨著事件持續持續存在，事件存放區可能會經歷指數級的大小成長，尤其是在具有高事件輸送量或延長保留期的系統中。因此，您必須定期將事件資料封存至符合成本效益的儲存體，以防止事件存放區變得過大。
+ **事件存放區的可擴展性：**事件存放區必須有效率地處理大量的寫入和讀取作業。擴展事件存放區可能具有挑戰性，因此擁有提供碎片和分區的資料存放區非常重要。
+ **效率和最佳化**：選擇或設計可有效處理寫入和讀取作業的事件存放區。事件存放區應針對應用程式的預期事件磁碟區和查詢模式進行最佳化。實施索引和查詢機制可以在重建應用程式狀態時加快事件的擷取速度。您也可以考慮使用專門的事件存放區資料庫或提供查詢最佳化功能的程式庫。
+ **快照：**您必須以時間為基礎的啟用定期備份事件日誌。在上次成功備份資料時重新執行事件，應該會導致應用程式狀態的時間點復原。復原點目標 (RPO) 是自上次資料復原點以來可接受的時間上限。RPO 會判斷最後一個復原點與服務中斷之間可接受的資料遺失。資料和事件存放區的每日快照頻率應以應用程式的 RPO 為基礎。
+ **時間敏感度：**事件會依照事件發生的順序儲存。因此，網路可靠性是實作此模式時要考慮的重要因素。延遲問題可能會導致系統狀態不正確。使用先進先出 (FIFO) 佇列 (最多一次傳遞)，將事件傳送至事件存放區。
+ **事件重新顯示效能**：重新執行大量事件以重建目前的應用程式狀態可能非常耗時。為了增強效能，尤其是從封存資料重新播放事件時，必須進行最佳化工作。
+ **外部系統更新：**使用事件來源模式的應用程式可能會更新外部系統中的資料存放區，而且可能會將這些更新擷取為事件物件。在活動重播期間，如果外部系統預期不會更新，這可能會成為問題。在這種情況下，您可以使用功能旗標來控制外部系統更新。
+ **外部系統查詢：**當外部系統調用對呼叫的日期和時間敏感時，接收的資料可以存儲在內部資料存儲中，以便在重播期間使用。
+ **事件版本控制：**隨著應用程式的發展，事件的結構 (結構描述) 可能會改變。實作事件的版本控制策略，以確保向後和向前相容性是必要的。這可能涉及在事件裝載中包含版本欄位，以及在重新執行期間適當地處理不同的事件版本。

## 實作
<a name="event-sourcing-implementation"></a>

### 高層級架構
<a name="event-sourcing-high-level-arch"></a>

**命令和事件**

在分散式、事件驅動的微服務應用程式中，命令代表傳送至服務的指示或要求，通常是在其狀態中啟動變更的目的。服務會處理這些命令，並評估指令對其目前狀態的有效性和適用性。如果命令執行成功，服務會透過發出表示採取的動作和相關狀態資訊的事件進行回應。例如，在下圖中，預訂服務會透過發出已預訂「乘車」事件來回應「預訂」乘車命令。

![事件來源模式中的命令和事件](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/event-sourcing-1.png)


**事件存放區**

事件會記錄到不可變、僅附加、按時間順序排序的儲存庫或稱為*事件存放區*的資料存放區中。每個狀態變更都會視為個別事件物件。具有已知初始狀態、其目前狀態以及任何時間點檢視的實體物件或資料存放區，可以按照事件發生的順序重新建構。

事件存儲作為所有操作和狀態變化的歷史記錄，並作為有價值的單一事實來源。您可以透過重新執行處理器傳遞事件，使用事件存放區來衍生系統的最終最新狀態，重新執行處理器會套用這些事件，以產生最新系統狀態的精確表示。您也可以透過重新執行處理器重新播放事件，使用事件存放區來產生狀態的時間點透視。在事件來源模式中，目前狀態可能不會完全由最新的事件物件來表示。有三種方式可以得出：
+ 透過彙總相關事件的方式。相關的事件物件會結合在一起，以產生用於查詢的目前狀態。這種方法通常與 CQRS 模式結合使用，因為事件會合併並寫入唯讀資料存放區。
+ 透過使用具體化視觀表的方式。您可以使用具體化視觀表模式的事件來源來計算或彙總事件資料，並取得相關資料的目前狀態。
+ 透過重播事件的方式。事件物件可重新顯示，以執行產生目前狀態的動作。

下圖顯示要儲存在 `Ride booked` 事件存放區中的事件。

![在事件來源模式中使用事件商店](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/event-sourcing-2.png)


事件存放區會發布其儲存的事件，並可篩選並將事件路由至適當的處理器，以供後續動作使用。例如，可將事件路由至總結狀態並顯示具體化視觀表的視觀表處理器。事件會轉換為目標資料倉儲的資料格式。這種架構可加以延伸以導出不同類型的資料存放區，導致資料的多語言持續性。

下列圖表說明乘車預訂應用程式中的事件。應用程式內發生的所有事件都會儲存在事件存放區中。然後會篩選儲存的事件，並將其路由至不同的消費者。

![事件來源模式的高階實作範例](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/event-sourcing-3.png)


乘車事件可用於透過使用 CQRS 或具體化視觀表模式產生唯讀資料存放區。您可以透過查詢讀取商店來取得乘車、駕駛或預訂的目前狀態。某些事件 (例如 `Location changed` 或 `Ride completed`) 會發布給其他消費者進行付款處理。乘車完成後，系統會重新顯示所有乘車事件以建立乘車歷史記錄，以供稽核或報告之用。

事件來源模式通常用於需要時間點復原的應用程式中，以及必須使用單一事實來源以不同格式預測資料時。這兩項作業都需要重新顯示處理作業，才能執行事件並衍生必要的結束狀態。重新顯示處理器也可能需要一個已知的起點，最好不是從應用程式啟動開始，因為這不是一個有效率的程序。我們建議您定期建立系統狀態的快照，並套用較少數量的事件來衍生最新狀態。

### 使用 AWS 服務來實作
<a name="event-sourcing-aws-services"></a>

在下列架構中，Amazon Kinesis Data Streams 會用作事件存放區。此服務將應用程式變更擷取並管理為事件，並提供高輸送量和即時資料串流解決方案。若要在 AWS 上實作事件採購模式，您也可以根據應用程式的需求，使用 Amazon EventBridge 和 Amazon Managed Streaming for Apache Kafka (Amazon MSK) 等服務。

若要增強耐久性並啟用稽核，您可以將 Kinesis Data Streams 擷取的事件封存在 Amazon Simple Storage Service (Amazon S3) 之中。這種雙儲存方法有助於安全地保留歷史事件資料，以供未來分析和法規遵循之用。

![使用 AWS 服務實作事件來源模式](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/event-sourcing-4.png)


工作流程由以下步驟組成：

1. 一個乘車預訂請求透過行動用戶端送出至 Amazon API Gateway 端點。

1. 乘車微服務 (`Ride service` Lambda 函數) 會接收請求、轉換物件，然後發布至 Kinesis Data Streams。

1. Kinesis 資料串流中的事件資料存放在 Amazon S3 中，以達到法規遵循和稽核歷史記錄的目的。

1. 這些事件由 `Ride event processor` Lambda 函數轉換和處理，並存放在 Amazon Aurora 資料庫中，以提供乘車資料的具體化視觀表。

1. 系統會篩選已完成的乘車事件，並將其傳送至外部付款閘道進行付款處理。付款完成後，系統會將另一個事件傳送至 Kinesis Data Streams 以更新乘車資料庫。

1. 乘車完成後，乘車事件會重新提供給 `Ride service` Lambda 函數，以建立路線和乘車歷史記錄。

1. 乘車資訊可以透過 `Ride data service` 讀取，該服務會從 Aurora 資料庫中讀取。

API Gateway 也可以將事件物件直接傳送到 Kinesis Data Streams，而不需使用 `Ride service` Lambda 函數。但是，在複雜的系統中，例如乘車招呼服務，事件物件可能需要先處理和豐富，然後才能擷取到資料串流中。基於這個原因，此架構具有 `Ride service`，可在將事件傳送至 Kinesis Data Streams 之前先處理該事件。

## 部落格參考
<a name="event-sourcing-blog"></a>
+ [AWS Lambda 的新功能 — 將 SQS FIFO 做為事件來源](https://aws.amazon.com/blogs/compute/new-for-aws-lambda-sqs-fifo-as-an-event-source/)