トランザクションスクリプト

あれこれ1年やってまいりましたが。トランザクションスクリプトでいいじゃん、と。「で」いいじゃんというより、「が」いいじゃんという境地です。

まずはそこを見れば、ユースケースやら機能仕様書やらの内容がそのまんまですからという点。

それから、ややこしいんで長文になりそうですが。

AというエンティティとBというエンティティがあったとして、それぞれに業務ロジックがあるわけです。で、それぞれの業務ロジックはそれぞれにあるべきとすると、AとBにメソッドとして実装されたり、まあそこまでしなくてもA用のロジッククラス、B用のロジッククラスなんてものがあって、そこに実装される訳です。

Aのある項目を算定するルールとか、そのエンティティに閉じたようなというか、静的というか、そういう業務ロジックならばAクラスの中にあっていいんだろうけど、業務ロジックって大概手順ですんでこれまた。

で、ある業態では、画面からAを生成してDBにしまっておしまいだけど、違う業態だと、画面からBを生成したり更新したタイミングで、システムで自動的に関連するAを生成して両方をDBにしまう、なんて場合に、Bのロジックが呼ばれてAの初期化ロジックが呼ばれて、となるはずです。

この受注は出荷基準なので引き当て終わって出荷確定したと同時に裏で売上作るけど、売上単独でエントリーもしたいなーとか。

そうすると、B用クラスがA用クラスを参照しつつ、Bのあるロジックの中でAのロジックはA用クラスへ処理を委譲する事になる。まあ普通だ。

作る時はいいんだけどねぇ・・・・運用に入ると、特にフォローアップ期間中でガンガン変更が入ったりすると結構しんどいんだ。可読性の問題じゃない。A単独の場合はこうだけど、B経由だとAは微妙にこうでした、という要望が結構多いんだ・・・。

そうすると、AにB経由用の初期化メソッドを追加する事になる。苦し紛れぽいが仕方ない。けど、Bからみりゃ必然なのが判るけど、A単独でみてよくわからんメソッドに見える。

大体、このB経由用の初期化メソッドというのは、Aの持ち物なのか?「Bの登録」というトランザクションの持ち物なのではないのか?ならばそこに持たせればいいんじゃないか。

なので最初からトランザクションスクリプトにしておけばいいじゃん、と。

うーん、わかりにくいかなあ・・・・。ではメール送信の話でどうだ。

Bの生成時、DBに登録した後ユーザーにその旨メールを送信するという仕様。それと同時に、Aも生成して欲しい、と。そのAは既に実装済みで、これまたDB登録と同時にお知らせメールを送る仕様だった。

さてBのロジックの中身をどうする?

  1. BのDB登録
  2. Bのメール送信
  3. Aのロジックへ委譲
    1. AのDB登録
    2. Aのメール送信

DBアクセスが複数ですんで当然同一トランザクション内ですわな。AのDB登録でコケたらロールバックされますわな。でもBのメールは送られちゃって取り返しつきませんわな。

それならってんでちょっと工夫してこんな順番で実装する。

  1. BのDB登録
  2. Aのロジックへ委譲
    1. AのDB登録
    2. Aのメール送信
  3. Bのメール送信

うん、これならどっちのDB登録でコケてもメールは送られない。

って、なんでAの実装の中身を意識してBを実装せにゃならんのか。気持ち悪い。すごーく気持ち悪い。

次。どんな順序で処理が書かれてても大丈夫なように、トランザクション内のメール送信をフックしてキューに溜め込んでおいて、コミットした時点で一斉送信という事にすれば大丈夫じゃん。

と思ったまではいいが、逆を考えるとだめだめです。DB登録は無事済んでコミットされた後、メール送信でコケたらどうなるか。画面にはエラー文言が出て、実際にメールも送信されないけど、DBにはデータが入っちゃってるという不健康な状態になります。

大体経験上、DBエラーよりメール送信エラーのほうが頻度高いです。メールアドレスに何が入るか判ったもんじゃないから。

ゆえに、S2Maiトランザクション対応機能はお奨めしませんです。危険が危ない。

なのでこうする。

  1. BのDB登録
  2. AのDB登録
  3. Bのメール送信
  4. Aのメール送信

オーイエー、もう全ての問題が解決だ。ビールでも飲もうぜ。まあこれじゃあんまりだというなら、Aのロジックをこまかくして

  1. BのDB登録
  2. AのDB登録ロジックへ委譲
  3. Bのメール送信
  4. Aのメール送信ロジックへ委譲

みたくする。この場合ビールはアサヒのDRYで。おっとうまい事云っちまった。

なので気分はトランザクションスクリプトちゃんでござるの巻でした。