MEETUPこんにちは!BOOTH部所属エンジニアのRND(らんど)です。2023のライトニングトークセッションにて「限界ORM!BOOTHとギフトとライブラリ」というタイトルで発表を行いました。本記事はそちらの発表内容を元に記事に起こしたものです。Railsで開発されています。RailsではORMとしてActiveRecordが付属していますが、プロダクトを開発する中でActiveRecordの標準の機能では対応できないクエリが必要となるケースが多々あります。いったケースにBOOTHではどのように立ち向かっているのか、BOOTHのギフト機能の開発でぶつかった2つの事例を紹介します。BOOTHのギフト機能の概要についてギフト機能とは、BOOTH上で販売されているダウンロード商品を購入する際にギフトとして購入することができる機能です。ギフトとして購入した商品には、専用のギフト用URLが発行されます。このURLは商品の購入者でなくともアクセスでき、アクセスしたページで商品を受け取ることができます。受け取った商品はBOOTH上のライブラリなどからファイルを閲覧・DLできるようになります。テーブルの構造は下図のようになっており、ギフトの場合と通常の購入の場合とで商品の所有者が異なる仕様になっています。この仕様のために後述するBOOTHのライブラリ画面は大きく変更が必要になりました。事例1:ライブラリでの表示BOOTHのライブラリ画面は自分が所有しているダウンロード商品の一覧を表示する画面です。ギフト機能導入以前は、自分が購入したダウンロード商品を入手順に並べているだけの画面でした。ギフト機能によって、購入者と所有者が一致しなくなったため、このロジックは変更が必要になりました。ギフトの仕様上、ギフトとして購入した場合とそれ以外の場合では所有している商品を取得するクエリが異なります。これまで通り自分が所有している商品を入手順に並べる場合、ギフトで受け取った商品とそれ以外の商品を合わせてから入手順に並び替える必要があります。いったケースではSQLのUNION句を用いて、異なる経路でJOINしたテーブルをひとつにまとめる必要がありますが、ActiveRecordにはUNION句を発行する機能がありません。gemを用いてActiveRecordの機能を拡張するなどの方法もありますが、BOOTHでは実装方法について検討する前に要件の見直しを検討しました。その結果、ライブラリのページをギフトとして入手した商品と購入した商品とで分割することにしました。ライブラリ画面をギフトとそれ以外で分割する形であっても、ユーザー体験に支障がないと判断したためです。これによって2つの経路でJOINされたテーブルをまとめる必要がなくなりました。事例2:ギフトの複数回受け取りライブラリ画面をギフトに対応したものに改修する上でもう一つ課題がありました。BOOTHのギフト機能には、同一商品のギフトを複数回受け取れるという仕様があります。チームで議論し、複数回ギフトとして受け取った商品については直近に受け取ったものだけをライブラリに反映するのが良いだろうという結論になりました。BYでギフトの受け取り日時で並べ替えるといったクエリが必要になります。BYよりも後に実行されるという仕様のため、これを実現するには集約関数を用いたクエリか、サブクエリを用いたクエリが必要です。集約関数を用いたクエリについてはActiveRecordの標準の機能では記述できないため、SQLの一部を直接書く必要があります。サブクエリを用いた方法はActiveRecordのfromを使うことでActiveRecordの標準機能の範囲内で記述できます。このクエリはギフトの件数が多くなってきた場合にパフォーマンスが落ちます。根本的な解決として、ライブラリ専用のテーブルを作成し、ギフトや購入の際にテーブルを更新する設計にする案が考えられます。この設計であれば前述のような複雑なクエリが不要になります。将来的にはライブラリ用にテーブル設計を変更した方がメンテナンスしやすく変更が容易で様々な要件に対応できます。実装コストが高く、まずはユーザーにとって必要なものを素早く出すことを優先し、サブクエリを用いた方法を採用することにしました。まとめRailsのアプリケーションを書いている中で、ActiveRecordが対応していない複雑なクエリが必要になるケースは頻繁に生じます。いったケースが生じる背景にはいくつか種類があります。ビジネスロジックが複雑になっているテーブルやモデルの設計がビジネスロジックとうまく合致していない標準SQLにない機能を使用している今回紹介した事例1のように、ビジネスロジックが必要以上に複雑になっている可能性を考え、まずは要件と仕様を見直し、ビジネスロジックの複雑化を回避できないかを考える必要があります。要件を見直した結果、ユーザー体験のために必要な仕様であれば、今回紹介した事例2のようにテーブル設計から見直すことでシンプルにビジネスロジックを表現できる形がないかを探るべきでしょう。MySQLのインデックスヒントを使用したいケースなど標準SQLにない機能を使用する場合にはActiveRecordの標準の機能では対応できません。いったケースでは特定のDBにロックインするデメリットを受け入れた上でActiveRecordを拡張してそれらの機能に対応する選択肢が考えられます。このように、ActiveRecordが対応していないクエリが必要になったタイミングで要件や設計の見直しを行うのが良いと思います。一方で、理想の形を実現するのにコストがかかる場合には、まずはより素早く実装できる選択肢をとるべきです。新しい機能の使われ方には不確実な部分も大きく、機能を出してみてから見直すことでより適切な設計が見えてくる場合もあります。おわりにピクシブではバックエンドエンジニアを募集中です。興味をお持ちの方は下記URLよりエントリーをお待ちしております。hrmos.coRND2019年4月新卒入社。クリエイター事業部所属。BOOTHでサーバサイドエンジニアをやっています。絵を描くのとボードゲームが趣味。MEETUP2024-10-30pixivというシステムはどんな形をしているのか、それはなぜか。insideは、ピクシブ株式会社が運営するオウンドメディアです。Reserved.引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。