例えば、注文が確定された時、注文確定メールを飛ばしたい。
パターン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ではイベントストアと言っているっぽい。