clara-rulesは異るエンティティ間のジョイン操作に使えるという知見を講演の動画から得たので、自分で簡単な例を実装してみた。
問題
- TypeA
- 属性attr-aとjoin-keyを持つ
- TypeB
- 属性attr-bとjoin-keyを持つ
- TypeC
- 共通のjoin-keyを持つTypeA.attr-aとTypeB.attr-bを属性に持つ
コード
(ns clara-rules.join
(:require [clara.rules :refer [defrule fire-rules insert insert! mk-session]]))
(defrecord TypeA [attr-a join-key])
(defrecord TypeB [attr-b join-key])
(defrecord TypeC [attr-a attr-b join-key])
(defrule Join
[TypeA (= ?jk-a join-key) (= ?a attr-a)]
[TypeB (= join-key ?jk-a) (= ?b attr-b)]
=>
(insert! (->TypeC ?a ?b ?jk-a)))
(defrule Print
[?result <- TypeC];; Will always match
=>
(println ?result))
(-> (mk-session)
(insert (->TypeA "a1" :foo)
(->TypeA "a2" :bar)
(->TypeA "a3" :baz)
(->TypeB "b1" :foo)
(->TypeB "b2" :bar)
(->TypeB "b3" :baz)
(->TypeB "b4" :qux))
(fire-rules))
;;#clara_rules.join.TypeC{:attr-a a1, :attr-b b1, :join-key :foo}
;;#clara_rules.join.TypeC{:attr-a a2, :attr-b b2, :join-key :bar}
;;#clara_rules.join.TypeC{:attr-a a3, :attr-b b3, :join-key :baz}
- 狙いの通り共通の
:join-key
を持つTypeAとTypeBのペアに対してTypeCがプリントされている。やったね! -
(->TypeB "b4" :qux)
は同じ:join-key :qux
を持つTypeAのファクトが無いため結果に表れない。なるほど。
defrule左辺
の<-
の説明
ファクトの属性ではなく、ファクトそのものを変数に束縛する。
[?result <- TypeC]
を例にとると、マッチしたTypeCのインスタンス全体が?resultに束縛される
感想
- インメモリのジョインのロジックを、SQLとは異るアプローチで宣言的に書くことができる
- 例えばDB上のジョイン操作が水平スケーラビリティのボトルネックになっている場合に、ジョイン操作をアプリケーションの方でインメモリで行うように変更するのに使えるかもしれない。
- あるいはデータはあるけどそれクエリをかけることができない環境とかで役に立つかもしれない