Skip to content

Instantly share code, notes, and snippets.

@j5ik2o
Last active October 3, 2020 00:48
Show Gist options
  • Save j5ik2o/38a7b8950b617dacad619c2f226e9a24 to your computer and use it in GitHub Desktop.
Save j5ik2o/38a7b8950b617dacad619c2f226e9a24 to your computer and use it in GitHub Desktop.

State Sourcing(CRUD)なら以下のようなイメージ。まぁ普通によく見るロジック。

class AddCartItemCommandProcessorOnSS(cartRepository: CartRepository) {

  def execute(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = {
    // 最新の集約(グローバルなエンティティ)をストレージから取得する
    val cart = cartRepository.findById(cartId)
    // ロジック実行: 予算超過ならカートオブジェクトが商品の追加を拒否する! (1)
    val newCart = cart.addItem(itemId, num) 
    // 更新された最新状態をストレージに保存する
    cartRepository.store(newCart) // 必要なら楽観ロックなどを行う (2)
  }

}

Event Sourcingだと以下のようになる。(1)の整合性チェックは可能だが、(2)のロックはできなくなる…。複数のサーバやスレッドでこのメソッドが呼ばれてしまうと不正なイベントがキューに登録されてしまうことがある。

class AddCartItemCommandProcessorOnES(cartEventService: CartEventService) {

  def addCartItem(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = {
    val allEvents = cartEventService.getAllEventsById(cartId)
    val cart = new Cart(allEvents)
    // ロジック実行: 予算超過ならカートオブジェクトが商品の追加を拒否する! (1)
    val itemAdded = cart.addItem(itemId, num)
    // 複数サーバで実行されても大丈夫? いや大丈夫ではない。壊れます!
    // 一般的なキューだと楽観ロックはできません。かといってRDBも不向き 
    cartEventService.publish(itemAdded) // イベントストレージに追記書き込み。常に追記になります。(2)
  }
  
}

Cart#addItemが、システム全体でただ一つの一意な軽量プロセスで処理されるなら、Cart#versionを使って楽観ロックを実現することが可能です。

class AddCartItemCommandProcessorOnES(cartEventService: CartEventService) {

  def addCartItem(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = {
    val cart = actorSystem.actorOf(cartId) // システム全体でただ一つの軽量プロセスとしてのCartの参照を取得する
    // コマンドのバージョンとCartのバージョンが一致していなければ拒否する!
    // ロジック実行: 予算超過ならカートオブジェクトが商品の追加を拒否する!
    val itemAdded = cart.addItem(itemId, num)
    cartEventService.publish(itemAdded) // イベントに現在のバージョン+1を格納する
  }
  
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment