hono について書きたいよ!
honoというアツいフレームワークが存在するらしい。どうやら。いつ頃に聞いたのか忘れてしまったが、日本発でこんなに界隈を騒がせる OSS が存在するなんてすごい!
だいぶ乗り遅れていたりするけれども、でも書く。
とりあえず書く範囲を限定しないことには記事は書けない。どんな記事を書こうか?と思ったところで、2 つに記事を分けることにした。
機能を調査する記事・プロダクトを作成する記事だ。
プロダクトは、todo じゃつまんない。todo api とかは制約や面白みが無くて、全くもってそそられないから、ちょっといつもと趣向を変えよう。
ChatGPTなどを使ったマルチエージェントシステムを作ってみたい!と思うのでその前段となるエージェントを複数登録するサービスを作ってみよう。
今回は調べるだけの記事とする。
何も知らないから何でも調べてみたくなってしまうが、少しだけに制限すること。
hono について何も知らない……
とりあえず調べようと決めたけれども、honoについて何も知らない事に気づいた。
全くの門外漢である。htmxというライブラリを聞いて、そんなへんてこなライブラリがあるんかや、と思ったところ。
だから、honoの歴史をついばんでみよう。
hono の原点
honoはフレームワークの中ではかなり後発だ。けれど、それにもかかわらず歴史と呼べるほど地層が積み重なっている。
それ、honoがここまで成長しているポイントだと先に言っとこう。
honoはyusukebe さんが作ったフレームワークだ。最初は個人開発だった。(ちなみに、この方はあのボケてを作った方だ、すごい)
At first, I just wanted to create a web application on Cloudflare Workers. But, there was no good framework that works on Cloudflare Workers. So, I started building Hono.
I thought it would be a good opportunity to learn how to build a router using Trie trees. Then a friend showed up with ultra crazy fast router called "RegExpRouter". And I also have a friend who created the Basic authentication middleware.
Cloudflare Workers上で動くフレームワークが存在しておらず、自分で作ってみたそうだ。彼のブログをざっと遡ると分かる通り、彼は技術的な興味で何度もフレームワークを作ったり作ったりしている。perl 時代。手遊び的に作品を作ってみる方だ。
Plack::Request を使っていくつも「出来損ないの Sinatra」を作っては投げ出し、というのを繰り返したものだ。 つまり、Plack::Request のようなリクエストオブジェクトが提供されていればフレームワークは簡単に作れるということが分かっていた。
以前 R25 チャンネルで成田氏が言っていた。
「ビジョンや崇高なストーリーを語る経営者が増えた。成り立ってしまっている地味な企業の方が好感が持てる」
yusukebe 氏のプロダクトの作り方は、まさにそういった感じではないか。
彼の手遊び的な美点。Plack::Requestを使ってフレームワークを作っていた経験もある。Trie 木で Router を実装してみたかったというのもそうだ。
そういう初期衝動・技術的課題に駆動されて作られたプロダクトだ。
hono の特色
honoの一番の特色を挙げようと思ったときに最初に聞いたのは、どんな環境にでもデプロイできることだ。
:::note info
どこにでもデプロイできる
:::
それは、Web Standard API に則っているからである。
Using only Web Standard APIs, we could make it work on Deno and Bun. When people asked "is there Express for Bun?", we could answer, "no, but there is Hono". (Although Express works on Bun now.)
Web Standard API に則っているということで、honoは [Cloudflare Workers](https://www.cloudflare.com/ja-jp/) のみならず、Deno や **Bun**にも対応している。
ちなみに、Bunというのは徹底的な速さが売りの JS ランタイムだ。
「インフッラ」について苦手意識を持っているので、環境と効くと蕁麻疹が出てしまう。だがここで言う環境は、どちらかと言うとこの場合は JS ランタイムのことを指しているはずだ。
honoはDeno や Bunという JS ランタイムに対応しているから、開発体験がとても良い。
良い開発体験
デフォルトでTypeScript採用をしている。
更に、特筆するべきはZodやtRPCなどと組み合わせた型安全な API/フロントエンド開発だ。
バックエンドで作った API を利用するときに、型安全に呼び出せる(!)ということだ!
普段バックエンドについてほとんど触らないわけで、OpenAPIあたりなんかはとてもごにょごにょ言っているけれど、tRPCもOpenAPIも、宣言的に API 作れたら良くない?ということだ。
ついでに言うとtRPCは[Clojure](https://clojure.org)でいう[cljc](https://clojure.org/guides/reader_conditionals)ファイルみたいなもので、バックエンドとフロントエンドで型を共有できるもの。
それはとっっても便利。なぜなら、型の二重管理はちょっと考えただけでも煩雑だし、BFF みたいなことをやるにしてもコストがかかる。
Zodなどを利用して型安全に開発をすることに関しては別の記事に譲ろう。
ここでは、honoの本丸である Router に乗り込んでいきたい。
Router
……honoは Router が凝っているらしい。
Router について調べてみるのは面白いと思う。
Router がコア機能であることには、実はちゃんとした理由がある。それは、Cloudflare Workersが Web 標準の API しか利用していなかったから。
そして、Cloudflare Workersがエッジサーバーフル活用するエコシステムを持っていて、軽いフレームワークが求められていたこと。
必然的に、Clojure における Ring framework のような、うっすい web フレームワーク(もはや機能と言ってもいい)ものが求められたということになる。
Ring framework は、Clojure における API を抽象化して捉えたものだ。Request -> Response という一つの Handler 関数として捉える考え方である。ここでは割愛する。
「Router ぐらいに機能を絞ったかっるいフレームワークを作りたい!」
だからこれが初期衝動だったのだ。
一見してフレームワークなんて何をやっているか全くわからない。JWT・セッション、Auth、ファイルベースルーティング、果ては SSR とか入ってくるともうおてあげだ。
でも、最終的にフレームワークがやっていることはリクエストを取得してレスポンスを返すということだ。だから、Router を学ぼう。
公式ドキュメントによると、Router は 5 つ存在する。
- RegExpRouter
- TrieRouter
- SmartRouter
- LinearRouter
- PatternRouter
え!?5 つもあるの……?
よく理解すると、Router の中で重要なのはたった 2 つだけだ。安心してほしい。(RegExpRouter/TrieRouter)
その他は、尖り散らかしているのが 2 つ(LinearRouter/PatternRouter)
あと 1 つは、RegExpRouter と TrieRouter を使い分けてくれる SmartRouter だ。
0. 他のライブラリなどで見られる Router
5 つの Router について解説する前に、他のライブラリはどのように Router を作っているだろうか。それを抑えておくことで、honoの Router がどうすごいのかがわかりやすくなると思う。
まず、君たちは Router を作ると言ってどのようなところから始めるか?
Router がしなければいけないのは、とりあえずURL -> Handler
の紐づけだろう。どの URL に訪れたとき、どういう風に Request を Response に変換するか、ということだ。
そして、URL でよくあるのが以下のケース。
/users/:id/:repository-id
こういうワイルドカード的なものに対応する必要がある。じゃあ、正規表現を使ってみるか。たとえばこんなふうに。
^/users/([^/]+)/([^/]+)
だから、素朴な実装としては、キーとして正規表現、そして値に Handler もしくは Handler の関数名を登録することになりそうだ。
そして、URL に訪れた際は、上から順番に舐めて確認していくのだ。React Router とかもそういう風になっているのではないだろうか。多分。
React Router の実装詳細について軽く触れると、ルートのマッチングプロセスは、ユーザーが特定の URL に移動したときに始まる。React Router は、Routes コンポーネント内で定義された各 Route を順番に評価し、現在の URL が各 Route の path と一致するかどうかを確認する。最初に一致した Route のコンポーネントがレンダリングされ、一致しない場合は、ワイルドカードパス(*)を使用して 404 エラーを処理するフォールバックルートを定義する。
このように、React Router は上から順にルートを評価する設計になっており、この特性によってシンプルかつ直感的なルーティング管理が可能である。
1. RegExpRouter
さて、話をhonoの Router へと戻そう。
これが一番速い。なぜなら、たった一つの正規表現でマッチするからだ。
どういうことか。私の理解だと大雑把に言ってこういう正規表現になる。
(ルート1|ルート2|ルート3……)
このように、一度の正規表現マッチによって一発で Handler を取得できるから速いのだ。
ちなみに、このもととなる Router 実装は perl に存在する。正規表現の統合に関しては、Router::Boom が基礎となっている。Router::Boom は、個々のルートパターンを論理 OR 演算子(|)を用いて一つの包括的な正規表現に統合する。この方法により、各ルートを順番にチェックする必要がなくなり、特に多くのルートを持つアプリケーションでのパフォーマンスが向上する。
さすが正規表現大国 Perl(そんなものあったか?)。
2. TrieRouter
こちらも、RegExpRouter に比べると遅いというだけで、とても速い Router だ。
なぜか。Trie Tree(日本語ではトライ木と呼ぶことが定着している。語源は reTrieval なので、英語圏ではツリーツリーとも発音される)を利用しているからだ。
Trie Tree というのは、先祖をプレフィクスとして持つツリー構造である。
URL は / (スラッシュ)で区切られたセグメントが複数ある。そして、一番左から見て、親から子どもへと連なっていると見ることができる。
この URL の構造を、そのまま木構造として保持したのである。
例えば、以下のルートが存在するとしよう。
/users/\:user-id/articles/\:article-id
/users/\:user-id/articles
/users/\:user-id/profile
/settings
/settings という URL へユーザーが訪れたとき、すでにページが/users 関連ではないということが確定しているはずだ。だから、TrieRouter は/users 配下のルートをすべてシャットアウトすることができるのだ。
だから、速い。枝切りできる。
これら 2 つの Router だけで、基本的になんとかすることができる。また、SmartRouter というものが存在して、RegExpRouter を使うことができるときには RegExpRouter を使う、というような選択ができるのだ。
残りは尖っている Router を説明して、一旦終わりとしよう。
LinearRouter
LinearRouter は、ごく一般的な Router と一緒だ。つまり、登録された順番に上から繰り返し確認していく。
だから、確認されるルートも上から決定されるため、ワイルドカード的なルートは先に登録してはいけないというような制約がある。
この Router が輝くのは、一番最初の登録時間である。なぜなら、登録してからデータを整形することがないから。lazy なのだ。
PatternRouter
これは、5 つの中で一番尖っているだろう。
なんと、Router として一番容量が軽い。公式の記述によると、なんと 15KB 以下である(!)
これは、多分リソースに制約がある無料枠での個人開発にとても役立つのではないか…?
ここまで駆け足でhonoというフレームワークについて見てきた。技術についても面白かったが、何よりこのhonoが歩んできた道のりが面白かった。
どのようなライブラリやプロダクトも、コミュニティが大事である。yusukebe 氏 は海外も見据えて積極的にコミュニティのための貢献をしてきた、その結果がこのhonoだ。
最近個人的にもようやく余裕が出てきて、肌で感じている。
一人でできることは少ないけれど、コミュニティに貢献することで、大きな成果を生み出すことができるんだって。