自作TODOアプリの技術をルーレットで決めて作る

この記事いいな〜と思ったが、試したい技術がたくさんあったのでルーレットで決めることにした。
試しに次のように技術をジャンル問わず列挙して、ルーレットを回して3つの技術を決めて、それを使ってTODOアプリを作る。
- React
- Solid.js
- Vue
- Nuxt
- Next.js
- Android
- iOS
- Fastly Compute
- AWS lambda
- GCP Cloud Functions
- Cloudflare Workers
- Deno Deploy
- Rust
- Go
- Deno
- Node.js
- Ruby
- Python
- PHP
- Java
- Kotlin
- Swift
- TypeScript
- Dart
- C#
- C++
- D
- F#
- Haskell
- Julia
- Lua
- Perl
- Postgresql
- MySQL
- MariaDB
- SQLite
- MongoDB
- Redis
- Elasticsearch
- Tailwind CSS
- Bootstrap
- Material UI
- Chakra UI
- OCaml

Webルーレットを使った。
次のようなルーレットを回す。無限に技術があってウケる。

1つ目: Deno
2つ目: GCP Cloud Functions
3つ目: Perl
やっていき

行き詰まったらルーレットをまた回して要素を追加することにする

アーキテクチャ雑に書いた
疑問
- Cloud FunctionsでPerl使えるのか?
- Stateをどこに持てばいいんだ?
- Cloud Functionsで無限に叩かれると困るので認証入れたい
- HTTPSトリガー時にトークンをAuthorizationヘッダで渡す https://cloud.google.com/functions/docs/securing/authenticating?hl=ja
- このためViewはホスティングすることなく手元のみとしたい

Cloud FunctionsでPerl使えるのか?
Cloud Functions 実行環境 | Cloud Functions Documentation | Google Cloud
Node, Python, Go, Java, .NET, Ruby, PHPのランタイムがある。Perlはない。
アプローチが2つ
- システムの中にPerlがありそうなので、perlのファイルを直にCloud Functionsに持っていてnodeのランタイムで
exec(...)
を叩く - Perlをwasmに変換して、nodeランタイムからロードする

Stateをどこに持てばいいんだ?
Google Cloud データベース | Google Cloud
AlloyDB for PostgresSQL, Cloud SQL, Spanner, BigQuery, Bigtable, Firestore, Firebase Realtime Database, Memorystore
いっぱいあるけどFirestoreでいってみよう

決定
- 手元に立てるView: Deno
- Add等の処理: Cloud Functionsのnodeランタイムでperlコマンドをexecで叩く
- DB: Firestore

GCP久々に使うからプロジェクト新しく切って課金設定して予算とアラート設定した

GCPでOpenTofuを使ってfirestoreのdefaultデータベースを立てるところまでやった。

念の為firestoreを消しておいた

hello world的なCloud functionsを書いて、opentofuのコードも書いたのでデプロイ
...APIの有効化をしてないので色々怒られる。
- Cloud Functions API
- Artifact Registry API
- Cloud Build API
を有効化した

applyが通ったけど、1st gen cloud functionsだったので2nd genに変更して再度apply
結構詰まったけど認証をつけてデプロイできた
Cloud Functionsのコードなどはここに置く

以下のようなコードでPerlを実行した時のstdoutの値を返せた。めちゃくちゃ素朴にPerlを実行してる
同じディレクトリにget.plを含めてzipに固めている
const { exec } = require('node:child_process');
exports.execute = async (req, res) => {
const out = exec('perl get.pl', (err, stdout, stderr) => {
if (err) {
console.error(err);
return res.status(500
).send(err);
}
console.log(stdout);
res.status(200).send(stdout);
});
if (!out.killed) {
return;
}
res.status(200).send('hello world.');
}

firestoreにperlから読み書きするのは無理だ...
perl要素が薄くなるけど、入力値をperlに渡してデータ整形したらnodeに戻してfirestoreに突っ込む形にしよう。

方針
- GoでCloud Functionsを書く
- Perlを使える場所を探す
- Goからexecでperlを呼ぶ

どうせならデータ整形ではなくPerlの正規表現を使ってValidationを書いたら面白いかもということで、ValidationをPerlに任せる

Perlの正規表現が難しい。
正規表現で日本語と半角英数字の間にスペースを挿入する - make world
こちらの記事を参考にしてtextlintのサブセットみたいなことをしてみようと思ったのだけど、日本語にマッチして数字にマッチしないみたいな正規表現を書くことができない...

ユニコードとPerlの情報が古いものが多すぎて何も分からん...
諦めてnumberとnon-numberの間にspaceを挟むやつにした

Perlにシェル経由で引数渡すの脆弱すぎて笑う
今回はCloud Functions側に認証をかけているので目を瞑って実装

バックエンドの実装が終わった。
ここまででやったことまとめ
- OpenTofuを用いてCloud Functionsをgoランタイムで立ち上げた code
- 手動でやっているところ: Cloud RunのExecution時の権限にFirestoreの権限をアタッチ
- (正直Google Cloudの権限の用語周りまだ分かってないので勉強したい)
- Cloud Functionsの中身を書いた code
- Goでメイン処理を書いた。get/post/put/deleteに対応した。
- Perlで数字と非数字の間に半角スペースを入れるようにした。これはShell escapeして渡しているが多分変なの渡すと壊れる。Cloud Functions側で認証をかけているので目を瞑った。
- Cloud Buildでgoのビルドをする際にPerlのスクリプトが消えるので困っていたが、Go embedを使ってビルド成果物にPerlのファイルを埋め込んで、関数実行時に毎回ファイルをwriteして実行することにした。ここは非効率だけど目を瞑っている。

達成できた
- Cloud Functions
- Perl
- Todoアプリのバックエンド
これからやる
- DenoでCLI軽く書く
- バックエンドであるCloud Functionsとの接続

TUIチャレンジしてみようかと思ったけど、もう3日もかけていて消耗してきてたので妥協することに。
$ deno run --allow-all main.ts -- post "宿題やる"
$ deno run --allow-all main.ts -- put 2900821b-ac99-457f-8afc-a7491dd34356 true "ご飯食べる"
$ deno run --allow-all main.ts -- done be8430a4-f628-4113-a047-fc3cb2e36db1
$ deno run --allow-all main.ts -- del c83a3ea4-030b-4a7f-9a49-334b2afbc033
$ deno run --allow-all main.ts -- show
⬜ コード書く (id: 112a16a4-afe1-4b21-8cf7-6bac54187a91)
✅ ご飯食べる (id: 2900821b-ac99-457f-8afc-a7491dd34356)
✅ 宿題やる (id: be8430a4-f628-4113-a047-fc3cb2e36db1)
こんなノリのCLIをさっくり書いて終わりとした。
$ deno run --allow-all main.ts -- post "卵を3つ買ってくる"
$ deno run --allow-all main.ts -- show
⬜ コード書く (id: 112a16a4-afe1-4b21-8cf7-6bac54187a91)
✅ ご飯食べる (id: 2900821b-ac99-457f-8afc-a7491dd34356)
✅ 宿題やる (id: be8430a4-f628-4113-a047-fc3cb2e36db1)
⬜ 卵を 3 つ買ってくる (id: ff2de2f0-7a5f-48c0-bb45-d44d2d611ae0)
ちゃんとPerlで数字と非数字の間にスペース挟むやつも動いている。

学んだ
- 学習の叩き台としてのTodoアプリは良い。
- 結構何を作るかを考えがち(新規性のあるもの・課題解決をするもの・長期に渡り使われるもの...)だけど、開発コストをかけて"捨てる"ことが大事なのだろうと理解した。どうしても時間をかけてコストをかけて作って長く使おうという思考に個人的にハマってしまいがちだったので、お題をTodoアプリに固定して思考コストをゼロにするのはいいなと思った。
- 手を動かさないと意外と知識は古くなっている。
- Cloud Functions、gen2は裏側Cloud Runになっているの知らなかった。
- Perl、思ったより情報源の探し方から分からずつらかった
- 手を動かして初めて「Cloud Functionsじゃなくて今はCloud Runを使うべきっぽいな?」とかわかる
- Firestoreのローカルエミュレータえらい
- 微妙なことをするのは良くない
- 元記事でmattnさんは興味をメモしてTODOアプリで試すみたいなことを書いていて、こうあるべきという感じがした。今回でいうとルーレットで決めたのでPerlをCloud Functionsで使うところとか非本質で詰まっていた。
- Denoに慣れているんだなと感じた
- DenoでCLI書くパートは一瞬で終わったのでやっぱ慣れって強いなと思った。他の言語にも慣れていきたい。
- 改善フェーズに入るまでを課題に感じているとTodoアプリ作成は良さそう
- スクラッチで書くのが苦手で、考えているうちに時間が経っちゃうタイプだったのでやばいコードであってもとりあえず動くものを作って、そこから改善していけばいいというフェーズに持っていくのはいいなと思った
- テストもCI/CDもないし、コードも褒められたものではないけど、とにかく手を動かすという点においては達成できた
- スクラッチで書くのが苦手で、考えているうちに時間が経っちゃうタイプだったのでやばいコードであってもとりあえず動くものを作って、そこから改善していけばいいというフェーズに持っていくのはいいなと思った

こんな感じになった
コード
後片付け
tofu destroy
todo-app-1のリポジトリをアーカイブ(気が向いたらアーカイブ解除する)