Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

Flutterだけやってるのが怖くなってきた話

2年半ぐらいFlutterをやっているし、今後もやっていくのですけれど、初学の頃に比べると刺激がなくなってきた。 「やらなければできない」ことが減っていることが怖い。

UIの実装ができれば、悩みどころは状態管理・Widgetの初期化と後処理・デザインシステムの3つ。RiverpodとHooksの使い方が固まってきたので、悩むこともなくなった。デザインシステムはThemeExtensionを使うのが簡単で良さそうだ。

Flutter SDKの開発者体験のスタンダードなレベルが高すぎるので、Dartが3になってFlutterも4や5になるんでしょうけど、革新的なコードベースの変化というか、今までの書き味がガラッと変わるような変化が想像できない。1.0の頃とWidgetの設計は全然変わってない。更に簡便な書き味が扱いやすいAPIWidgetがリリースされる可能性はあるけど、Vue2 -> 3ほどの変化はないと思う。

コードの書き方についても、Lintで潰せる部分を除けば、Widgetの分割と初期化方法ぐらいじゃないですかね。RiverpodとHooks使っていたら、必然的にパターンが決まる。Riverpod前提だと、Hooksを使うケースってuseEffect / useStateぐらい。9割がuseEffectかな。ローカルなデータとグローバルなデータを共存して、Widgetをまたいでデータが管理できるRiverpodに抱かれるのが楽。

Widget単体のテストを通せるようにクラスを分けて(ヘルパーメソッドをなるべく使わない)、RiverpodでデータとUIが分離できていれば、捨てやすくなる。捨てやすいコードがきれいなコード。自分の書いたコードを捨てて再構築する過程を見て、ますますそう感じた。

Flutterを第一言語にするのは今年まで、来年以降はTypeScriptを第一言語にして、React/Vue3 を使ったフロントエンドの経験値を上げていくぞ。

ビジネスロジックのAPIをオーケストレーションするDSLをどう作るのか

早くこれになりたい。本当にそう思う。

ビジネスロジックAPI群とそれを駆動するDSLを駆使したプラグインの塊というのは、こういう感じのYAMLが作られて、DSLで呼び出し順やパラメーターを指定すると、ビジネスロジック自体が生成され、あとはそれを適宜順番に流すみたいなやつをイメージしている。

qiita.com

func: print
args:
    # argsは配列として記述
    - Hello, World!
    - Goodbye!
import yaml

# DSLから呼び出し可能な関数を列挙
func_dict = {'print': print}

with open('hello1.yml') as yaml_file:
    dsl = yaml.load(yaml_file)
    if 'func' in dsl and 'args' in dsl:
        func_name = dsl['func']
        if func_name in func_dict:
            func = func_dict[func_name]
            func(*dsl['args'])

見積を登録するという処理があったとすると、単純なDBへのUPSERT(明細型UIの場合、変更時に変更前の明細行が消えることがあるので、DELETE→INSERT)だけが関数として定義されているのがサーバーサイドの基本。ここで内部で色んなテーブルのUPDATEが走ってしまうとキツイので、イミュータブルなデータモデルにDB設計を可能な限り行う。状態の変更はテーブルのINSERTとDELETEで賄う。DB書き込みは単純なのが一番だ。在庫だけはどうしようもない。引当と出荷の変更が入ったら変えざるを得ない。

データを登録してから、色んな要望が出てくることは予想される。Slackに飛ばしたい、ワークフローを回したい、PDFを出力したい、送料を算出したい、Cubic Meterを元に総重量を出したい、任意のExcelで出力して委託倉庫にデータ投げたい、KintoneのようなPaaSのAPIを叩きたい等。これら一つ一つが 関数として定義されていて、多分それがビジネスロジックAPI群のこと。モノの輸出入をする場合、運賃のベースレートとなるCBMが必要になることが多い。タンカーで運ぶ賃料が変わるので。

そのAPIを叩くタスクランナーとしてのDSLがあって、beforeUpsert afterUpSert みたいなタスクを定義して実行できると強そう。このタスクランナーを作るAPIが別途あって、それがFirestoreあたりにしれっと保存されている感じ。顧客サイドは管理画面でチェックをポチポチ、行を追加するだけでこの辺がカスタマイズできるとベストだな。

version: '3'

tasks:
  beforeUpsert:
    calc_shipping_charge:
      - {{.ZIPCODE}}
      - {{.WEIGHT}}
  afterUpsert:
    webhook:
      - {{.WEBHOOK_URL}}

全テナントに共通でもつデータと、顧客属性に応じてカスタマイズして持つデータがある。設計としては以下のようになる。共通属性と可変属性、可変属性は属性のキーとタイプ、中身は属性情報に持つ。WordPressでよく見た構成だな。弱点としては、属性情報を保存するテーブルのカラムは文字列にしかできない点かな。1:1のJOIN。

マルチテナント・アーキテクチャ - @IT https://atmarkit.itmedia.co.jp/fdotnet/bookpreview/azureoverview_0301/azureoverview_0301_04.gif

TypeScriptであれば、必須情報と属性情報を型演算で和集合してそいつをPrismaに食わせることができるとコードはスッキリするが。

まぁ初回としてはこんなもんかな。1年かかるなこれ作り上げるのに、今の仕事を抱えながらだと...

令和のフルスタックWebフレームワークは、T3 Stackじゃないかな〜

qiita.com

ここに書いてあるBeforeのコードに強い危機感を覚えている。あそこまでこんがらがってはいないけど、jQueryとテンプレートエンジンでお茶を濁してバックエンドとフロントエンドの境界が曖昧になり、つらみがある点は自分も同じなので。

RailsフルスタックWebフレームワークの扉が開き、様々な言語でRailsインスパイアなWebフレームワークが作られ、jQueryと共に成長してきたはいいけど、UIの表現力が乏しいのでフロントエンド周辺がカオスになってしまう。すごくよくある話だと思う。

この状況だと、フロントエンドがどんどんバックエンドを侵食すべきで、フロントが書ける人間がバックエンドを書けばいい。で、TypeScriptで統一しちゃって、メンバーがフロント・バックの両方を書けるようになるのが一番いい気がしている。なので、T3 Stackに大いに可能性を感じている。

今まで(Rails,Cake.Django等)のフルスタックなWebフレームワークは、MVCのMCがメイン。Vが弱くHTMLを吐き出すテンプレートエンジンがある程度で、UIの状態管理がフレームワーク側でできないので、jQueryでカオス(煩雑で似たようなコード)を多く生み出してしまう。UIに関する変更コストって結構高いんだよね。

令和のフルスタックは、Cを意識しなくて良くなる。エンドポイントが自動で生えるから。tRPCとか使うと。VとMがスキーマを共有してつながり、Viewは宣言的にスッキリと作れ単体でテスト可能になる。いいじゃん、これで。この開発体験で気持ちよい。

TypeScriptのエコシステムはどんどん拡大している。ReactからZodでバリデーション決めてtRPC叩いてORMまで一気通貫。T3 Stackがまさにそれ。

3年後ぐらいにTypeScriptできるエンジニアの市場価値がもっと上がる!はず・・・!

T3 Stackすげえ。2023年はこれを追いかけるわ。

create.t3.gg

ちらっとTodoアプリを作ってみたけど、これが令和のフルスタックWebフレームワークなのかと驚いた。特に tRPC がすごい。どういう理屈なのかわからないけど、フロントとバックをシームレスにつなげてくれる。APIのエンドポイントを書かなくていい(自動で作ってくれるのだろう)という体験は初めてだった。

Webシステムのつらみの多くはフロントとバックエンドがつながってないこと。CakePHP+jQuery製UIをReactでシンプルにした件 - Qiitaが典型例。状態管理やUIの再構築を中途半端にjQueryPHPのテンプレートエンジンでやると「おお、もう」となる。それが嫌でReactに手を出したらNext.jsが流行りだして、T3 Stackが出てきた。

Node.jsのおかげで、JavaScriptはクライアントからサーバーサイドまで実装できるようになり、TypeScriptで最強の型演算ができるようになって、フロントとバックエンドが真につながったのかも。それが2022年なのかもしれん。フロントエンド出来る人がバックのコード書けるのが一番生産性高いじゃん。

Pythonより面白いわ。TypeScriptがんばろっと!!

イミュータブルの次は何が来るのかな

ここ数年のソフトウェア開発の大きな傾向として「イミュータブル(不変)」というキーワードがあると思う。

インフラはIaCが当たり前になった。IaCは、インフラのイミュータブル化。ChefやAnsibleが10年ぐらい前に出てきてIaCのムーブメントがあって、インフラはイミュータブルになった。作ったインフラを都度変更するのでなく、スクリプトを書いてリビルドすれば最新化されるので、不変という意味。フレッシュな環境が持ち運べるようになるなんて、ホンマに素晴らしい。

フロントエンドはReact/Vue/Flutterに代表される、const/finalな宣言的UI。特に関数が宣言的になる(関数型プログラミング)とフロントエンドは相性が良い。サーバーサイドもなるべくconst/finalが良いし、多分ほとんどの変数はfinalになるでしょう。イミュータブルでないコードを書くとLintにぶち殺すって言われる時代だし。

自分はPythonの書き味が好きだけど、結局人類には型が必要なんだよね。

イミュータブルなプログラミングをするためには、型で表現できないとなんにもならないので。コンパイラがチェックできないし。昨今流行している T3 Stackの肝は、データベース、サーバーサイド、フロントエンドでスキーマ(型)を共有できることだよね。そこに辿り着くためには Stackの言葉にあるように、色んなライブラリを積み上げる必要があるわけなんだけど。

オブジェクトという単位でプログラミングを行うことから大きなパラダイムシフトが起こるような気はしない。RDBに変わるデータ演算の仕組みが無いのと同じで、それより合理的な仕組みがなさそう。関数型プログラミングでもオブジェクトは必要だし。

プログラミングの表現としてはオブジェクトを宣言的に使う感じになっていって、webAssemblyの勃興によりブラウザがよりネイティブアプリに近づいてくのかな。

WTFormsで GETで飛んできたパラメータを捕捉する

ちょっとハマった。結論から言うとこれで動いた。

@app.route("aaa")
def user_summary():
    form = UserForm(request.args)

request.argsを引数に入れると、フィールドの変数と同じ名前のパラメータのデータを紐付けてくれる。 この引数が入っていないと、フィールドのdata変数が全部 Noneになってしまう!

class UserForm(FlaskForm):
   keyword = StringField("キーワード")

HTMLはこんな感じ。

  <form action=" method="get">
        <div class="row g-1 inline">
            <div class="col-auto">
                {{form.keyword.label}}
            </div>
            <div class="col-auto">
                {{ form.keyword(class_="form-control")}}
            </div>
            <div class="col-auto">
              <input type="submit" value="検索" class="btn btn-primary" class="form-control" />
            </div>
        </div>
    </form>

Cloud Runのアイドル時間課金がすごく大きくなってしまった話

今月の半ばから、VPS/Herokuを卒業して、全てGCPに移してCloudSQL x Cloud Runに切り替えた。

BtoB向けのシステムをやっている関係上、営業時間がすぎるとほとんどアクセスされない。CloudRunの最小インスタンスを1にしておくと、アイドル時間にもメモリとCPUがAllocateされるため、課金対象になることに今更気づく。

アイドルの課金額が実際にリクエストを処理している課金額より圧倒的に高くなってしまい、これは・・・と。

  • 最小インスタンスをゼロに(これでアイドルしてるインスタンスが消えるはず)
  • 営業時間(*/30 8-19 * * 1-5)内にヘルスチェックするだけのスケジューラーを仕込んだ。

20時以降にダウンタイムが発生した所で、多分2〜3秒だし。

そんな所で。

2022.12.26 追記

最小インスタンスをゼロにしてアイドル時間を減らしたら、課金額が大幅に減りました。立ち上がりには1〜2秒かかりますね。ブーストONにしても。全然OKです。

2023.06.10 追記

常にCPUを割り当てる設定にすると、料金が跳ね上がった。CPUに仕事をさせると高くなるよね。 Cloud Run、もしかしてお高い・・・? 小規模ならGAEのほうがコスパ高いんかな。