前回に引き続き、プレゼンス管理の仕組みを作っていきます。
今回はDBをなんとかします。
イメージ
個々のユーザが検出した位置情報はDBに保存して一元管理できるようにしていきます。
また、数多ある検出されたiBeaconのうち、どのビーコンが位置ビーコンなのか定義する必要があります。
位置ビーコンは追加されたり変更されたりしそうなので、こちらもデータベースを作成した方が良さそうです。
さらに、ユーザ情報も一意でないと困るのでついでにデータベース化しましょう。
なお、DBへのアクセスはSQLクライアントから直接でも良いですが、RESTfulなAPIを作成したいと思います。
図で示したようにAPIはFastAPI、ユーザ位置情報はInfluxDB、ビーコンとユーザ情報はSQLiteを利用して作成します。
FastAPI
概要
FastAPIによると、
FastAPI は、Pythonの標準である型ヒントに基づいてPython 3.6 以降でAPI を構築するための、モダンで、高速(高パフォーマンス)な、Web フレームワークです。
とのことです。
FastAPIには色々特徴はありますが、SwaggerUIのドキュメントが自動で作られるのが便利でかっこいいです。
インストールとちょっとしたgetting start
まずはFastAPIそのものとASGIのインストールをします。
pip3 install uvicorn fastapi
インストールが終わったら早速動かしてみます。
APIのURLを http://*********/api
にしようと思っているので、そのインタフェースで"Hello World"してみましょう。
api.py
from fastapi import FastAPI app = FastAPI( docs_url='/api/docs', redoc_url='/api/redoc', openapi_url='/api/openapi.json', ) @app.get("/api") async def hello(): return {"message": "Hello World"} if __name__ == "__main__": import uvicorn uvicorn.run("api:app", host="0.0.0.0", port=1234, reload=True)
上記のファイルを実行すると、ポート1234で外部に公開されます。
試しにブラウザで http://*********:1234/api
を開くと、 hello()
の戻り値で指定した通り ”Hello World"が返ってきています。(私の環境はデフォルト設定なので`http://raspberrypi.local:1234/api
です)
さらに、 http://*********:1234/api/docs
を開くとドキュメントが作成されています。すごい!
実装
さて、ビーコン情報とユーザ情報のDBとDBへアクセスするAPIを作成していきます。
各テーブルのスキーマはこんな感じ。
ビーコン情報
Field | Type | Key |
---|---|---|
ID | int | Primary |
位置 | text | |
UUID | text | |
Major値 | int | |
Minor値 | int |
ユーザ情報
Field | Type | Key |
---|---|---|
ID | text | Primary |
名前 | text | |
詳細 | text |
ビーコン情報は UUID, Major, Minor の3つでユニークになるのでIDたりえるんですが、面倒なので自動インクリメントのIDをプライマリキーにしています。
これらの情報を読んだり書いたりするCRUDなAPIを作るわけですが、 FastAPIのチュートリアル SQL (Relational) Databases - FastAPI がそのまま参考になるのでチュートリアルと異なる点だけ解説します。
ビーコン情報もユーザ情報もデータ形式が違うだけで似たようなAPIを提供するため、ディレクトリを分けて下記のような構造にします。
locationとuserのAPIを分割して実装するわけです。
+-- App +--- api.py +--- database | +-- db.py +--- location | |- crud.py | |- model.py | |- router.py | |- schemas.py +--- user |- crud.py |- model.py |- router.py |- schemas.py
分割したAPIは チュートリアル Bigger Applications - Multiple Files - FastAPI によるとそれぞれ APIRounter を作成して FastAPIのインスタンスにつなげる事ができるようです。(FlaskのBlueprintみたいな感じですね!)
それぞれのrouter.pyのスケルトンとAPIRounterをFastAPIに繋げるスクリプトは下記の通り
location/router.py
from fastapi import APIRouter router = APIRouter(tags=['locations']) @router.get('/') async def read_all(): pass @router.get('/{id}') async def read(id: int): pass @router.post('/') async def create(): pass @router.put('/{id}') async def update(id: int): pass @router.delete('/{id}') async def delete(id: int): pass
user/router.py
from fastapi import APIRouter router = APIRouter(tags=['users']) @router.get('/') async def read_all(): pass @router.get('/{id}') async def read(id: str): pass @router.post('/{id}') async def create(id: str): pass @router.put('/{id}') async def update(id: str): pass @router.delete('/{id}') async def delete(id: str): pass
api.py
from fastapi import FastAPI from user.router import router as users from location.router import router as locations tags_metadata = [ { 'name': 'users', 'description': 'CRUD Interfaces for database of users.', }, { 'name': 'locations', 'description': 'CRUD Interfaces for database of location identificator.', } ] app = FastAPI( docs_url='/api/docs', redoc_url='/api/redoc', openapi_url='/api/openapi.json', openapi_tags=tags_metadata, ) app.include_router(locations, prefix='/api/location') app.include_router(users, prefix='/api/user') if __name__ == "__main__": import uvicorn uvicorn.run("api:app", host="0.0.0.0", port=1234, reload=True)
実行してみると、想定通り /api/location と /api/user のドキュメントができています。FastAPIにRouterがきちんとつながっているようです。
あとはチュートリアルを参考にAPIの中身を作っていくだけです。
データモデルとスキーマを先に示した構造に合わせて変更したスクリプトはこちらになります。
チュートリアルと同様、ORMにsqlalchemyを利用していますので、入ってない場合はインストールします。
pip3 install sqlalchemy
実行は先ほどと同様 api.py
を実行します。
ちゃんと動いてるかSwaggerUIから確認してみましょう。
/api/user/{id} のPOSTメソッドを開いて Try it out
をクリックするとSwaggerUIからAPIを叩くことができます。
試しに、 id=”user01" name="ichigo" description="starlight school" でユーザを作成してみます。
Execute
すると Code 204 で成功しました。
ちゃんと保存されているかも確認してみます。
ユーザのリストが返ってくるので追加されてますね。
ちゃんとスターライト学園のいちごさんが登録されてます。よかった。
InfluxDB
続いて ユーザの最新位置情報を保存するDBを準備します。
こちらはSQLiteではなくInfluxDBを利用します。
InfluxDBは時系列DBと呼ばれるタイプのDBで、その名の通り時刻情報を主キーとした構造となっています。
ログなど時系列に沿って蓄積されるデータを扱うためのDBなので、ユーザの最新位置を記録するDBとしてはもってこいなDBです。
さらに、データの保存期間(Retention Policy) を備えているので、今回のように昨日以前のデータが無用の場合わざわざクエリを発行しなくても勝手に消えてくれます。
何はともあれインストールです。
Install InfluxDB OSS | InfluxDB OSS 1.8 Documentation の通りだとなんだかうまくいかなかったので、下記のコマンドでインストールします。
curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add - echo "deb https://repos.influxdata.com/debian buster stable" | sudo tee /etc/apt/sources.list.d/influxdb.list sudo apt update sudo apt install influxdb
うまくインストールできていればsystemctrl status
で active となっているはずです。
CLIでInfluxDBに接続してデータベースを作成してみます。
test
という名前のデータベースを作成してみました。
また、RETENTION POLCYも設定してみます。
テスト用なので保存期間を1時間としてデフォルトのポリシーに設定します。後ほど行う登録のテスト後、1時間経ったらデータが消えていれば成功です。
CREATE RETENTION POLICY d1h ON test DURATION 1h REPLICATION 1 default
試しにデータを登録してみましょう。 試しとはいえ、本番と同じ形が望ましいのでデータ構造を決めます。 蓄積するデータはユーザ毎にあり、検出した位置ビーコンを特定する値をポストしたいのでUUIDとMajor,Minor値がフィールドとなります。
ということで key は id、Fieldはuuid, major, minor からなるデータを登録してみます。
InfluxDBはHTTPによるAPIが用意されているのでそちらを使ってみます。
curl -X POST 'http://localhost:8086/write?db=test' --data-binary 'sample,id=user01 uuid="xxxx",major=1,minor=2'
先ほど作成したDB、test
のMeasurement "sample"に対して user01の位置情報を登録しています。
きちんと登録されているかCLIで確認します。
SHOW MEASUREMENTS
をすると先ほど登録した"sample"ができています。
Measurement "sample" からSELECT * するクエリを投げてみると1項目追加されているようです。
InfluxDBはHTTPのAPIが用意されているのでこれ以上何かを準備しなくても良さそうです。 これでDB関連は準備完了ですかね?
思った以上に長文になったので今回はここまで。次回に続きます!