0
0

More than 3 years have passed since last update.

RailsにバッチAPI導入してみた

Last updated at Posted at 2020-04-28
1 / 10

バッチAPI


バッチAPIの目的

  • システムによって、バッチAPIの目的が違います

非同期の処理のエンドポイント

  • リクエストして、返すのはデーターじゃなくJobID
  • その後に、Jobの状態を収得して、成功であれば、データーが取れます。
  • Twitter API

複数リクエスをまとめるエンドポイント

  • 多くリクエストをしたい場合 → 一つのリクエストにまとめる
    • 100リクエストを送りたい
    • 同じEndpointですが、Paramsが違います
  • 複数リクエストと関係があり → 一つのリクエストにまとめる
    • 同時に成功した・同時に失敗したい
    • 前のリクエストの結果は次のリクエストのParams
  • Google API

バッチAPIが必要

  • フロントエンドは良く変えましたが、バックエンド修正したくない
  • 同時に複数POSTリクエストを送りたい
  • 複数POSTリクエストの中に一つ失敗したら、戻したい

RailsにバッチAPI簡単に入れられます

  • Rails middleware処理
  • リクエスト → MiddleWare → Router → Controller
  • Rack Middleware 入門

バッチ導入しました

  • Batch middleware追加 → 複数リクエスト対応
  • DB Transactions追加 → リクエスト失敗時に、戻せる

RailsにバッチAPI簡単に入れられますが

  • Auth処理 欠点
  • DB Transactions 欠点

Rails6.0のバッチAPIのコード


class BatchApi
  def initialize(app)
    @app = app
  end

  def call(env)
    if env['PATH_INFO'] == '/api/batch'
      process_batch(env)
    else
      @app.call(env)
    end
  end

  def process_batch(env)
    responses = []
    request = ActionDispatch::Request.new(env.deep_dup)
    ActiveRecord::Base.transaction do
      request.parameters[:requests].each do |override|
        # 子供は別のtransationを使いたいですので、requires_new: trueをつけます。
        ActiveRecord::Base.transaction(requires_new: true) do
          response = process_request(env.deep_dup, override)
          responses.push(response)

          # 子供は何かエラーは発生した時に、子供のデータをロルバックする
          raise ActiveRecord::Rollback unless success?([response])
        end
      end

      # レスポンスが失敗の場合、全部ロールバックします。
      raise ActiveRecord::Rollback unless success?(responses)
    end

    render_response(responses)
  end

  def process_request(env, override)
    path, query = override['url'].split('?')
    env['REQUEST_METHOD'] = override['method']
    env['PATH_INFO'] = path
    env['QUERY_STRING'] = query
    env['FROM_BATCH_API'] = true
    # env['rack.input'] = StringIO.new(override['body'].to_json)
    # 2020/11/12アップデート: to_jsonは&がある場合、変気宇されちゃったのでたまにエラー発生した、代わりにJSON::generate または JSON::dump 使うべき
    env['rack.input'] = StringIO.new(JSON::generate(override['body']))


    status, headers, body = @app.call(env)
    { status: status, headers: headers, body: status == 200 ? JSON.parse(body&.body) : body }
  end

  def render_response(responses)
    hash = {}
    hash[:l] = responses

    success_response = responses&.find { |response| response[:status] == 200 }
    headers = success_response ? success_response[:headers] : responses[0][:headers]

    hash_json = hash.to_json

    # レスポンスの長さを更新します。
    headers = headers.merge('Content-Length' => hash_json.bytesize)

    [200, headers, [hash_json]]
  end

  def success?(responses)
    responses&.each do |response|
      return false if response[:status] != 200 
    end
    true
  end
end


まとめる

  • バッチAPIが作れました
  • メリット/デメリット出来ました
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0