Rust中的事件溯源 - ariseyhun

banq發表於2022-02-08

Rust 是一種與事件溯源藝術相結合的令人難以置信的語言。
這篇博文的目的是介紹我一直在全職工作的一個名為Thalo的專案。
它是一個 Rust 庫,提供構建事件源系統所需的一切。
目前,Thalo 提供:
  • 具有基本聚合、事件、事件儲存和事件流特徵的核心板條箱,以及一些派生宏
  • 測試庫(Given-When-Then)
  • Postgres、記憶體和檔案事件儲存
  • 卡夫卡事件流

 
您可以透過克隆專案並在單獨的終端選項卡中執行客戶端和伺服器來執行示例。

$ git clone git@github.com:thalo-rs/thalo.git && cd thalo
$ cargo run -p example-protobuf --bin server
# In separate terminal tab
$ cargo run -p example-protobuf --bin client


由於Thalo提供的派生宏,編寫聚合體並不費力,但我仍然覺得缺少一些東西。我已經研究了CloudEvents和AsyncAPI作為定義聚合模式的選擇,但它們似乎並不擅長這樣做。
 

ESDL
事件源模式定義語言是一種模式語言,用於定義集合體、命令、事件和型別,可用於在Rust中生成程式碼,大大簡化了集合體的模板。網址



examples > bank-account > B bank-account.esdl
   1 aggregate BankAccount { open_account(initial_balance: Float!): OpenedAccount!
   3     deposit_funds(amount: Float!): DepositedFunds!
   4     withdraw_funds(amount: Float!): WithdrewFunds!
   5   }
   6
   7     event OpenedAccount {
   8     initial_balance: Float!
   9   }
 10
 11      event DepositedFunds {
 12      amount: Float!
 13    }
 14
 15      event WithdrewFunds {
 16      I amount: Float!
 ¹⁷      }
 18


如果你熟悉GraphQL,它是不言自明的,但讓我們一行一行地看下去。
  • aggregate  BankAccount {
    一個esdl檔案總是必須精確地定義一個聚合體,在我們的例子中名為BankAccount。
  • open_account(initial_balance: Float!): OpenedAccount!
    我們定義了一個名為open_account的命令,它需要一個初始餘額的浮動值,併產生一個OpenedAccount事件。
    注意!這意味著型別是必須的,不能是未定義/空/無。
  • deposited_funds(amount: Float!): DepositedFunds!
    withdrew_funds(amount: Float!): WithdrewFunds!: WithdrewFunds!
    如上所述,這些也是接受一個金額並返回一個事件的命令。
  • event  OpenedAccount {
    這裡我們定義了OpenedAccount事件和它的可用欄位。
  • initial_balance: Float!
    開戶事件有一個初始餘額,要求有一個浮動值。

......其餘的就很好解釋了。
在這一點上,我們有一個用於事件源的Rust庫,和一個用於定義聚合的模式語言。如果我們把這兩者結合起來,我們就可以用esdl檔案來實現模式,而用Rust來透過程式碼生成來實現。
為了從這個esdl檔案中生成Rust程式碼,我們可以使用build.rs檔案並使用esdl crate進行編譯。

// build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    esdl::configure()
        .add_schema_file("./bank-account.esdl")?
        .compile()?;
    Ok(())
}


而在我們的Rust程式碼中,我們可以使用Thalo提供的宏來匯入它。

thalo::include_aggregate!("BankAccount");

這將包括生成的程式碼,其中包括:
  • trait BankAccountCommand
  • enum BankAccountEvent
  • struct OpenedAccountEvent
  • struct DepositedFundsEvent
  • struct WithdrewFundsEvent

由此,我們可以開始執行命令:

use thalo::aggregate::{Aggregate, TypeId};
#[derive(Aggregate, Clone, Debug, Default, PartialEq, TypeId)]
pub struct BankAccount {
    id: String,
    opened: bool,
    balance: f64,
}
impl BankAccountCommand for BankAccount {
    type Error = ();
    fn open_account(&self, initial_balance: f64)
        -> Result<OpenedAccountEvent, Self::Error> { ... }
    fn deposit_funds(&self, amount: f64)
        -> Result<DepositedFundsEvent, Self::Error> { ... }
    fn withdraw_funds(&self, amount: f64)
        -> Result<WithdrewFundsEvent, Self::Error> { ... }
}

然後用apply函式來更新聚合狀態:

fn apply(bank_account: &mut BankAccount, event: BankAccountEvent) {
    use BankAccountEvent::*;
    match event {
        OpenedAccount(OpenedAccountEvent { initial_balance }) => {},
        DepositedFunds(DepositedFundsEvent { amount }) => {},
        WithdrewFunds(WithdrewFundsEvent { amount }) => {},
    }
}


ThaloESDL將讓您在為事件源系統編寫乾淨且一致的聚合方面領先一步。
我正在積極地全職開發 Thalo,並希望在未來幾個月內釋出一些實質性的東西,敬請期待!
 

相關文章