我的文件庫

多根目錄側邊欄

flex-layout

  • Getting Started

  • Guides

  • Reference

typed-rx-http

  • Getting Started

  • Guides

  • Reference

global-rx-state

  • Getting Started

  • Guides

  • Reference

webflux-fe-dev-assistant

reactive-mongo-dsl

概述

MongoDB對於最初是RDBMS開發者的我來說,作為一個小查詢助手,隨著實際項目需求的增長,從1.0.0開始成為Driver-first MongoDB便利層的歷史和設計標準。


起源故事

我在RDBMS領域開發了約四年,並且對運行環境大多熟悉AWS RDS。在開始新的個人項目時,我選擇了MongoDB,隨著了解的深入,我感覺它不僅僅是一個靈活的數據庫,而是一個能夠大幅提升我開發生產力的工具。

特別是MongoDB Atlas對我來說幾乎是新世界。我可以在一個數據平台內使用專業搜索和向量搜索,而不必考慮將其連接到單獨的系統,備份、監控和擴展等運行工作在我以AWS RDS為標準的情況下也變得更加方便。如果需要更專業的規模和功能,可以將搜索引擎或向量數據庫分離為單獨的服務,但在快速構建和運行一個項目的階段,Atlas能夠解決的範圍非常吸引人。

問題在於對於初次接觸MongoDB的我來說,編寫動態查詢的速度非常慢。起初,repository的findBy...形式已經足夠,但隨著查詢條件變得複雜,我不得不直接組合QueryCriteria和運算符表達式。在不熟悉的情況下,重複組合條件容易出錯,持續編寫相同形式的代碼也大大降低了開發速度。

從查詢用簡易 DSL 開始

因此,我最初創建的是一個簡單的類,用於實現CRUD中的R。這是目前名為ReactiveMongoDsl的類的起點。當時,aggregation、pipeline、lookup等功能完全不存在,只是稍微加快了Spring Data ReactiveMongoTemplateQueryCriteria和分頁的組合。其餘的工作我認為使用基本的ReactiveMongoTemplate或MongoDB Driver就足夠了。

但與我一起工作的開發者的想法不同。他也是第一次接觸MongoDB,每當需要upsert或bulk等操作時,總是先詢問我創建的DSL是否具備該功能。我本可以告訴他直接使用基本API,但我認為他也會面臨我最初學習MongoDB時的困難。因此,我開始在每次需要功能時,逐步將其添加到ReactiveMongoDsl中。

最初大約有600行,當達到1,000行時,我認為沒有必要將其分成多個類。當達到2,000行時,我稍微考慮了一下,但為了分離而設計的結構反而感覺更龐大,最重要的是,老實說,這項工作讓我感到厭煩。當超過5,000行時,我再也無法拖延,將一些可分離的功能移到外部,但要細分核心流程已經變得非常困難。核心類別龐大的原因並不是因為從一開始就設計了龐大的DSL,而是因為在實際項目中不斷堆疊所需的功能。

在添加了查詢、聚合、lookup、原子更新、bulk、歷史、Atlas Search和Vector Search後,突然發現我在Java項目中使用MongoDB時所需的功能大部分都已經包含在內。我將其分離出來,以便在多個項目中以相同的方式使用,而不是僅僅作為一個項目內部的助手,這就是reactive-mongo-dsl的誕生過程。

從Spring助手到Driver-first庫

起點是為了更方便地使用ReactiveMongoTemplate的助手,但隨著功能的增長和在多個項目中的重用,我認為特定框架的執行模型本身不應成為庫的邊界。1.0.0的核心是將MongoExecutionContext作為執行契約,並直接使用MongoDB Reactive Streams Driver。

這一變化並不是為了排除Spring Data MongoDB。在Spring應用程序中,可以將ReactiveMongoTemplate的集合命名、MongoConverter、自定義轉換和反應式審計連接到MongoExecutionContext適配器。核心獨立於框架,僅在需要的應用程序中繼續使用現有的Spring設置。

同時,MongoDB Driver已經很好地提供的功能不會在DSL中重新實現的方向也變得更加明確。一般的聚合可以直接接收Bson階段,Search/Vector則提供SearchOperatorVectorSearchQuerydriverOptions(...)stage(Bson)等逃生口。即使Driver首先提供新功能,也希望能夠在不等待DSL下一次發佈的情況下使用。

開發哲學

不會隱藏MongoDB

我並不想創建一個將MongoDB的功能轉換為其他數據庫概念的ORM。我將MongoDB的查詢、聚合、事務、Atlas Search和Vector Search以更簡短和易於發現的流程連接起來。

僅添加實際需要的功能。

這不是一個先設計功能列表再實現的庫。因為在運行項目中需要查詢,所以添加了查詢功能;因為需要 upsert 和批量操作,所以添加了這些功能;並且因為需要搜索功能,所以添加了 Atlas Search 和 Vector Search。

優先考慮生產力而非結構的完美性

核心類別的大小並不被認為是理想結構。在開始時,團隊以相同的方式快速處理不熟悉的 MongoDB 操作是更重要的問題,現在仍然優先考慮實際使用流程而非抽象本身。

不會重複 Driver 已經在做的事情

如果 MongoDB Java Driver 有類型化建構器,則儘可能使用該類型。DSL 提供的便利 API 可以有價值地減少重複組合,但不會僅僅改名再複製一次 Driver API。

應該能夠降級到基本 API

不認為 DSL 可以替代所有情況。保留 Bson、Driver filter/sort/operator/options、publisher customizer 等逃生口,必要時可以直接使用 MongoDB Driver。

當前文檔中處理的四種流程

一般 Mongo 查詢

這是這個庫開始的流程。按照 execute* → fields(...) → end() → find/findAll/count/delete/exists/atomicUpdate 的順序組織條件和執行。

Driver-native 聚合

如果需要從 pipeline 的第一個階段開始直接控制,或者想要直接使用 Driver 新提供的聚合功能,則使用 aggregation().stage(Bson) 流程。像 $score$scoreFusion 這樣 Driver 已經提供的階段,DSL 不會重新實現而是直接傳遞。

Atlas 搜索

在不先引入單獨的搜索服務的情況下,將 Atlas Search 應用於項目時添加的流程。search(index) 後面組合 text、compound、score、highlight 和 sequence token 等。

向量搜索

為了在 MongoDB 中連接嵌入式搜索而添加的流程。組合查詢向量或自動嵌入、ANN/ENN、前/後過濾器和嵌套/數組嵌入選項。

1.0.0 的執行模型
text

將一般查詢與搜索/向量分開,是為了不隱藏 MongoDB 的實際 pipeline 限制。$search$vectorSearch 有第一階段的限制,整個第一階段的功能必須由調用者控制,可以降級到 aggregation()

在實際項目中使用的範圍

將沒有條件的對應項傳遞為 null,以組裝每個畫面的動態搜索條件。

通過 PageStream 將數據和 totalCount 分開,保持大批處理流程為反應式發布者狀態。

通過 atomicUpdate().upsertOne().document().setOnInsert(...) 創建安全的重複執行操作。

根據 ID 或業務鍵進行批量 upsert,保存收集的數據和外部集成數據。

通過 executeLookupAndCount 在一個 pipeline 中返回查詢結果和總數。

使用 Atlas Search 的序列 token 和 Vector Search 的自動嵌入查詢。

Driver 提供的新聚合階段直接連接到 aggregation().stage(Bson) 或搜索/向量的 stage(Bson)

如果 DSL 操作之間需要原子性,則通過 getTxJob(...) 明確指定 ClientSession 事務範圍。

© 2026 Byeolnaerim. 版權所有。介紹隱私政策