CloudFunctionsをきれいに整理したい。

こんばんは、[Stamp Inc](https://stamp.team/)の[村本](https://twitter.com/1amageek)です。

普段はFirebaseの技術コンサルティングをやってます。
今日は、[Firebase Meetup #15 Cloud Functions Day](https://firebase-community.connpass.com/event/154400/)が行われてると言うことで、僕は仕事で参加できなかったんだけどちょっとしたTipsを紹介します。

CloudFunctionsの管理って大変ですよね？みんな`index.ts`にベタ書きしてたりしませんか？


## CloudFunctionsの`Function`を整理する

### Functionはネスト可能

CloudFunctionsはJSのモジュールを使えば結構きれいに整理することができます。
CloudFunctionsではネストした`Function`も認識してくれます。次の例をみてください。

これはシンプルなCallable functionの例です。

```typescript:index.ts
export const v1 = {
  func: functions.https.onCall((data, context) => {
    // do something
  })
}
```

これをデプロイした時、クライアントから次のように呼び出すことが可能です。
注目すべきは__`v1-func`__。

```swift
functions.httpsCallable("v1-func").call { (result, error) in
  // do something
}
```

CloudFunctionsではネストされたFunctionは`-`でつなげられられます。

### Functionはモジュール化可能

次にモジュール化したFunctionを見てみましょう。

```typescript:v1/index.ts
export const func = functions.https.onCall((data, context) => {
    // do something
})
```
```typescript:index.ts
import * as V1 from './v1'
export const v1 = { ...V1 }
```

これも先ほどと同じように__`v1-func`__で呼び出すことができます。

## CloudFunctionsの構造化

モジュール化できる性質を利用すれば、次のようにファイルを整理することができます。

### CloudFirestore TriggerのFunctionをまとめる。

```
functions
├── index.ts
└── v1 // version管理
    ├── firestore // firestore triggerをまとめる
    │   ├── index.ts
    │   ├── item.ts // itemのtriggerを定義
    │   └── user.ts // userのtriggerを定義
    ├── callable // callableをまとめる
    └── index.ts
```

まずは`/functions/v1/firestore/user.ts`から見ていきましょう。
ここでは`onCreate` `onUpdate` `onDelete`を定義しました。

```typescript:functions/v1/firestore/user.ts
import * as functions from 'firebase-functions'

export const onCreate = functions.firestore
    .document('/versions/{version}/users/{userID}')
    .onCreate(async (snapshot, context) => {
        // do anything
    })

export const onUpdate = functions.firestore
    .document('/versions/{version}/users/{userID}')
    .onUpdate(async (snapshot, context) => {
        // do anything
    })

export const onDelete = functions.firestore
    .document('/versions/{version}/users/{userID}')
    .onDelete(async (snapshot, context) => {
        // do anything
    })
```

次に、`/functions/v1/firestore/index.ts`

```typescript:functions/v1/firestore/index.ts
import * as User from './user'
import * as Item from './item'
export const user = { ...User }
export const item = { ...Item }
```

この階層では、各々の__Collection__に対してファイルを分離することでFunctionの名前を統一化できます。


次に、`/functions/v1/index.ts`

```typescript:functions/v1/index.ts
import * as Firestore from './firestore'
export const firestore = { ...Firestore } // Trigger系はここに
export const callable = { ...Callable } // Callable系はここに
```

最後に、`/functions/index.ts`

```typescript:functions/v1/index.ts
import * as V1 from './v1'
export const v1 = { ...V1 }
```

全てを`v1`として出力すれば完成！
これで次のようにデプロイされるようになります。

| functions       | 
|:-----------------|
| v1-firestore-user-onCreate |
| v1-firestore-user-onUpdate | 
| v1-firestore-user-onDelete | 
| v1-firestore-item-onCreate |
| v1-firestore-item-onUpdate | 
| v1-firestore-item-onDelete | 
| v1-callable-... | 

ここでは定義していませんが、こんな感じでCallableに関しても階層を作ってあげると管理しやすいと思います！


version管理してなくて、index.tsが悲惨になることってあると思います。🤫

## デプロイ

ファイル分割の方法を使えばデプロイも楽になります。

__全ての`functions`をデプロイする__

```
firebase deploy --only functions
```

__`v1`全てをデプロイする__

```
firebase deploy --only functions:v1
```

__`v1-firestore`をデプロイする__

```
firebase deploy --only functions:v1.firestore
```

__`v1-firestore-user`をデプロイする__

```
firebase deploy --only functions:v1.firestore.user
```

ネストした構成をデプロイするときは、`-`のところを`.`で表現するとその部分だけをデプロイ可能です。

---

おすすめの記事

- [Firebase Cloud FirestoreのTransactionについて考える](https://qiita.com/1amageek/items/2eff436fb69bea5875ea)
- [待ち焦がれたCollectionGroupがCloud Firestoreへやってきた。](https://qiita.com/1amageek/items/343f262ba3b50e657078)