こんにちは、YOUTRUST のやまでぃ(YOUTRUST/X)です。
最近のわたくしごとですが
先日、1年振りにグランドピアノで Summer を弾いてみました。
会場は代々木のピアノスタジオ マイレッスンさん
1.5年前に友達から電子ピアノを譲ってもらったのをきっかけに、隙あらば家で弾いて練習しています。
自分で好きな曲が弾けて楽しいし、良い気分転換にもなるし、気がついたら風呂も沸いているので結構気に入っている趣味です。
今日はなんの話?
先日 YOUTRUST で実際に発生してしまっていた不具合についてのお話です。
まずは以下の擬似コードをご覧ください。
class User < ActiveRecord::Base has_one :hoge end class Hoge < ActiveRecord::Base belongs_to :user end
class HogeController < ApplicationController before_action :authenticate_user! # 認証処理 def create # `current_user` → 認証された User インスタンス result = HogeQuery.run(operation_user: current_user) return head :ok if result use_case = CreateHogeUseCase.run(operation_user: current_user) if use_case.success? head :ok else head :bad_request end end end
class HogeQuery include QueryRunnable # .run → .new.run みたいなクラスメソッドを定義している def initialize(operation_user:) @operation_user = operation_user end def run Hoge.where(user: @operation_user).exists? end end
class CreateHogeUseCase include UseCase::Base # .run → .new.tap(&:run) みたいなクラスメソッドを定義している attr_reader :hoge def initialize(operation_user:) @operation_user = operation_user end def run # 排他制御のためのロック @operation_user.with_lock do command = CreateHogeCommand.run(user: @operation_user) if command.success? @hoge = command.hoge else errors.add(:base, 'failed') raise ActiveRecord::Rollback end end end end
class CreateHogeCommand include Command::Base attr_reader :hoge validate :validate_user def initialize(user:) @user = user end def run @hoge = Hoge.create!(user: @user) end private def validate_user if Hoge.where(user: @user).exists? errors.add(:base, 'invalid') end end end
module Command::Base extend ActiveSupport::Concern include ActiveModel::Model module ClassMethods def run(**args) new(**args).tap { |command| command.valid? && command.run } end end def success? errors.none? end end
説明用の超簡素化されたコードなのですが、「Hoge レコードがなければ作る」というとってもシンプルなものになっています。
こちらはご覧の通り User は Hoge を一つのみ所有できることを想定しています。
ですが、こちらのコードには不具合が潜んでおり、同じ User に対して複数の Hoge レコードが作成されてしまう可能性があります。(もちろんデータベースのテーブルに UNIQUE インデックスを貼っていたらエラーが発生して作成はされませんが、今回はインデックスがないと仮定してください。)
僕は最初、「完璧なロジックだ。複数作成される訳がない。データベース側の不具合なのでは?」と少し本気で思っていました。(ごめんなさい)
▼コードを読み解くための参考リンク
不具合の詳細の解説
…をしても良いのですが、折角なので読者の方にも少し考えていただいて、解説は次回の記事にて行いたいと思います。
- 「原因が分かった!早く答えを言いたい!」
- 「全然分からなくて夜も眠れない!次回の記事まで待てない!」
- 「YOUTRUST のエンジニアリングに興味がある!話を聞きたい!」
と思われた方は是非ともわたくしとカジュアル面談 しましょう!
もちろんそのまま下記募集に応募くださっても大丈夫です!
それでは今回は以上となります。
次回の解説編にご期待ください!