ディスカッション (11件)
HNの皆さん、こんにちは!モデルを何度も定義する作業にうんざりして、Oxyde(オクサイド)を作りました。
FastAPIを使っている方なら「あるある」ですよね。API用にPydanticモデルを定義し、データベース用に別のORMモデルを定義して、さらにその間の変換処理を書く……。SQLModelはこの解決を試みていますが、中身はSQLAlchemyのままです。TortoiseはDjango風の素敵なAPIを提供してくれますが、独自のモデルシステムを持っています。Django ORMは素晴らしいですが、フレームワークと密結合すぎます。
私が欲しかったのはシンプルさです。「Pydanticモデルそのものがデータベースモデルになる」ということ。1つのクラスで、入出力の完全なバリデーション、ネイティブな型ヒント、そして重複ゼロを実現しました。クエリAPIは、最高に洗練されたデザインの一つであるDjangoスタイル(.objects.filter()、.exclude()、Q/F式など)を採用しています。
「暗示的より明示的であること」
あらゆる「魔法」を排除するよう努めました。クエリは、.all()や.get()、.first()といった終端メソッドを呼び出すまでデータベースにアクセスしません。明示的に.join()や.prefetch()を呼ばない限り、関連データはロードされません。遅延ロード(Lazy Loading)も、裏で勝手に発生する「N+1問題」もありません。コードを読めば、何がデータベースにヒットするか正確に分かります。
型安全性へのこだわり
Pythonの弱点は実行時のサプライズ(予期せぬ挙動)です。Oxydeは3つのレベルでこれに対処します。(1) makemigrations実行時に、完全に型定義されたクエリ用の .pyi スタブファイルを自動生成します。これにより、IDEは filter(age__gte=...) にintが必要であることや、.all() が list[Any] ではなく list[User] を返すことを認識します。(2) PydanticがDBへの投入データを検証します。(3) model_validate() を通じてDBからの戻り値も検証します。これらすべてが、たった一つのモデル定義から得られるのです。
なぜRustなのか?
目的は単なる「速度」ではありません。言語の優劣を競うつもりもありません。ビジネスロジックの表現において、Pythonの右に出るものはないでしょう。しかし、SQL生成、接続プーリング、行のシリアライズといったインフラ部分は、システム言語が本領を発揮する場所です。そこで役割を分担しました。Pythonはモデルとビジネスロジックを担当し、Rustはデータベースの配管部分(plumbing)を担当します。クエリはPython側で中間表現(IR)として構築され、MessagePackでシリアライズしてRustに送られ、そこで方言に応じたSQLの生成・実行が行われ、結果がストリーミングで返されます。速度はその副産物ですが、利便性のためにパフォーマンスを犠牲にする必要はありません。興味があればベンチマークも見てみてください:https://oxyde.fatalyst.dev/latest/advanced/benchmarks/
現在の機能:
Djangoスタイルのマイグレーション、セーブポイント付きトランザクション、JOINとPrefetch、PostgreSQL/SQLite/MySQL対応、FastAPI統合、そしてFastAPI/Litestar/Sanic/Quart/Falconで動作する自動生成アドミンパネル(https://github.com/mr-fatalyst/oxyde-admin )など。
現在はv0.5のベータ版で、活発に開発中です。APIはまだ変更される可能性があります。これは、私が個人的にずっと使いたかったORMを作ろうという試みです。フィードバックや批判、アイデアをぜひお待ちしています!
(笑)Django ORMがSQLAlchemyより速いなんて知らなかった。でも両方使ったことある身としては納得。 > なぜRustか? ... RustがDB周りの配管を担当。クエリはPythonで中間表現(IR)として構築され、MessagePackでシリアライズされてRustに送られる。そこで方言ごとのSQLが生成・実行されて、結果がストリームで返ってくる。高速化は副作用であって、目的じゃない。 いいね。で、デプロイには依存関係的に何が必要?
これ良さそう。エコシステムにこういうツールの空白地帯が本当にあるよね。sqlmodelは有望に見えたけど、Pydanticモデルに追加するとバリデーションが全部無効になっちゃうから、ぶっちゃけ使い物にならないどころか逆効果なんだよね。
なんでこの2つを密結合させたいんだろう? APIインターフェースとDBスキーマが紐付いちゃわない? 実際にはこれらは別々の概念だし、たまにAPIから返す'user'がDBの'user'と同じに見えたとしてもね。純粋な疑問なんだけど、最近FastAPIを使い始めたばかりで最初は重複が多いなって混乱したけど、少し経験を積んだら、これらは常に同じではない別物だと分かった。何か見落としてるかな?
SQL生成、コネクションプーリング、行のシリアライズみたいなインフラ部分はシステム言語が向いている。 いや、そうでもない。大事なのはJIT可能でPyPyと相性がいいことだよ。 > 型安全が大きな動機だった。 もし型付きクエリ言語が、スキーマオブジェクトで定義済みのフィールド名を文字列で参照(stringly references)することから逃れられないなら、もうその時点で負け戦だね。
ORMは間違いで過去の遺物だってことで、委員会の合意が取れてたんじゃなかったっけ?
ひそかにこれがDjango ORMに取って代わることを期待してる。
Rustのパッケージにはもっと独創的な名前が必要だよ。これじゃ混乱を招く。すでにOxide computersがあるし、JSのリンター/フォーマッターのOxcもあるんだから。
これいいね、エコシステムのニーズに応えてくれそう。あと管理ダッシュボードはDjangoの超強力な機能だから、最初から作り込んでるのはナイスだね。
TypeScript側だとPrismaが型安全なORMを凄く上手くやってると思うけど、Pythonではしっかりサポートされてないみたいで残念だった。これはそれに似た雰囲気があるし、理にかなってるね!
ORM使わない派だけど、psycopg3に標準で備わってるPydantic機能は最高に気に入ってるよ。