84
82

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.

ZOZOテクノロジーズ #5Advent Calendar 2019

Day 17

うわっ・・・FastAPIでGraphQLの構築、楽すぎ・・・?

Last updated at Posted at 2019-12-16

自己紹介

こんにちは,ZOZOテクノロジーズの内定者さっとです。
最近FastAPIをよく利用していますが、今回紹介するGraphQLやWebSocketがサポートされており、
便利すぎでは?と感じる今日この頃です。

※本記事はZOZOテクノロジーズ#5の17日目です.
昨日は@e_tyuboさんが「Git for Windowsでautocrlfの設定を間違えちゃった時の対応」という記事を出しました.
他にもZOZOテクノロジーズでは5つのアドベントカレンダーを毎日更新しています!

概要

FastAPIを使ってGraphQLをさくっと構築する方法を紹介したいと思います。

以下、本記事の構成です。

  • FastAPI?GraphQLとは?
  • FastAPI×GraphQLで楽々ハローワールド
  • SQLAlchemyを使ってDB接続

FastAPIとは

Python3.6以上で動作する高速なAPIフレームワークです。
https://fastapi.tiangolo.com/
2019年初頭頃、少し話題になったと思います。

特徴としては、Node.jsやGoと同等のパフォーマンス性能でかつFlaskライクな書き方で簡単にRESTful APIやGraphQL、WebSocketを構築することができます。ハッカソンなど時間が限られた開発で有効なフレームワークだと思います。

また、Swaggerが自動で生成されるため、動作チェックやドキュメント作成の時間が大幅に短縮されます。

GraphQLとは

Facebookによって、従来のRESTful APIの問題点を解決するために開発されたオープンソースの言語です。
https://graphql.org/

特徴としては、1つのエンドポイントを使用して、必要なデータを必要な分だけ1回のクエリで取得できるという点です。

この利点によって、従来のRESTful APIで必要なデータを取得するために、余計なデータまで付属してきたという状況を回避することができます。これにより、電力の消費やネットワークの帯域を抑えた通信が可能となりました。

Hello GraphQL

コードの説明は一旦おいておいて、動かしてみましょう

$ git clone https://github.com/sattosan/hello_fastapi_graphql.git

# コンテナをビルド&起動
$ docker-compose up -d --build

GraphQLの動作チェック

コンテナが起動次第、下記のリンクにアクセスしてみましょう
http://127.0.0.1:8000/graphql

そうすると、下記のようなWebユーザインタフェースが表示されると思います。
Screenshot from 2019-12-14 03-00-14.png

画像の通り、下記のクエリを左側に記述して、実行すると

{
  hello(name: "FastAPI")
}

右側にレスポンスが表示されることがわかるかと思います。

コードの説明

では、さっそくコードの中身を見ていきましょう。

動作環境

  • Ubuntu 18.04.3 および macOS Catalina 10.15.3
  • Python 3.8
  • FastAPI 0.45.0

ファイル構成

今回は、Pythonのいろいろなパッケージを使う関係で、Docker Composeを使って環境を構築しています。

.
├── api
│   └── main.py
├── docker
│   └── uvicorn
│       ├── Dockerfile
│       └── requirements.txt
└── docker-compose.yml

Docker Composeで環境構築

  • Composeファイル
./docker-compose.yml
version: "3"

services:
  # FastAPI
  api:
    container_name: "api"
    build: ./docker/uvicorn
    restart: always
    tty: true
    ports:
      - 8000:8000
    volumes:
      - ./api:/usr/src/api
  • FastAPIのコンテナを定義
./docker/uvicorn/Dockerfile
FROM python:3.8

WORKDIR /usr/src/api
ADD requirements.txt ./
# requirements.txtにリストされたパッケージをインストールする
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# FastAPIを8000ポートで待機
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]
  • 必要なPythonのパッケージを一気にインストールするためにリスト化したもの
./docker/uvicorn/requirements.txt
uvicorn
fastapi
graphene

FastAPIでGraphQLのエンドポイント作成

RESTful APIと違い、GraphQLで扱うエンドポイントは1つだけです。

今回は、FastAPIを使用して、エンドポイント/graphql上にGraphQLスキーマを公開するサーバと、クエリを簡単に実行するためのGraphiQLと呼ばれるWebユーザインタフェースを作成します。

ライブラリstarletteを使うことで、これらの機能を実現するためのサポートをしてくれます。

./api/main.py
import graphene
from fastapi import FastAPI
from starlette.graphql import GraphQLApp


# Grapheneを利用したGraphQLスキーマを作成する
class Query(graphene.ObjectType):
    # 引数nameを持つフィールドhelloを作成
    hello = graphene.String(name=graphene.String(default_value="stranger"))

    # フィールドhelloに対するユーザへ返すクエリレスポンスを定義
    def resolve_hello(self, info, name):
        return "Hello " + name


# FastAPIを利用するためのインスタンスを作成
app = FastAPI()
# GraphQLのエンドポイント
app.add_route("/graphql", GraphQLApp(schema=graphene.Schema(query=Query)))

SQLAlchemyを使ってDB接続

DBを用意して、GraphQLでデータを取得できるように既存ファイルを編集します。

DBはSQLiteで構築し、ORMモジュールであるSQLAlchemyを使ってデータを操作します。

完成コードはGitHubにあるので、すぐ試したい方は、こちらにアクセスしてください。

ファイルの追加&編集

変更後のファイル構成は、このようになっています。

.
├── api
│   ├── main.py    # 編集
│   ├── models.py  # 追加
│   ├── schema.py  # 追加
│   └── init_data.py # 追加
├── docker
│   └── uvicorn
│       ├── Dockerfile
│       └── requirements.txt # 編集
└── docker-compose.yml

パッケージの追加

FastAPIのコンテナでDBとデータのやり取りを行うために、
SQLAlchemyとGraphQLを扱うためのgraphene_sqlalchemyを追加します。

./docker/uvicorn/requirements.txt
uvicorn
fastapi
graphene
SQLAlchemy # 追加
graphene_sqlalchemy # 追加

テーブルモデルの定義

SQLiteと通信するためのdb_sessionを作成し、
DepartmentEmployeeテーブルを定義します。

./api/models.py
# flask_sqlalchemy/models.py
from sqlalchemy import *
from sqlalchemy.orm import (scoped_session, sessionmaker, relationship,
                            backref)
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///database.sqlite3', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))

Base = declarative_base()
# クエリを扱うために宣言
Base.query = db_session.query_property()


class Department(Base):
    __tablename__ = 'department'
    id = Column(Integer, primary_key=True)
    name = Column(String)


class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    hired_on = Column(DateTime, default=func.now())
    department_id = Column(Integer, ForeignKey('department.id'))
    department = relationship(
        Department,
        backref=backref('employees',
                        uselist=True,
                        cascade='delete,all'))

スキーマーの定義

特定の従業員情報や部署情報を取得するためのEmployeeDepartmentを定義します。

また、Queryとして、特定のノードを取得するためのnodeと、
すべての従業員情報や部署情報を取得するための、all_employees、all_departmentを提供しています。

./api/schema.py
import graphene
from graphene import relay
from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
from models import Department as DepartmentModel, Employee as EmployeeModel


class Department(SQLAlchemyObjectType):
    class Meta:
        model = DepartmentModel
        interfaces = (relay.Node, )


class DepartmentConnections(relay.Connection):
    class Meta:
        node = Department


class Employee(SQLAlchemyObjectType):
    class Meta:
        model = EmployeeModel
        interfaces = (relay.Node, )


class EmployeeConnections(relay.Connection):
    class Meta:
        node = Employee


class Query(graphene.ObjectType):
    node = relay.Node.Field()
    # デフォルトでは、ソートが有効化されている
    all_employees = SQLAlchemyConnectionField(EmployeeConnections)
    # ソートを無効化
    all_departments = SQLAlchemyConnectionField(DepartmentConnections, sort=None)


schema = graphene.Schema(query=Query)

FastAPIのコードを修正

定義したスキーマー及びクエリGraphQLAppに投げて扱えるようにします。
また、 シャットダウン時に、DBセッションを削除するイベントを追加します。

./api/main.py
from fastapi import FastAPI
from starlette.graphql import GraphQLApp

from models import db_session
from schema import schema

# FastAPIのインスタンスを作成
app = FastAPI()

# GraphQLを提供するためのエンドポイントを定義
app.add_route("/graphql", GraphQLApp(schema=schema))

# APIサーバシャットダウン時にDBセッションを削除
@app.on_event("shutdown")
def shutdown_event():
    db_session.remove()

テストデータの挿入

SQLite上で作成したテーブルに下記のコマンドを入力して、テストデータを挿入します。

$ docker-compose run --rm api python init_data.py

init_data.pyはテストデータをまとめたものです。

./api/init_data.py
# 初期データを流し込むためのスクリプト.以下を実行してTableにデータを流し込む
# docker-compose run --rm api python init_data.py
from models import engine, db_session, Base, Department, Employee
Base.metadata.create_all(bind=engine)

# Tableに流し込む初期データ
engineering = Department(name='Engineering')
db_session.add(engineering)
hr = Department(name='Human Resources')
db_session.add(hr)

peter = Employee(name='Peter', department=engineering)
db_session.add(peter)
roy = Employee(name='Roy', department=engineering)
db_session.add(roy)
tracy = Employee(name='Tracy', department=hr)
db_session.add(tracy)
db_session.commit()

コンテナの再構築

もろもろ変更したので、コンテナを再構築します。

$ docker-compose up -d --build

DBのデータをGraphQLでリクエストしてみる

下記のリンクにアクセスして、GraphQLでDBに挿入したデータを取得してみましょう。

このようなクエリを投げてみてください。

{
  allEmployees {
    edges {
      node {
        id
        name
        department {
          name
        }
      }
    }
  }
}

Screenshot from 2019-12-15 01-34-50.png

allEmployeesを使って、すべての従業員情報を取得することができました。

まとめ

今回は、FastAPIを使ってGraphQLの構築方法を紹介しました。
たった数行で構築することができ、とりあえず試してみようと思う方にとっては、
FastAPIはベストな選択だと思います。

明日は、@hmsnakrさんの記事です!お楽しみに!!

参考

https://fastapi.tiangolo.com/tutorial/graphql/
https://docs.graphene-python.org/projects/sqlalchemy/en/latest/tutorial/

84
82
2

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
84
82

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?