45
57

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

python×Cloud Functions×FirestoreでAPIを簡単に作ってみる Pt1

Last updated at Posted at 2019-07-03

概要

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コレクションからユーザ一覧を取得してみます。
FireShot Capture 001 - データ – Firestore – sample-project – Google Cloud Platform_ - console.cloud.google.com.png

肝心のソース。とりあえず、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」がデフォルトですが、詳細オプションから変更可能なので、気になる人は変えましょう。
少し待つとデプロイが完了して、関数が作成されます。
FireShot Capture 002 - Cloud Functions - sample-project - Google Cloud Platform_ - console.cloud.google.com.png

テスト

デプロイした関数をテストしてみます。コンソールからパラメータを指定せずに関数をテストしてみると、userコレクションのデータが取得できます。
FireShot Capture 003 - Cloud Functions - sample-project - Google Cloud Platform_ - console.cloud.google.com.png

また、「トリガー」タブから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でテスト

  • コンソールから条件を設定してのテスト

    • name="Alice"に一致するデータが返ってきます。
      FireShot Capture 005 - Cloud Functions - sample-project - Google Cloud Platform_ - console.cloud.google.com.png
  • curlでのテスト

  • GET/POSTのいずれでも、name="Bob"に一致するデータが返ってきます。

GET(クエリ文字列)
$ curl https://<リージョン>-<プロジェクトID>.cloudfunctions.net/getFilteredUser?name=Bob
{"users": [{"age": 21, "name": "Bob", "gender": "male"}]}
POST(json)
$ 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で実装していきます。

その他、コメントや指摘などいただけると幸いです。

45
57
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
57

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?