概要
GCPのCloud Functionsを使って、FirebaseのFirestoreからデータを取得するAPIを作る機会があったのですが、非常に簡単に作れたのでその時やったことをまとめます。
(本当はCloud Functions for Firebaseを使いたかったのですが、慣れているpythonには未対応のため、GCPのCloud Functionsで作ってみることに。)
Pt1では、Firestoreのデータを取得するシンプルなAPIをGCPのコンソールから作成します。
Pt2以降では、ローカルからのテスト、デプロイの方法をまとめます。
開発環境・前提
- 開発環境
- MacOS
- python3.7
- pycharm
- 前提知識
- Firestoreをコンソールから作成済み
- Cloud FunctionsのHello World関数を作ったことはある
※開発環境はPt2以降で使います。
プロジェクトの作成
FirebaseまたはGCPのコンソールから画面の指示に従うままに作成できます。
2つの連携について言えば、Firebaseのプロジェクトから先に作成した場合、GCPコンソールの「プロジェクトの選択>すべて」のタブからFirebaseで作成したプロジェクトを選択すれば、連携が完了です。
Firestoreのデータを取得する関数を書く
前提となるデータベースはこんな感じで、userコレクションからユーザ一覧を取得してみます。
肝心のソース。とりあえず、Cloud Functionsのコンソールから直接編集してみます。
import json
from google.cloud import firestore
def get_user(request):
db = firestore.Client()
docs = db.collection('user').get()
users_list = []
for doc in docs:
users_list.append(doc.to_dict())
return_json = json.dumps({"users": users_list}, ensure_ascii=False)
return return_json
- 簡単にソースを解説
- 引数であるrequestはHTTPのパラメータ等を指定しますが、このソースでは使ってません。とりあえず全データ取ってきます
- userコレクションから取得したdocsをforで回して、docの
to_dict()
メソッドでdictにしています。 - json.dumpsで
ensure_ascii=False
を指定しないと、日本語のデータが混ざったときにエスケープされた値が返されます。
デプロイ前の準備
Firestoreからデータを取得するためのパッケージであるgoogle-cloud-firestoreは、Cloud Functionsのデフォルトには含まれません。
そのため、「ソース>requirements.txt」タブから追記する必要があります。
# Function dependencies, for example:
# package>=version
google-cloud-firestore==1.2.0
Cloud Functionsであらかじめインストールされるパッケージは公式に載ってます。
Python での依存関係の指定 - あらかじめインストールされるパッケージ
デプロイ
Cloud Functionsのコンソールから「実行する関数」に関数名(get_user)を指定してデプロイします。リージョンは「us-central1」がデフォルトですが、詳細オプションから変更可能なので、気になる人は変えましょう。
少し待つとデプロイが完了して、関数が作成されます。
テスト
デプロイした関数をテストしてみます。コンソールからパラメータを指定せずに関数をテストしてみると、userコレクションのデータが取得できます。
また、「トリガー」タブからURIが確認できますので、curlでの確認も可能です。
$ curl https://<リージョン>-<プロジェクトID>.cloudfunctions.net/getUsers
{"users": [{"gender": "female", "name": "Alice", "age": 19}, {"age": 21, "gender": "male", "name": "Bob"}]}
ひとまず、Firestoreからのデータ取得はできました。
ただ、これだとコレクションの全データを返してしまうので、引数であるrequestを使って取得するデータの条件を指定できるようにします。
パラメータの条件に一致するデータを取得する
前提として、関数の引数となるrequestは、FlaskのRequestオブジェクトです。
そのため、クラス変数やメソッドはFlaskの公式ドキュメントで確認できます。
Flask - API
例えば、request.method
と書けばGETやPOSTが文字列として取得できるので、条件分岐に使うことも可能です。
APIでリクエストされたパラメータもこのオブジェクトから取得します。
というわけで早速ソースです。
import json
from google.cloud import firestore
def get_filtered_user(request):
# リクエスト本文からjsonを取得
request_json = request.get_json()
# クエリ文字列を取得
if request.args and 'name' in request.args:
request_name = request.args.get('name')
# jsonから条件を取得
elif request_json and 'name' in request_json:
request_name = request_json['name']
else:
request_name = ''
db = firestore.Client()
# whereでdocument内の条件に一致するデータを取得
query = db.collection('user').where('name', '==', request_name)
docs = query.get()
users_list = []
for doc in docs:
users_list.append(doc.to_dict())
return_json = json.dumps({"users": users_list}, ensure_ascii=False)
return return_json
- コメントが入ってるところが主なソースの修正箇所です。
- オブジェクトからパラメータを取得
- リクエストされた内容をwhereで条件にしてFirestoreにアクセス
-
where name = "リクエストされた名前"
の条件に一致するdocumentをリターンします。
新しい関数としてこのソースをデプロイした後、テストしてみます。
コンソールとcurlでテスト
$ curl https://<リージョン>-<プロジェクトID>.cloudfunctions.net/getFilteredUser?name=Bob
{"users": [{"age": 21, "name": "Bob", "gender": "male"}]}
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "Bob"}' https://<リージョン>-<プロジェクトID>.cloudfunctions.net/getFilteredUser
{"users": [{"age": 21, "gender": "male", "name": "Bob"}]}
テストとデプロイ方法の改善(次回)
Cloud FuctionsとFirestoreを使った簡単なAPIはできました。
とはいえ、いちいちコンソールからデプロイして動作確認するのは非常に面倒です。
なので、ローカルで気軽にテストしたり、ローカルからデプロイしたりを、Pt2以降では試してみます。
ところで、APIを公開で作成した場合、URIを知っていれば誰からでもアクセス可能なので、気になる人はデプロイした関数を消しておきましょう。
参考文献
以下の記事を参考にさせていただきました。
Google Cloud FunctionsでPythonを利用してみた(Beta利用)
Cloud FirestoreのデータをPythonで取得する
余談
ここまで書いてなんですが、モバイルアプリではCloud Functions for Firebaseを使ったほうが便利とも公式で書かれています。(使いたかったのはこれが理由...)
Javascriptが得意な方はこちらを使った方が良いかもしれません。
Google Cloud Functions と Firebase
とはいえ、pythonしか書けない人なので、引き続きGCPで実装していきます。
その他、コメントや指摘などいただけると幸いです。