無伺服器與事件溯源結合的演示案例:將事件溯源作為Azure函式的資料持久化機制的庫

banq發表於2019-11-14

簡單的說,事件溯源是一種儲存狀態(對於實體)的方法,該狀態是通過儲存該實體發生的所有事件的順序歷史記錄而起作用的。對實體的更改將作為新事件寫入,附加到該實體的事件流的末尾。

當查詢或業務流程需要使用實體的當前狀態時,它會通過在事件流上執行投影來獲取此資訊,這是一段非常簡單的程式碼,對於每個事件,它決定(a)我是否關心這種型別事件(b)如果是,我收到事件後該怎麼辦。

目標是能夠與實體的事件流進行互動,而無需在Azure函式本身中進行任何額外的設定-既可以訪問事件流,又可以通過在執行Azure函式時例項化的繫結變數來執行投影。

要將事件新增到事件流,可以使用事件流屬性和類,因此:

[FunctionName("OpenAccount")]
public static async Task<HttpResponseMessage> OpenAccountRun(
              [HttpTrigger(AuthorizationLevel.Function, "POST", Route = "OpenAccount/{accountnumber}")]HttpRequestMessage req,
              string accountnumber,
              [EventStream("Bank", "Account", "{accountnumber}")]  EventStream bankAccountEvents)
{
    if (await bankAccountEvents.Exists())
    {
        return req.CreateResponse(System.Net.HttpStatusCode.Forbidden , $"Account {accountnumber} already exists");
    }
    else
    {
        // Get request body
        AccountOpeningData data = await req.Content.ReadAsAsync<AccountOpeningData>();

        // Append a "created" event
        DateTime dateCreated = DateTime.UtcNow;
        Account.Events.Opened evtOpened = new Account.Events.Opened() { LoggedOpeningDate = dateCreated };
        if (! string.IsNullOrWhiteSpace( data.Commentary))
        {
            evtOpened.Commentary = data.Commentary;
        }
        await bankAccountEvents.AppendEvent(evtOpened);
                
        return req.CreateResponse(System.Net.HttpStatusCode.Created , $"Account {accountnumber} created");
    }
}

從事件流中獲得狀態的方式稱為投射 projection:

 [FunctionName("GetBalance")]
   public static async Task<HttpResponseMessage> GetBalanceRun(
     [HttpTrigger(AuthorizationLevel.Function, "GET", Route = "GetBalance/{accountnumber}")]HttpRequestMessage req,
     string accountnumber,
     [Projection("Bank", "Account", "{accountnumber}", nameof(Balance))] Projection prjBankAccountBalance)
   {
       string result = $"No balance found for account {accountnumber}";

       if (null != prjBankAccountBalance)
       {
           Balance projectedBalance = await prjBankAccountBalance.Process<Balance>(); 
           if (null != projectedBalance )
           {
               result = $"Balance for account {accountnumber} is ${projectedBalance.CurrentBalance} (As at  {projectedBalance.CurrentSequenceNumber}) ";
           }
       }
       return req.CreateResponse(System.Net.HttpStatusCode.OK, result); 
   }

這兩個屬性的所有屬性都設定為AutoResolve,因此可以在執行時設定它們。

由於事件流本質上是僅附加系統,因此其基礎的儲存技術是AppendBlob-一種特殊的Blob儲存型別,它僅允許將塊附加到Blob的末尾。每個Blob最多可以儲存50,000個事件,並且容器路徑可以與任何其他Azure Blob儲存相同的方式巢狀。

對於高容量流,可以使用Azure Tables後端代替AppendBlob。儲存技術和儲存目標的選擇可以通過應用程式上的配置設定進行切換。

在該庫中,必須根據需要檢索實體的狀態-這是為了使函式應用程式分解為零,並且實際上允許多個獨立的Azure功能應用程式使用相同的基礎事件流而不必使用任何“始終線上”一致性服務。

 

相關文章