スタディプラスでサーバーサイドを担当している花井です。
先日田口さんが投稿したこちらのプロジェクトで、実験的にCloud Firestore / Cloud StorageとRailsでAPIを構築したので、その顛末を紹介します。
Firestoreの理由
今回のプロジェクトの要件に、一度データを入稿してしまえば変更はほとんど必要ないような要件がありました。 極端なことを言えば、yamlかjsonファイルをマスタデータとして扱うという選択でもよかったのですが、それだと変更のたびにデプロイが必要なのとスケールするかが心配でした。 また、普段AWSを使っているので単純にGCP環境を使う実績解除という意味あいも多分にありました。そんなわけで、画像の置き場としてCloud Storage、データベースがわりにCloud Firestore を使う構成になりました。
Railsから使う
install Gem
Cloud Firestoreの操作には公式のGemを使いました。
gem 'google-cloud-firestore'
ローカルでテストしている時にGCPにつながらず、地味にはまりました。認証のための情報はこちらを参考に環境変数を設定しています。
データ構造と/models
の構成
データ構造は以下のようにしています。
マスターデータCollection |_ 親モデルDocument | |_ url : 遷移先URL | |_ banner_image_url : バナー画像のURL(Cloud Storageの公開URL) | |_ published: 公開フラグ | |_ 子モデルのMap | | |_ 子モデル識別子 | | |_ [いろいろな情報] | | |_ 孫モデルのMap | |_ [いろいろな情報] Clientから取得するデータCollection |_ Document | |_ 子モデル識別子 | |_ [いろいろな情報]
DBとして扱うので、Railsのお作法にしたがってCloud Firestoreを操作するクラスは /models
に定義しました。
GCPへの接続を各モデルに書かなくていいように、GCPへの接続とFirestore操作を司るクラスは別々に用意しています。
class Gcp require 'google/cloud' class_attribute :client self.client = Google::Cloud.new(ENV['gcp_project_id']) end
class Firestore < Gcp class_attribute :connection self.connection = client.firestore end
Firestoreから取得したデータをModelに変換するところはActiveModel::Attributes
を使いました。
class Collection名 < FirebaseFirestore include ActiveModel::Model include ActiveModel::Attributes class_attribute :parents self.parents = connection.collection ENV['parent_collection_name'] attribute :id, :string attribute :url, :string attribute :banner_image_url, :string attribute :published, :boolean attr_reader :child_model class << self def available parents.where('published', '==', true).get.map do |parent| new(parent_params(parent)) end end private def parent_params(parent) # parentはGoogle::Cloud::Firestore::DocumentSnapshotを想定 # parentのドキュメントIDをidとして扱うためにここで操作 parent.data.merge({ id: parent.document_id }) end end def child_model=(child_models) @child_models = child_models.map do |id, fields| params = { id: id } child_model_params = params.merge(fields) ChildModel.new(child_model_params) end end end
今回の要件では一定数を超えないことがわかっていたので、child_model =
の中でループを回してしまっていますが、もうちょっとうまいやり方もあったかなぁとは思っています。
はじめて使ってみてこのあたりのモデリングをした感想としては、階層関係が深くなったり、件数に制限がなくなったりするデータをCloud Firestoreから読むのは厳しそうと思いました。