トランザクションを貼る位置について考える機会があったので、それをまとめたいと思います。
トランザクションを貼る目的
トランザクションを貼る目的としては、データの整合性を担保したいからです。例えば次のようなコードを考えます。
def create
subscription = Subscription.create(subscription_params)
SubscriptionLogService.execute!(subscription) # ログを作成
end
サブスクリプションのレコードを作成した後、そのログレコードを作成しています。このとき、ログレコードの作成に失敗したときにどうなるかというと、サブスクリプションは作成されたが、そのログレコードは残っていないという状況になります。これではデータの整合性が担保されません。これを防ぐためにトランザクションを貼ります。
def create
ActiveRecord::Base.transaction do
subscription = Subscription.create(subscription_params)
SubscriptionLogService.execute!(subscription)
end
end
こうすることで、ログ作成の箇所で失敗したとしても、サブスクリプションレコードが作られる前までロールバックすることができます。
トランザクションを貼る位置
トランザクションを貼る位置はデータの整合性を担保したい最小単位で貼るのがいいです。例えば以下のようにコントローラーで貼るのがいいのか、ServiceやModelで貼るのがいいのかという議論が出てきます。下の例はコントローラーで貼っている例です。
def create
ActiveRecord::Base.transaction do
SubscriptionCreateService.execute!(subscription_params)
end
end
# subscription_create_service.rb
def execute!(params)
subscription = Subscription.create(params)
SubscriptionLogService.execute(subscription)
end
この場合は、Serviceで貼ったほうがいいです。なぜなら、整合性を担保したい最小単位で貼ろうとすると、Serviceになるからです。
プログラムの意図としては、サブスクリプションのレコードとそのログレコードで合わせたいはずです。だとすると、整合性担保の最小単位はexecute!
の中ということになります。
トランザクションブロックが広すぎたり、他のファイルで貼ってあると、どこの整合性を保とうとしているのかが分かりづらくなります。
また、他のファイルからSubscriptionCreateService
を呼び出したときに呼び出し元でトランザクションを貼るのを忘れてしまうといったことも起こりえます。
処理が複雑になるとトランザクションがネストしてしまいますが、ActiveRecord::Rollback
以外の例外が起きたときは一番外側のトランザクションまでロールバックされるので、整合性の担保は保証されるでしょう。
ActiveRecord::Rollback
についての挙動はこちらの記事をどうぞ!
Railsでのトランザクションの貼り方について
トランザクションの貼り方について補足をしておきます。
Railsではトランザクションは以下のようにも貼ることができます。
def create
Subscription.transaction do
subscription = Subscription.create(subscription_params)
SubscriptionLogService.execute!(subscription)
end
end
# 保存するモデルと異なってもOK
def create
User.transaction do
subscription = Subscription.create(subscription_params)
SubscriptionLogService.execute!(subscription)
end
end
ただし、当然といえば当然ですが上の例でいうとsubscriptions
テーブルとusers
テーブルが異なるデータベースにある場合はトランザクションを貼ることはできません。
まとめ
今回の記事の内容をまとめると、トランザクションを貼る位置はデータの整合性を担保したい最小単位で貼るのがいいということでした。
分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!
Twitterもやってますので、フォローしていただけるとうれしいです。