Cloud functions + typescriptでファイル分割を行う方法（あとレスポンスの改善とか）

Gakuです。
firebase楽しいぃいいい！状態でGWは完全ひきこもって実装していました。
まぁ、ニートなので時間は無限にあり、GWとかあんま関係ないんですけどね(´・ω・｀)

今回はfirebaseのCloudFunctionsで、ファイル分割する方法でちょっとハマったので、備忘録的に簡単なtipsを掲載したいと思います。

# ファイルを分割するのは超簡単

```
functions/src/
  - index.ts
  - event.ts
  - event2.ts
```
みたいな感じでファイル分割をしたい場合、

```typescript:index.ts
import * as admin from 'firebase-admin';
import * as event from './event';
import * as event2 from './event2';

admin.initializeApp();

export {
    event,
    event2,
};
```
のようにindex.tsで対象ファイルを呼び出してあげればokです。
event.tsとevent2.tsは以下のような感じで、普通に記述します。

```typescript:event.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

// region指定
const f = functions.region('asia-northeast1');

// カウントアップする
export const test = f.firestore.document('tests/{id}')
.onCreate(async (snap: FirebaseFirestore.DocumentSnapshot, context: functions.EventContext) => {
    await admin.firestore().collection('tests').doc('counter').set({
        count: admin.firestore.FieldValue.increment(1.0),
    }, { merge: true });
});
```

これでdeployすると「ファイル名-関数名」でcloud functions上に生成されます。
簡単(´・ω・｀)b

# レスポンスの問題
[[Ginco Tech Blog]GincoにおけるCloud Functionsの利用とその高速化](https://tech.ginco.io/post/ginco-engineer-meetup-2018-cloud-functions/)
こちらの記事を読んでいますと、Cloud functionsはCold start時、index.tsから全関数を読み込んで、その後に対象の関数を実行するらしいです。
なので、巷の記事では

```javascript
// deploy時: process.env.FUNCTION_NAMEは空なので、定義した関数をdeploy
// 実行時: process.env.FUNCTION_NAMEには関数名が入っているので、対象関数のファイルのみ実行
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'Func1') {
    exports.Func1 = require('./funcs/func1');
}
```
として、呼び出しの際は対象の関数しか読み込まず、実行することでレスポンスの改善をはかる方法があるらしいです。

今回のコードで書くとしたら以下のようになります。

```typescript:index.ts
import * as admin from 'firebase-admin';

admin.initializeApp();
const files = {
    event: './event',
    event2: './event2',
};

const loadFunctions = (filesObj: any) => {
    for (const key in filesObj) {
        if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME.startsWith(key)) {
            module.exports[key] = require(filesObj[key]);
        }
    }
};

loadFunctions(files);
```

```typescript:event.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

// region指定
const f = functions.region('asia-northeast1');

// カウントアップする(export const ~~~　→ module.exports.~~~に変更)
module.exports.test = f.firestore.document('tests/{id}')
.onCreate(async (snap: FirebaseFirestore.DocumentSnapshot, context: functions.EventContext) => {
    await admin.firestore().collection('tests').doc('counter').set({
        count: admin.firestore.FieldValue.increment(1.0),
    }, { merge: true });
});
```
こういう感じで変更してあげると、Cold Start時でも、ファイル単位で読み込んで実行してくれるようになります。
1ファイル1関数で実装してあげると、Cold Start時、1関数ずつの読み込みになるのでレスポンスはさらに良くなるはずです。
ただ、反面、CommonJS構文であるrequire/module.exports構文を記述する必要があるため、ちょっと気持ち悪くなります。
(実装する際はES6のimport/exportは使わず、CommonJS構文で統一した方がいいかもしれません。

# regionは指定しておいた方が良い
Cloud functionsはデフォルトリージョンが「us-central1」となっており、結構遅いです。
東京に変更すると結構なレスポンス改善になるようですし、1行コードを追加するだけでokなので、最低限設定しておいた方が良いと思います。

```typescript:リージョンの変更(各ファイルの先頭に記述し、fで呼び出すようにする)
import * as functions from 'firebase-functions';

// region指定
const f = functions.region('asia-northeast1');
```

# おわりに
TypeScript楽しいぃいいい！
Firebase楽しいぃいいいい！
Firebaseは根底NoSQLなのでRDBMSとは思想が全く異なっており、触れば触るほど新しい知見が得られるので、RDBMS飽きた人にもオヌヌメです(´・ω・｀)b
あと安いし、サーバサイド実装なしで爆速実装できるのでプライベートエンジニアにもオヌヌメです(´・ω・｀)b

# 参考文献
[[Ginco Tech Blog]GincoにおけるCloud Functionsの利用とその高速化](https://tech.ginco.io/post/ginco-engineer-meetup-2018-cloud-functions/)
[[stack overflow]How do I structure Cloud Functions for Firebase to deploy multiple functions from multiple files?](https://stackoverflow.com/questions/43486278/how-do-i-structure-cloud-functions-for-firebase-to-deploy-multiple-functions-fro/44865340)
[[Qiita]exports と module.exports の違い](https://qiita.com/kuboon/items/cb1b532ac8a8959973b2)
[[Qiita]# CommonJS と ES6の import/export で迷うなら](https://qiita.com/rooooomania/items/4c999d93ae745e9d8657)
[[Qiita]Firebase Functionsを関数ごとにファイル分割 with TypeScript](https://qiita.com/nekomimi-daimao/items/eea046dca2ab669a7fa9)