8
5

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 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

Fletのアプリをfly.ioへデプロイしてcloudflare workers&D1と連携

Last updated at Posted at 2023-06-22

前回、Fletの機能を実装を交えて、確認しました。今回はその続きです。

  1. Flet使うとPythonでWeb/Desktop/Mobileアプリ作れちゃうんだ ←前回の内容
  2. Flet Docs (controls編) + cloudflareのWorkersデプロイ(D1を添えて) ←今回ここです!
  3. 1と2の学習を経て、実践的なサイト構築予定(詳細未定です)

Fletの以下ドキュメントで気になる内容に対して、手を動かしてみました。しかし、レイアウトだけでは・・・ってところです。今回もcloudflareと連携させます。workersとD1との連携を含めた形で進めようと思いました。
どちらかというと、今回はcloudflareの割合が高いと思います!!

cloudflare WorkersとD1

前回の記事ではcloudflareのpagesを用いて、Fletで作成した簡単なアプリケーションのデプロイまで行いました。冒頭の話と重なりますが、今回はAPI(Workers)とデータベース(D1)に登場していただきます。

今回のゴールは、Workers(Build serverless applications)とD1(serverless SQL databases)の連携方法を理解し、Fletで作成した画面上でそれらを利用できるようにすることです。

※最初にD1のページに記載された注意書きについて共有します。

Open Alpha While in the Open Alpha period, there is a possibility of breaking changes. The Alpha is meant for testing purposes and using it for production traffic is not recommended.

D1は現在オープン アルファ版であり、本番データおよびトラフィックには推奨されません。

実装準備

以下ページの手順通りで、デプロイまで完了でいます。

コマンド自体を覚える必要は特にないので、都度コピペすればいいです。
今回は内容を整理するために、各コマンド実行後に、ローカル環境とクラウド環境上でそれぞれどういった状態になっているのか、図を載せることにしました。

Workersのプロジェクト作成

wrangler init d1-tutorial -y

ローカル環境(PCのイラスト)にWorkersの土台を作成します。Workersの画像を少し薄くしていますが、ローカル≒ダミー、という私の感覚でこのようにしました。
image.png
DB作成

wrangler d1 create <DATABASE_NAME>

リモート環境に、データベースだけを作成します。(テーブルなどはないです)
image.png
この段階でフォルダは以下のようになっています。
image.png
スキーマ定義と初期データ投入
プロジェクトのルートディレクトリにschema.sqlを作成して、必要なテーブルの作成や初期データ投入などを実施します。--localをつけると、ローカル環境だけにスキーマを適用します。schema.sqlの内容は、チュートリアル内に記載されている内容をそのまま記載してます。

npx wrangler d1 execute sample-d1 --local --file=./schema.sql

データが投入されたか確認するために、コマンドパレットを開きます。(VsCodeで作業してます)
image.png
選択肢に出てきますので、選択します。(直前に記載したコマンドを実行で、自動的に生成されます。)
image.png
Show Tableをクリックすれば、データを確認できます。(初期データは4件あります)
image.png
ここまでのイメージが以下の通りです。これでローカル環境で動作確認できる状態が整いました。
image.png

D1構築時のファイル確認

wrangler.tomlの修正
fly.ioを使うようになってからtomlファイルを触るようになりましたが、XXX.iniXXX.yamlなどの拡張子でもある設定ファイルのようなもの、という認識で作業してます。

wrangler.toml
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "<DATABASE_NAME>"
database_id = "<unique-ID-for-your-database>"

database_idはcloudflareのマネジメントコンソールから取得します。
以下画像の<unique-ID-for-your-database>部分にIDが記載されてます。コピーして貼り付けます。
image.png
src/index.tsを確認
特定のPathへのアクセス時はXXXを、それ以外のPathの場合はYYYをレスポンスに設定する構造になってます。

実装以外の話ですが、Workers以外の何と連携できるのかわかるコメントは助かります。公式ドキュメントを辿れば分かりますが、試しに触ってみた段階で分かるのはうれしいです。

index.ts
    export interface Env {
    	// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
    	// MY_KV_NAMESPACE: KVNamespace;
    	//
    	// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
    	// MY_DURABLE_OBJECT: DurableObjectNamespace;
    	//
    	// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
    	// MY_BUCKET: R2Bucket;
    	//
    	// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
    	// MY_SERVICE: Fetcher;
    	MY_SAMPLE_DB: D1Database;
    }

実装開始

以下の機能があると、WorkersとD1を利用したアプリケーションかな~といったところです。

  • ルートパスは読み込み時にデータ取得(GETリクエスト)を行い、全件表示
  • データ登録のためのフォームを用意して、POSTリクエスト
  • データ登録時、必要項目に値が設定されていない場合は、送信時ボタンを非活性
  • DBへの接続に失敗した場合は、画面にメッセージ表示

Layout検討

サンプルとしてTrolliが用意されていたので、それをベースにしました。

Trolliのサンプルは、今回実装予定のアプリケーションより機能が多いです。サンプルから不要と思う実装を削除する過程を挟んで作成を進めました。この作業方法の場合、以下画像のように何度もエラーが表示されます。それでも、このエラー過程を挟むことで、実装に対する理解が深めることができるので、やってよかったと思いました。

何かエラーがある場合の画面表示形式
image.png

PythonでPostリクエストの実装

Fletのアプリケーションからリクエストを投げるための実装方法確認です。
Pythonのrequestsを用いたPOST通信の実装方法(Bobyの設定)

CloudflareのWorkersでD1からデータ取得・操作

こちらは、WorkersからD1に対する指示をする方法を確認しました。

Honoを使ったルーティング定義というものありました。

ローカル開発

workersとD1がローカルだけで完全再現できることが、本当に開発を行いやすくしてくれます。
Fletのコードをローカルで実装後、ローカルで挙動を確認して、問題がなさそう!となったら、デプロイすればOKです。

以下コマンドでWorkersをローカル環境で起動します。(wrangler2.19.0`の場合)

npx wrangler dev --local --persist

真ん中あたりの枠で囲まれた部分にある通り、キーボードの入力値によって、挙動が変わるようです。動かすだけであれば、[b] open a browserになるかと。。。
image.png
127.0.0.1なので、自身のPC(ローカル)です!
image.png
wrangler 3.1.1による動作確認
※wrangler 2.19.0使用時に、wrangler 3.1.1の使用を推奨されたためバージョンを変えてみました。以下コマンドでローカルDBの起動ができました。

npx wrangler dev --local

また、コンソールを見ると・・・

▲ [WARNING] --local is no longer required and will be removed in a future version.

--localはもういらない、とのことです。以下コマンドで起動できました。

npx wrangler dev

1日のリクエスト制限を大切に

実際のWorkersのURLに対してリクエストを実施すると、バンドルリクエスト数が加算されます。
本日のということなので、10万リクエスト/日は、かなりの太っ腹な数ですが、節約できるならしたい、はずです!!Workersをローカルで起動して、http://127.0.0.1:8787にアクセスする場合は、このバンドルリクエスト数がカウントされません。
image.png

実装のワンシーン(Fletアプリからcloudflare Workersへの疎通確認)

画像のinsert_data_to_customerをクリックで、処理が走ります
image.png

上記画像はあくまでも挙動確認のための簡単なアプリケーションです。Fletの挙動理解のためでもありますが、いきなり本格的に作り上げるとミスったときに、戻すのが大変でやる気が削がれるので・・・この方法をとりました。

Fletのアプリケーション側のコードでPOSTリクエストを行うメソッド定義
image.png
cloudflare Workers側では、Fletアプリケーション側からのリクエストのBodyが受け取れているか確認
image.png
Flet側のコンソール
200(HTTPレスポンス)なので、正常にリクエストができたことが分かります。
image.png
cloudflare Workers側のコンソール
データ受け取れてますね。
image.png

ローカルDBとリモートDB

ローカルで開発が完結していることを、データ数で確認してみました。以下画像はWorkersのコード側でデータ追加の実装が完成した直後のテーブルの状態です。(初期データが4件あったので、2件追加)
image.png
このときに、リモートの状態(cloudflareのマネジメントコンソール)のデータ数を確認すると、4件で初期の状態から変わっていません。きちんと分かれてて、開発体験良しです~~
image.png

完成したアプリケーションの画面と実装

各画面と挙動

トップ画面
image.png
ユーザ追加画面
image.png
image.png
作成が成功したメッセージが表示されてから1秒経過すると、トップ画面(一覧画面)に戻る処理を入れています。一覧画面で、先ほどの入力値が追加されてますね!
image.png
Workers側のサーバを起動しない・間違ったURLに対するリクエストを実行したときの画面
image.png
image.png

実装を交えて(最後にリポジトリ情報を載せていますので、そちらを参照いただく形でもOK!です)

cloudflareの機能が素晴らしいことに感動して、Fletの話を忘れそうになります。

上記完成物の中で、以下に関する実装を取り上げます。
アクセスしたとき、以下の画面です。
submitを非活性にして、送信処理が実行されないようにしています。
image.png
disabled=Trueとしています。この記述によって、画面表示時はボタンを非活性状態にできます。

# 追加処理のボタン
submit_button = ft.ElevatedButton(
    disabled=True,
    text="Submit", on_click=button_clicked)

次に、テキストフィールドに値を入力したときの挙動を見てみます。
image.png
各テキストフィールド(Company Name,Contract Name)それぞれに対して、on_changeを定義しています。
テキストの内容が更新されると、on_change=XXXのXXXが実行されます。今回はtextfield_changeメソッドを定義しています。Company NameまたはContract Nameが空文字であれば、ボタンを非活性とする実装です。
両方の値が空文字でなければ、非活性状態が解除されます。
またdisabledのステータスを更新するため、最後には、self.page.update()を実行します。このupdate処理は実装全体でも登場することがしばしばあります。

# テキストフィールド内容更新時の処理
def textfield_change(e):
    if company_name_textfield.value == "" or contract_name_textfield.value == "":
        submit_button.disabled = True
    else:
        submit_button.disabled = False
    self.page.update()

# テキストフィールド
company_name_textfield = ft.TextField(
    label="Company Name", on_change=textfield_change)
contract_name_textfield = ft.TextField(
    label="Contract Name", on_change=textfield_change)

両方入力できていれば、Submitが活性状態になります。
image.png
送信しました。メッセージ表示がされたと同時に、テキストフィールドが空っぽにあり、ボタンも非活性状態に戻りました。
image.png
登録成功・失敗時のメッセージの要素について、visible=Falseとしています。画面表示したタイミングでは非表示とするためですね。try-exceptまではPOSTリクエスト情報を作成しています。

リクエストを実行時には・・以下を満たす必要がありそうです。

  • 登録成功
    • 登録できたことを伝える
    • 画面をアクセスしたときの状態に戻す
  • 登録失敗
    • 登録できなかったことを伝える

登録の結果を伝えるためのメッセージ表示については、XXXX.visible = Trueを使用します。
コードのコメントでも記載していますが、アクセスしたときの状態に戻すために、以下の処理が必要です。

  • テキストフィールド初期化:XXX.value = ""
  • ボタンを日活性に:submit_button.disabled = True

そして忘れずにpage.update()を実行します。

# customer登録失敗時のメッセージ表示
msg_failed_add_customer = Row(
    [
        ft.Text("Sorry Failed to send new customer, Retry Access few minutes Later", 
        size=24, color=ft.colors.RED_500
                )
    ],
    visible=False
)
# customer登録成功時のメッセージ表示
msg_success_add_customer = Row(
    [
        ft.Text("Success to create new customer!",
                size=24, color=ft.colors.BLUE_900,
                )
    ],
    visible=False
)

# 送信(Customer登録処理)
def button_clicked(e):
    if (len(company_name_textfield.value) == 0 or len(contract_name_textfield.value) == 0):
        page.update()
        return
    # POSTリクエスト
    url = os.getenv('WORKERS_URL')
    data = {
        'CompanyName': company_name_textfield.value,
        'ContactName': contract_name_textfield.value,
    }
    data_encode = json.dumps(data)
    try:
        response = requests.post(url, data=data_encode)
        # テキストフィールド初期化
        company_name_textfield.value = ""
        contract_name_textfield.value = ""
        # 画面の状態更新
        submit_button.disabled = True
        msg_failed_add_customer.visible = False
        msg_success_add_customer.visible = True
        page.update()
    except:
        msg_failed_add_customer.visible = True
        page.update()

# 追加処理のボタン(再掲)
submit_button = ft.ElevatedButton(
    disabled=True,
    text="Submit", on_click=button_clicked)

最後は余談ですが、FletがそもそもFlutterを意識されているので、Rowの実装を見ていると、Flutterに近いものがあると感じました。

今回のFletの実装

image.png

Flutterの実装(過去の実装からテキトーに取り出しました)

image.png

WorkersとD1のデプロイ

テーブル定義のデプロイ
これまでローカルだけで定義していたスキーマを、リモートに対して適用します。
これまでローカルで使用していたテーブルのデータは影響を受けず、schema.sqlに記述した定義を今度はリモート環境に適用します。

npx wrangler d1 execute sample-d1 --file=./schema.sql

cloudflare上にD1にテーブルが作成されました。
image.png

Workersの処理を記述したtsファイル自体のデプロイ
GET/POSTリクエストを実装できたので、こちらもデプロイします。

npx wrangler publish

コンソールの出力

Your worker has access to the following bindings:
- D1 Databases:
  - MY_SAMPLE_DB: {projectname} (b827dba5-47aa-427f-886e-d815287ca699)
Total Upload: 7.18 KiB / gzip: 2.27 KiB
Uploaded {projectname} (0.89 sec)
Published {projectname} (0.28 sec)
  https://{projectname}.{github-username}.workers.dev
Current Deployment ID: 7bd71725-ac94-401c-bd37-274b697b83f7

cloudflare上にWorkerが構築されました。
image.png

fletのアプリケーションをpages上にデプロイ(githubのリポジトリ経由)
cloudflare上にpagesが構築されました。
image.png

よし最終確認だ!と思って、デプロイしたアプリケーションの動作確認を始めると・・・

Can't connect to HTTPS URL because the SSL module is not available.

読んでみると、どうやら・・・Pyodideが関係している???

前回の記事でcloudflare pagesにデプロイしたアプリケーションは大した機能がないので、何も起きませんでした。ですが今回のように、いくつか機能を加えてライブラリも追加するようになると、不具合が発生するケースもあることが確認できました。
Fletの公式ドキュメントにも、以下のようにFlet static consとして以下の記述があります。

Limited Python compatibility - not every program that works with native Python can be run with Pyodide.

Pyodideで動かした場合に、Pythonで動いても機能しない部分がある、といった訳でしょうか。

ということで・・・
fletのアプリケーションをcloudflare pagesfly.io上にデプロイに変更します!!

image.png
公式ドキュメントに沿って、進めます。

fly launchコマンド実行
自動生成されるProcfileというファイルがあります。そこに、起動時のコマンドを設定します。
デフォルトは以下のようになっていました。(1行目はコメントです。)

# Modify this Procfile to fit your needs
web: gunicorn server:app

今回は、通常にPythonコマンドで動作確認もしていたので、それを使います。

# Modify this Procfile to fit your needs
web: python main.py

fly deployコマンドでデプロイして、画面から確認してみます。
きちんとデータが表示できました・・・
image.png

Fletのデザインについて学習した

cloudflareとそのためのアプリケーションの方が内容盛りだくさんで、かつ私自身としても共有したい内容だったので、Fletの内容確認は後ろに回しました。
前回と同様ですが全ては触っていません。実装を重ねる中で適宜調べるので、ざっくり見て気になった項目を確認しました。詳しい情報は公式ドキュメントをみていただきたいと思います。
また公式からデモページが用意されているので、こちらで動作確認がすぐに行えます。

Appbar

参考にしている、このドキュメントにもおそらく使われているパーツだと思います。
image.png
Appbarが画面上部ですが、横・下部に同様の実装を、navigationrail,navigationbarで説明していると思います。(省略)

ResponsiveRow

Responsiveという単語で想像つく部分はありましたが、実装方法を見てみました。
bootstrapやtailwindcsを経験された方ですと、レイアウトの話で12という数字はなじみがあるかと思います。画面幅がどれくらいであれば、どのように表示する、といった制御を行います。
image.png

Image

画像が表示できないのは困るので調べました。CORSの問題があったりして、画像が表示できないケースもある。
サンプルコードにあったhttps://picsum.photos/200/200の画像を表示する場合に、ブラウザで起動するとCORSエラーになるので、ft.appの引数にweb_renderer="html"を設定したりして、対応する必要もありそうです。

progressbar

メモ:処理に時間を要して、ユーザに待ってもらうときに、使うと思った。
これはFletに限った話ではないが、N秒でプログレスの最後まで到達するのか制御できるので、使いやすい。

image.png

IconButton

メモ:各商品ページやアカウントページなどでは、使いそうかな~

image.png

AlertDialog

メモ:ボタンをクリックしたとき、何か警告などを表示するときに使いそうかな~、

image.png

GestureDetector

Flutterでもお世話になる機会が多かったので、こちらでも触れておきます。
何か画面でイベント(入力、ボタンのクリック)が発生したときに、どんな処理を

Footer

アプリケーションでフッターはよく見ますが、そのサンプルをみてみました。
image.png

実装について

今後

Fletの基礎部分と、cloudflareのWorkdersとD1の基礎について、理解することができました。
次回以降は少し作成まで間が空いてしまうかもしれませんが、もう少し実践的な内容に挑戦してみようと考えています。

8
5
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
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?