やる気がストロングZERO

やる気のストロングスタイル

【DDD】ドメインイベントについて学んだので自分的理解をまとめる

例えば、注文が確定された時、注文確定メールを飛ばしたい。

パターンA

注文確定処理の中にメール送信処理を埋め込む

# 注文クラス
class Order
  def initialize(user_id)
    @user_id = user_id
    @item_ids = []
  end

  def add_item(item_id)
    @item_ids << item_id
  end

  def purchase
      # 注文確定処理実行
      # メール送信処理実行
  end
end

# クライアント側
def main
  order = Order.new(1)
  order.add_item(10)
  order.add_item(20)
  order.purchase # メールも飛ぶ
end

こうすると、メール送信に依存してしまう。
注文だけ実行したい要求が発生した時に困る。

パターンB

注文確定処理とメール送信処理を分けて、クライアント側で操作する。

# 注文クラス
class Order
  def initialize(user_id)
    @user_id = user_id
    @item_ids = []
  end

  def add_item(item_id)
    @item_ids << item_id
  end

  def purchase
      # 注文確定処理実行(メール送信はここでは行わない)
  end
end

# クライアント側
def main
  order = Order.new(1)
  order.add_item(10)
  order.add_item(20)
  if order.purchase
    # メール送信処理実行
  else
    raise("注文処理が失敗しました")
  end
end

柔軟に対応できそう。

パターンC(イベントを使う)

注文確定処理のイベントに対して、メール送信処理を登録しておく。

# 注文クラス
class Order
  def initialize(user_id)
    @user_id = user_id
    @item_ids = []
    @purchased_subscribers = []
  end

  def add_item(item_id)
    @item_ids << item_id
  end

  def purchase
    if 注文確定処理実行
      purchased
    end
  end

  # 注文確定処理実行時イベントに対するサブスクライバの登録
  def add_purchased_subscriber(subscriber)
    @purchased_subscribers << subscriber
  end

  private
    # 注文確定時に実行する
    def purchased
      @purchased_subscribers.each do |subscriber|  # 非同期に処理されるようにしておくと良さそう。。
        subscriber.execute(@user_id, @item_ids, DateTime.now)
      end
    end
end

# メール送信用サブスクライバ
class MailSendSubscriber
  def execute(user_id, item_ids, purchased_time)
    # メール送信処理
  end
end

# クライアント側
def main
  order = Order.new(1)
  order.add_purchased_subscriber(MailSendSubscriber.new)
  order.add_item(10)
  order.add_item(20)
  order.purchase
end

なんかややこしい。
パターン2でいいんじゃないか?

今回のような「完了したらメール送信」くらいだったらパターン2のほうがシンプルで良さそう。

ただし、もっと細かい要求がある場合、
例えば、
「購入処理中でAの状態になったらAメールを出したいし、Bの状態になったらサポートセンターに対応してもらうためにアラートを投げたいし」
とかだと、クライアント側で対応するには結果情報をすべて返却しないといけなくなってくるので辛い。

こういった場合はそれぞれの場合のイベントを用意して、それぞれのイベントに対応するサブスクライバを用意してセットしてやるといい感じに依存性を排除しながら処理を組み立てられる感じ。

こんな感じで、 「ドメインの処理の要所要所でイベントが発火するように作っておき、イベントのタイミングで実行させたい処理を登録できるようにしておくと依存性を抑えられる」
というのが「ドメインイベント」という理解をした。

同期処理と非同期処理

購入確定処理が実行された時、
在庫減算処理は即座に対応しないといけない。
対して、メール送信は即座に送信しなくてもいい。

この場合、在庫減算処理は同じトランザクションで一緒に処理してしまい、メール送信はキューに入れておいて別途メール送信処理が順次対応していけばいい。

順次対応して最終的に整合が取れている状態になることを結果整合性といい、 「キューに入れて」の部分をDDDではイベントストアと言っているっぽい。