
2026/07/08 1:48
Biff.graph:Clojure のコードベースを探索可能なグラフとして構造化する
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
最も大きな進歩は biff.graph です。これは、レゾルバー(resolver)と呼ばれる仕組みを用いて、データベースと派生データを単一のクエリ可能グラフに統合するための軽量ライブラリです。Pathom などの重い代替手段とは異なり、このツールはデータ形状を宣言することを簡素化し、生のデータベースソースと複雑な論理的変換の区別を効率的に隠蔽します。biff.graph は、Datomic pull に着想を得た EQL パターンの修正版サブセットの上に構築されており、開発者は背後にある取得メカニズムの詳細な知識を必要とせず、アプリケーションデータ構造を定義できるようになります。该系统は、キャッシュ機能、バリデーション、バッチ処理などの主要な機能をサポートしており、パフォーマンス向上のため、複雑なクエリ計画ステップは意図的に省略されています。レゾルバーは、入力形状(通常はプライマリーキー)から出力形状(すべての属性と必要な結合を含む)へデータを転換する独立したロジックの断片として機能します。これらのレゾルバーは単に
model/ ディレクトリ内で組織化でき、「グローバル」レゾルバーは、特定の入力クエリを必要とせずに、クリーンなタイトルの生成のような複雑なバックグラウンドタスクを処理します。このライブラリはキャッシュ用のミドルウェアとコンテキストラッパー(new-ctx)を通じて統合され、アクセス可能なレゾルバー関数によるテストに対応し、biff.fx マシンと共に実行可能ロジックフローを定義する作業も可能です。究極的には、biff.graph は独立したロジックの断片が変換を処理でき、重厚なアーキテクチャ上のオーバーヘッドや特定の入力クエリを必要としないことで、グラフ様構造に依存するアプリケーションの開発を簡素化します。本文
biff.graph データ可視化ガイド
biff.graph を利用することで、データベースおよびビジネスロジック/派生データを単一かつ拡張可能なグラフとして一元管理し、照会(クエリ)が可能になります。
- データモデルのコードは小さな独立した単位(「リゾルバー」)に分割できるため、アプリケーション側では必要なデータの形状を宣言するだけで、取得方法を記述する必要がなくなります。
- これにより、特にコードベースが大きくなるにつれて理解しやすく、テストも容易になります。
概要と特徴
biff.graph は本質的に Pathom の軽量版です。Pathom の機能サブセットのみを実装しており、意図的に簡素化されています。
メリットとデメリット
- メリット
- コードベース全体が約 600 行(クエリ実行部は約 200 行)という軽量構造です。
- デフォルトで Biff に含まれることがありますが、学習コストをかけずにコード構造上の利点を活かすことができます。
- デメリット
- Pathom よりも一部のクエリが効率的に実行されない可能性があります。
- クエリ計画(プランニング)のステップを持たない点が最大の違いです。
機能サポート
- バッチリゾルバーをサポートしています。
- キャッシング機能をサポートしています。
依存関係
Maven Central での依存追加例です。
com.biffweb/graph {:mvn/version "2.0.0-rc7"}
ステータス
本ライブラリは、他の Biff 2 ライブラリが全てリリースされるまで**ベータ版(RC)**の状態です。
- 互換性:現時点では重大な不互換の変更を予定しておりません。
API リファレンス
- 主要なパッケージ名:
com.biffweb.graph
コンセプト
biff.graph は、EQL / Datomic の pull パターンに基づき、データを定義する際に少し改良されたサブセットを採用しています。
リゾルバー (Resolver)
**「リゾルバー」**とは、入力および出力のクエリが関連付けられた関数のことです。
- 入力のクエリ形状: データベースから取得すべきデータの鍵(ID など)を受け取ります。
- 出力のクエリ形状: 計算された結果や関連データを含んだマップを返します。
複数のリゾルバーを定義した後、
biff.graph のクエリエンジンはそれらを利用して、照会された任意の形状のデータを返します。
コード例
バナナグラフモデル
以下のスニペットでは:
- シミュレートされたリゾルバー(
とrss-feed
)を定義しています。post - 派生データ計算のリゾルバー(
)を定義し、絵文字を除去したバージョンを返しています。clean-post-title - どの属性がデータベース取得か、派生処理かが自動で区別されます。
(require '[com.biffweb.graph :as biff.graph :refer [defresolver]]) (require '[clojure.string :as str]) ;; --- 1. RSS Feed リゾルバー --- (defresolver rss-feed {:input [:rss-feed/id] :output [:rss-feed/title]} [_ctx {:rss-feed/keys [id]}] (get {1 {:rss-feed/title "My Blog"}} id)) ;; --- 2. ポストリゾルバー --- (defresolver post {:input [:post/id] :output [:post/title :post/url {:post/rss-feed [:rss-feed/id]}]} [_ctx {:post/keys [id]}] (get {2 {:post/url "https://example.com/my-post" :post/title "My Post 🎅" :post/rss-feed {:rss-feed/id 1}} 3 {:post/title "My Other Post 🎅" :post/rss-feed {:rss-feed/id 1}}} id)) ;; --- 3. デリバリーフ(派生ロジック) --- (defn remove-emojis [s] (str/replace s #"🎅" "")) (defresolver clean-post-title {:input [:post/title] :output [:post/clean-title]} [_ctx {:post/keys [title]}] {:post/clean-title (-> title remove-emojis str/trim)}) ;; --- 4. コンテキスト作成とクエリ実行 --- (def resolvers [rss-feed post clean-post-title]) (def ctx (biff.graph/new-ctx resolvers)) (biff.graph/query ctx [{:post/id 2} {:post/id 3}] [:post/id :post/clean-title ;; オプションの属性は [:? ...] で示します [:? :post/url] ;; ジョイン属性はネストされたマップで示します {:post/rss-feed [:rss-feed/title]} ;; オプションのジョイン属性 {[:? :post/rss-feed] [:rss-feed/title]} ]) ;; => [{:post/id 2, :post/clean-title "My Post", :post/url "https://example.com/my-post", :post/rss-feed {:rss-feed/title "My Blog"}} {:post/id 3, :post/clean-title "My Other Post", :post/rss-feed {:rss-feed/title "My Blog"}}]
使用方法
リゾルバーの定義
通常、データベーススキーマに基づいてリゾルバーを自動生成する機能が必要です(
com.biffweb.graph/resolver を使用)。ガイドラインは以下の通りです:
- 1 トピック 1 リゾルバー: テーブル/エンティティタイプ 1 つに対して 1 つのリゾルバーを用意します。
- 入力クエリ: 主キーのみを含めるべきです(例:
)。[:person/id] - 出力クエリ: 他のすべての列/属性を含めるべきです(例:
)。[:person/age, :person/favorite-color, ...] - ジョインキー: 外部キー/参照属性に対して、ジョインキーおよびそのサブクエリとして当該エンティティの主キーを含めるべきです(例:
)。[{:person/pet [:pet/id]}, ...] - バッチ処理: リゾルバーオプションに
を含めます。これにより複数のエンティティを一括で取得できます。この設定を持つリゾルバーは、入力マップのベクトルを受け取り、同じ順序の出力マップのベクトルを返す必要があります。:batch true - 実装: リゾルバー関数は事実上
を実行し、ジョインキーを確認しながらネストされたマップとしてフォーマットする必要があります。SELECT *
※公開されていない
biff.sqlite ライブラリには SQLite 向けの機能が用意されています(未編集の AI コードによる注記)。
その後、任意のリゾルバーを
com.biffweb.graph/defresolver で定義できます。ヘルパー関数からのロジックを gradually に移動させることも可能です。
コンテキストの作成
最後に、定義したリゾルバーを
com.biff.web.graph/new-ctx に渡します。
- クエリエンジンが使用する単純なインデックス処理が行われます。
- リゾルバーは、キャッシングや検証などの処理を行うミドルウェアでラップされます。
クエリの実行
リゾルバー定義後、必要な場所(例:Ring リクエストハンドラー)からクエリを実行します。
(defn settings-page [{:keys [session] :as ctx}] (let [user (biff.graph/query ctx {:user/id (:uid session)} [:user/email :user/display-name :user/subscribed])] ...))
上記の例では、
com.biffweb.graph/new-ctx の出力が入ってくる Ring リクエストにマージされるミドルウェアがあることを前提としています。
デバッグ
com.biffweb.graph/query 内部で発生した例外は、:biff.graph/trace キーを通じて以下を教えてくれます:
- クエリエンジンがグラフ走査のどの位置にあったか。
- 入力データを解決しようとしたのはどのリゾルバーか。
- 失敗したパスに焦点を当てて最小再現例を作成できます。
テスト
リゾルバー関数は
:biff.graph/resolve-fn キーの下に格納されます。
(defresolver my-resolver {:input [:user/id] :output [...]} [ctx input] ...) (deftest test-my-resolver (is (= ((:biff.graph/resolve-fn my-resolver) {:biff.graph/input {:user/id 1}}) ...)))
biff.fx との統合
biff.fx を使用している場合、ハンドラーマップに com.biffweb.graph/fx-handlers をマージできます。これにより、:biff.graph.fx/query キー下でクエリが公開されます。
(require '[com.biffweb.fx :refer [defmachine]]) (defmachine do-something :start (fn [{:keys [session]}] {:user [:biff.graph.fx/query {:user/id (:uid session)} [:user/email ...]] :biff.fx/next :next}) ...)
defresolver も、リゾルバーの本体を biff.fx マシンの形式として定義する変形をサポートします。これにより、リゾルバー内部でデータベース照会や外部サービス呼び出しなどの副作用処理を行うことができます。
注意点:
で定義された状態関数は、通常のdefresolverマシンとは異なり、1 つではなく **2 つのパラメータ(biff.fxとctx)**を受け取ることに注意してください。input
biff.core との統合
(com.biffweb.graph/module) をモジュールに含んでいる場合:
- 他のモジュール上に
ベクトルを備えたリゾルバーを配置できます。:biff.graph/resolvers - アプリケーションスキーマを
で登録した場合は、com.biffweb.core/register
がリゾルバー出力がそのスキーマに適合していることを保証します。biff.graph
ヒントとベストプラクティス
- ファイル構成: リゾルバーを
配下に配置し、各ネームスペースがmodel/
を設定した:biff.graph/resolvers
モジュールを公開します。biff.core - UI コンポーネント: hiccup を返すリゾルバーを作成することも可能です(例:ボタンやモーダルなど)。これらを
配下に配置し、特定のデータモデル向けの再利用可能なコンポーネントとします。ui_components/(defresolver ... {:com.example/my-component (fn [{:keys [href]}] [:div ...])}) - グローバルリゾルバー: インプットクエリを持たない「グローバル」リゾルバーもあります。
- 特定の条件を満たす全ユーザーを照会したいバックグラウンドジョブ用。
から明示的な ID が不要なセッションデータ返信用。ctx
- 認証(パラムス): パラメータからエンティティ ID を受け取り、権限確認を行いジョインとして返すリゾルバーを作成します。
:output [{:params/widget [:widget/id]}]- 無権限の場合:例外を投げるか、出力からエンティティを省略するかのいずれかを選択可能です(HTTP 4XX への転換方法を探求中)。