一開始的任務只有一句話:在 S3 放一份可隨時修改的 file,應用程式讀取後「照條件做事」
筆者我很自然就想:那我用 functional programming 一個個解析條件式並進行匹配不就好了?
真的動手寫後,問題一個個浮現:
筆者開始意識到:我們其實需要的是「描述條件的語言」以及「穩定的執行引擎」
這就是 query engine 存在的理由
def eligible?(user)
return false unless user
over_18 = user[:age] && user[:age] >= 18
from_tw = user[:country] == 'TW' || user[:region] == 'ASIA-TW'
has_ruby = Array(user[:tags]).any? { |t| t[:name] == 'ruby' }
(over_18 && from_tw) || has_ruby
end
當需求變成「18 歲以上、且標籤含 ruby、且狀態 active 或 pending」,你會再加一層 OR/AND 嗎?很快地,這段 code 就會難以閱讀與測試
{
"age": { "$gte": 18 },
"$and": [
{ "status": { "$in": ["active", "pending"] } },
{ "tags": { "$elemMatch": { "name": "ruby" } } }
]
}
JSON 很適合「攜帶資訊」,但它不是「程式語言」
如果只是 parse 成 Ruby Hash,再用 if/else 解讀一次,本質問題依舊:你只是在另一個地方重寫一套解讀器
一致的語意:同一份條件,在任何資料結構(Hash、自定義物件)上,結果一致
可觀測性:能 explain(樹狀結構)、能 trace(逐步匹配),方便排錯
可擴充:新增運算子(如 $every
)不影響既有邏輯
效能:條件只解析一次,後續以可執行的 matcher tree 直接跑
require 'mongory'
Mongory.enable_symbol_snippets!
Mongory.register(Array)
records = [
{ 'name' => 'Jack', 'age' => 18, 'status' => 'active', 'tags' => [{ 'name' => 'ruby' }] },
{ 'name' => 'Jill', 'age' => 15, 'status' => 'pending', 'tags' => [{ 'name' => 'rails' }] },
{ 'name' => 'Bob', 'age' => 21, 'status' => 'inactive', 'tags' => [{ 'name' => 'ruby' }] }
]
condition = {
"age": { "$gte": 18 },
"$and": [
{ "status": { "$in": ["active", "pending"] },
{ "tags": { "$elem_match": { "name": "ruby" } }
]
}
records.mongory.where(condition).each { |r| p r }
差異在於:這不是把 JSON 轉成 if/else,而是把條件「編譯成 matcher tree」,比對時直接執行每個 matcher 的邏輯
Mongory 的目的不是替代 DB,而是把「查詢語意」帶到記憶體世界
典型情境 1:
先用 DB 取出一批資料(走索引)
再用 Mongory 在應用層處理「沒有索引的欄位」或「更複雜的陣列條件」
典型情境 2:
典型情境 3:
單子條件解包(unwrap):避免多餘的 AND 包裝,減少 callstack
$in
/$nin
對 Array 的「交集」語意:與單值 include? 不同
Array 欄位的包裝:{ tags: 'a' }
會自動轉為 $elemMatch
Explain 與 Trace:診斷複雜條件的必備工具
今天我們了解了「為什麼需要 Query Engine」:
它讓條件成為「可閱讀、可測試、可擴充」的語言,而不是散落的 if/else
它把 JSON 的資料攜帶能力,補上「可執行語意」
它提供 explain/trace,補足了篩選過程的「可觀測性」
明天(Day 3)我們會正式走進 matcher tree 的世界,從 eq/field/and
開始,把各個元件拆解清楚;後面會談進階運算子($gt/$gte/$in/$nin/$elemMatch
)與為什麼需要單子條件解包
如果讀者您在專案中也有「應用層過濾」的需求,歡迎在 Issue 分享你的情境~