CloudFunctions のトリガーに対するディレクトリ・フォルダ・ファイルの設計

こんにちは、最近、CloudFunction を 0->1 で導入することになった Nash です。

この記事では、「Firebase CloduFunctions のトリガーについてのディレクトリ設計」のまとめです。

考えた結果は下記の 3 案になります。

  • 案 1)ディレクトリ構造で表現
  • 案 2)ファイル名で表現
  • 案 3)フォルダ名で表現

では、見ていきます。

(ちなみに、便宜上は Firestore について言及してますが、Realtimedatabase でも可能です)

はじめに

tl;dr

先に結論です。

  • 案 1)で作りがちですが、個人的にはネストが深くてすぐに可読性が落ちるので微妙な設計かと思ってます。
  • 案 2)は小〜中規模のプロジェクトで、案 3)は中〜大規模のプロジェクトで合うように思います。

Firestore のストレージパスとトリガーファイル

3 つの案について見る前に、ディレクトリ設計をする理由などについて簡単におさらいします。

Firebase で提供されている Firestore と CloudFunction のトリガーを組み合わせると、

  • 任意の Firestore のストレージパスに対して CRUD されたことを発火点に、任意のトリガー関数を実行する

ということが出来ます。

そのため、下記についてのマッピングを考えます。

  • A)発火元のストレージパス
  • B)実行されるトリガーのファイル・ディレクトリ

それぞれが紐づくわけですが、B)の設計についてこの記事では考えます。

では、各案を見ていきます。

案 1)ディレクトリ構造で表現

一番、一般的なやり方かと思います。

version/v1/users/index.ts のようにディレクトリ配下にトリガーファンクションのファイルを管理します。

こんな感じ。

2020-05-23_0000_firebase-trigger-for-directory-architecture__01_dir

メリット

  • 直感的にわかりやすい
  • helper.tsなどを近くに起きやすい

デメリット

  • ネストが深くなる
    • Firestore の構造が複雑になり対応するトリガーも増加すると、合わせてこのディレクトリ構造のネストも深くなります。
  • 規約違反な書き方・置き方をしていても漏れやすい
    • ディレクトリが多くなってごちゃごちゃすると、どのファイルがどのストレージパスとマッピングしているか?で間違いが出てきたりします。

所感

普通に設計するとこの構造になるかと思うのですが、上に挙げたデメリットの負の部分が大きいと思っているので、個人的には案 2)か案 3)が良いかと思ってます。

2)ファイル名で表現

最近、試していて個人的に気に入ってます。

version.v1.users.{user_id}.ts のようなファイル名でトリガーファンクションのファイルを管理します。

こんな感じです。

2020-05-23_0000_firebase-trigger-for-directory-architecture__02_file

メリデメは下記かと思います。

メリット

  • ネストがない。ファイル名で深さを表現するので常にネストなしになります
  • 1 つのストレージパスに対して、絶対に 1 つのファイルになる。なので間違って変なところにトリガーを書くことがなくなる。

デメリット

  • 「ファイル名で表現」ということに抵抗があるメンバーがいるかも。。。(とはいえ、Next.js とか Nest.js でも割と使われてる方法なので、それを引き合いに出せば説得できるんじゃないかな)
  • 「あるストレージパスの配下だけで使う helper.ts」みたいのを定義できないです。
    • helper は、/triggersの外のパスに出して/src/helper.tsとかに置いてここを参照するような感じになるかと思います。

所感

1 つのストレージパスに対して複数のファイルを置くことができないので、大規模になると辛そうですが、小〜中規模ならメリットのほうが大きいと思ってます。むしろこの置き方のほうが制限ができるのでカオスになる可能性がなくなりますしね。

ちなみに、ファイル名による規約化も簡単にできるので規約化しちゃうコードを書くのもありかもです。

案 3)フォルダ名で表現

version.v1.users.{user_id}/index.ts のようにフォルダ名でトリガーファンクションのファイルを管理します。

こんな感じです。

2020-05-23_0000_firebase-trigger-for-directory-architecture__03_folder

メリデメで列挙すると下記かと思います。

メリット

  • ネストが 1 層だけでこれ以上は深くならない
  • 1 つのストレージパスに対して、絶対に 1 つのフォルダになる。なので間違って変なところにトリガーを書くことがなくなる。

デメリット

  • 案 2)と同じく、やや直感的ではない命名なのでチーム内で嫌がる人もいるかも。。。
  • ネストが 1 層あるので、ファイルの確認のために 1 回オープンしないといけないです。ネストが深くなることはないですが、エディタとかで見るときに絶妙に見づらいですし、意識しないと見にいけないです。

所感

比較で考えると、「ファイル名で表現する案 2」と比較して、1 層多くディレクトリを切るので複数ファイルを格納することができるようになります。

なので、

  • onCreate/onRead/onUpdate/onDelete が Fat になってしまうのでファイル単位に分けたい
  • 近くにhelper.tsを置きたい

みたいなことができるようになるので、中〜大規模のときに向いてるのかな、と思います。

おわりに

いろいろ設計を考えてみました。案 1)で実際に開発していた経験はあるのですが、案 2)・案 3)ではあまりゴリゴリ開発した経験がないので、ここに書かれている以外の隠れた問題があるかもしれないです。

あと、案 2)のファイルで設計する構造のときに規約化するロジックもまとめたのでこちらでどうぞ。

CloudFunctions のトリガーのファイルを規約化する - Qiita