未踏ジュニア終わったから超大規模リファクタリングしてみた
こんにちは。そうまめです。
:::message この記事は、未踏ジュニアアドベントカレンダーの記事です。 :::
今日は私が現在開発しているアプリ、TutoriaLLMというアプリを大規模にリファクタリングした話をしようと思います。だいぶ慣れてきたとはいえ、まだまだ界隈の方から見ると素人なので一般的に良くないとされるコードの書き方をしているかもしれません。あったらそっと教えてください。連絡先はこちら
TutoriaLLM とは?
Scratch のようなブロックプログラミングのチュートリアルを簡単に作成し、AI を利用して提供することができるセルフホスト型のソフトウェアです。
TutoriaLLM は、わかりやすく言ってしまえば、ScratchやMakecodeで起きた不満や、思いついたアイデアなどをたくさん盛り込んだ、先生と子供を対象としたブロックプログラミングの学習ソフトウェアです。
アプリ全体を監視しながら、先生のアシスタントとして子供にブロックプログラミングを教える AI 機能や、作成したコードをサーバーサイドで実行する VM システムなどの技術を使い、教える人にかかるコストを下げ、プログラミング教育へのアクセスをしやすくすることを目指しています。
アプリは今年の 5 月ごろから、クリエータ支援プログラム「未踏ジュニア」からの支援を経て開発しており、11 月 4 日に成果報告会を行いました。また、その後 17 日に開催されたアプリ甲子園では、なんと優勝することができました。びっくりです。このアプリはプログラミング歴 2 年の私が初めてまともに開発するアプリケーションだったので、不慣れな中コードを書いている私を温かい目で見守り、たくさんのことを教えていただいた未踏ジュニアのみなさんには感謝しきれません。
リファクタリングする理由
しかし、成果報告会を終え、アプリ甲子園で優勝し、満足げになった私ですが…短い時間でなんとか完成させた自分のアプリのコードを触っていると、「…なんか、コード汚くね?」と思うようになってしまったわけです。技術力が上がったということなのでしょうか。アプリはお世辞にも安定しているとは言えず、デモサイトの Sentry には毎日のようにエラー通知がきます。技術デモとしてはかなり高く評価されているこのアプリも、実際に世の中に実装するにはまだまだ不十分で、使い物にならないのです。
このアプリは先生や子供が使うことを想定しています。今までは高校生が作ったアプリとしてみんな多めに見てくれたのかもしれませんが、実際にユーザーが使ってエラーが出てしまうと大問題ですよね。
また、このアプリはオープンソースとしており、今はまだあまり参加してくれている方はいないとはいえ、今後貢献してくれる人が現れるかもしれません。そのために、開発者が気持ちよくコードを書ける環境を整える必要があると思いました…というか、友人に指摘されました。
5 月にコードを書き始めた頃は、全てが手探りで何もわからなかったけど、今ならなんとかできる気がする!ということで、リファクタリングをすることにしました。
リファクタリング開始!
リファクタリングにあたって、ネットの情報だけから作るのではなく、度々手伝ってもらっている助っ人 Yuta さんにリポジトリの設定とか、流行りのフレームワークとかについて、さまざまな助言をいただきました。やっぱり実際にプロダクション環境でアプリを使えるようにするとなると、実際にやったことがある人から話を聞くのが一番良いのかもしれないですね。 https://zenn.dev/yutakobayashi
DB の再設計
まず初めに、データベースをすべて作り直すということから始めました。 従来は PostgreSQL と Redis を組み合わせたものを使っていたのですが、これを PostgreSQL に統合することにしました。
元々 TutoriaLLM では、LLM の処理や、複数ユーザーによる同一セッションのアクセス、サーバーサイドでのコード実行を実現するために、ユーザーの作成したプログラムをリアルタイムにサーバーのストレージと同期していました。これを実現するために、Redis を使っていました。当時はあんまり PostgreSQL についての理解がなく、また、試験段階ということもあり、とにかく動くバージョンを早く作ることを優先していたので、シンプルに使いこなすことができ、すぐに使えるという理由で選びました。また、セッションのデータは JSON で記述されており、これを毎回 Redis に上書きするということをしていたので、インメモリによる高速な処理を謳っているというのも理由としてありました。
しかし、月日が経ち、実際に人に使ってもらう段階にあたって、「とにかく動く」ものより、「安定して動く」ことが求められるようになりました。Redis はインメモリなので、特に何もせずにサーバーを停止すると、全部データが吹き飛びます。実際に子供が使っている最中にサーバーが一瞬でも落ちたら子供が作っている途中だったデータは全て吹き飛び、大惨事です。また、データの更新方法も差分ベースにしたことで、一度に読み書きする量も減りました。これだったら…PostgreSQL で十分ですよね。
というわけで、とにかく動くデモを作る段階から、実際に人に使ってもらう段階に移るにあたって、DB を設計し直すことにしました。
選定した技術
設計し直したと言っても、セッション管理の部分を PostgreSQL に統合しただけなので、結構簡単にできました。ただ、後方互換などをサポートするのは厳しかったのでやめました。(実稼働でこれが起きるととても大変ですよね) PostgreSQL のサーバーには、AI 関連の機能で Vector データ(普通の PG にはなく、拡張する必要がある)を保存する必要があったので、初めからそれがサポートされている Docker イメージを使いました。 https://hub.docker.com/r/ankane/pgvector
これらの操作にあたっては、Drizzle ORM を使いました。Vector のサポートもしていて、Javascript/Typescript で書いている人はこれが一番良いと思います。 https://orm.drizzle.team/
さようなら、Express
TutoriaLLM では、Express でバックエンド、Vite でフロントエンド、というスタックだったので、これらを早く作るために、Vite-express というそのまんまの名前のものを使っていました。 https://github.com/szymmis/vite-express フレームワークというより、フルスタックを簡単にデプロイするためのラッパーとでもいえば良いのでしょうか(間違っていたらすいません)。アプリは静的ファイルで提供しているので、こういうものを使えば Vite でビルド → それを Express で読み込む → フロントエンド/バックエンドの提供…までの一連の流れを簡単にできたわけです。
開発した当初はこれめっちゃ便利じゃん!と思っていました。確かに便利です。しかし、こういうものを使ってしまうと、フロントエンドとバックエンドを切り離すことができなくなってしまいます。 TutoriaLLM は Docker イメージとして提供していますが、これだとスケーラビリティのかけらもありません。先ほどと同じように、動くデモを作るなら良かったのですが、使ってもらうとなると…ちょっと心配なわけです。
モノレポ作戦
ということで、Yuta さんの助けを借りながら、アプリケーションをモノレポにするところから始めることにしました。pnpm を利用していたので、pnpm ワークスペースを利用してフロントエンドとバックエンドを切り離します。実質 Vite と Express なので、ディレクトリを移動させればすぐにできる…はず…
型定義の問題
しかし、現実はそう甘くありませんでした。5ヶ月前のわたしは、フロントエンドとバックエンドで Typescript の型定義を共有していたのです。
フロントエンドとバックエンドの間を通信させる際は、何もしない場合、型定義をつけることはできません。型定義をつける方法としてはいろいろあるのですが…面倒臭くて、Any 使おうとしていました(全世界の Typescripter を敵に回しました)
型定義専用のモジュールを pnpm ワークスペースで作って、そこに管理する…とか、考えるだけで面倒臭いし、二重定義なんてしたら多分管理面倒くさくなってまた Any 地獄へ逆戻りしてしまいそうです
Hono + RPC
そんな面倒くさがりの個人開発者にアツいフレームワークがありました。Hono です。 Hono だったら、バックエンドで zod などを使って定義した型定義をフロントエンドに持ってくることができます。 https://hono.dev/docs/concepts/stacks
すごいですよね。一体どういう技術が使われているのか、私は見当がつきません(勉強しなさい)が、これを使えば、そのままフロントエンドに型を持っていくことができます。しかも、Zod+OpenAPI の組み合わせをもとにこれらを生成することもできます。API の仕様を定義して、それに合わせたレスポンスを記述するだけで、バックエンド側も完全に型が効いた状態で開発を行うことができるわけです。
そんな感じでどう考えても今の状況には Hono の方がマッチしているので、Express と決別することにしました。Express ってサーバーサイドを作るのにめちゃめちゃ使われているフレームワークではあるし、大規模なアプリケーションで使われているケースが多く、安定性も高いと思うのですが、少なくとも自分のようなケースだとこっちの方が良いです。
元々 TutoriaLLM でも Hono はユーザーのコードを実行する時に使っていました。 TutoriaLLM のコード実行機能では、Minecraft のようなゲームとかと接続してコードを実行することができるのですが、この時に Hono にある Websocket のヘルパーを利用することで、軽量な Websocket の接続環境を用意していました。結構この手の websocket サーバーってマイクラみたいな特殊なクライアントと接続できないケースがあるのですが、Hono はとてもシンプルに書かれているので、こう言ったトラブルも少ない印象があります。
という感じで今回のリファクタリングにあたっては、この Hono をアプリ全体に導入することになったのですが、使い勝手が良かったので、今後は Hono 信者になって Hono のことを推していこうと思います。
時間と成果
という感じでいかがだったでしょうか。 初めて作った Web アプリを、初めてリファクタリングした割には、かなり良い改善が行えたのではないかなと思います。
ここまでの変更にかかった時間に関しては、約3週間程度でした。技術選定、DB の再設計、リポジトリの大移動、モノレポ化、フレームワークの変更、型共有の設定などをすべて行って、この時間だったら上出来だと思います。
この変更によって、TutoriaLLM の安定性はとても良くなったと思います。今まで仮で動く状態だったものが結構きちんと動くようになってくれたので、とても満足しています。
とはいえこのリファクタリングを経ても動作が不安定な箇所はあり、もう少し直さないといけないところはあるのですが、このタイミングでしっかりとした開発の基盤をここで整え、アップデートを重ねつつ、いずれはプログラミングの教育を行っている教育機関に対してこのアプリを実稼働にデプロイする(使ってもらう)ことなどを考えています。
未踏ジュニア期間では、とにかく興味を持ってもらう人を集めるために、とりあえず動くデモを作成することに専念していました。もちろんそうやって、安定性を無視して開発を行うということもありだとは思うのですが、いつかはこうやって書き換えないといけない時が来てしまいます。安定して動くことも目指していかないといけません。これからも頑張ろうと思います。
0人が0回拍手しました
関連記事
💻 GitHubでチーム開発してみよう
GitHubでチーム開発してみよう
GitHub、アカウント登録はしたけど開発者じゃないから使わないというそこのあなたのために、さっと使い方を説明した記事を書きました。
🤖 GoogleスプレッドシートをAPI経由で読み込もう!
GoogleスプレッドシートをAPI経由で読み込もう!
去年別のサイトで書いた記事を再公開したものです。
超簡単です。Googleスプレッドシートに書いた情報をAPIというものを使って読み込む方法を解説します。 応用すれば、簡易CMSとしても使用でき...
✒️ Markdown記法チートシート(すごい簡単)
Markdown記法チートシート(すごい簡単)
Google DocsやWordとかを使ったことあると、 こんな感じで、太字とか、斜体とか、見出しとかを選択することができますよね。
でも、コードを書くエディタとかを使う時などでは、こういったボタン...
🤖 ビジュアルプログラミングにLLMを組み込みたい(Blockly × LLM)
ビジュアルプログラミングにLLMを組み込みたい(Blockly × LLM)
初めまして。そうまめこと得丸創生と申します。 1 年ほど前から Typescript / React の Web アプリ開発にどハマりして、現在は[「TutoriaLLM」](https://tuto...