我的文档库

多根目录侧边栏

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领域开发了大约4年,运营环境也大多熟悉AWS RDS。在开始新的个人项目时,我选择了MongoDB,随着了解的深入,我意识到它不仅仅是一个灵活的数据库,而是一个可以显著提高我开发生产力的工具。

尤其是MongoDB Atlas对我来说几乎是一个新世界。我可以在一个数据平台内使用专业搜索和向量搜索,而不再需要连接额外的系统,备份、监控、扩展等运维工作在我以往只使用AWS RDS的标准下也变得更加方便。如果需要更专业的规模和功能,可以将搜索引擎或向量数据库分离为单独服务,但在快速构建和运营一个项目的阶段,Atlas能够解决的范围非常吸引人。

问题在于,作为MongoDB的新手,我发现编写动态查询的过程非常缓慢。起初,repository的findBy...形式就足够了,但随着搜索条件的复杂化,我不得不直接组装QueryCriteria和运算符表达式。在不熟悉的情况下,重复的条件组装容易出错,而不断编写相同形式的代码也大大降低了开发速度。

从查询用简易 DSL 开始

因此,我最初创建的是一个简单的类,用于实现CRUD中的R。这是现在名为ReactiveMongoDsl的类的起点。当时没有聚合、管道、查找等功能,只是稍微加快了Spring Data ReactiveMongoTemplateQueryCriteria和分页的组装。其余的工作我认为直接使用基本的ReactiveMongoTemplate或MongoDB Driver就足够了。

但与我一起工作的开发者有不同的看法。他也是MongoDB的新手,每当需要upsert或bulk等操作时,总是先问我创建的DSL是否具备这些功能。我本可以让他直接使用基本API,但我认为他也会经历我初学MongoDB时的困难。因此,我开始在ReactiveMongoDsl中逐步添加所需的功能。

最初大约600行,当达到1000行时,我认为没有必要将其拆分为多个类。当达到2000行时,我有些犹豫,但为了分离而构建的结构反而感觉更庞大,最重要的是,老实说,这项工作让我感到麻烦。当超过5000行时,我再也无法推迟,部分可分离的功能被移出,但要细致地重新划分核心流程的负担已经很大。核心类之所以庞大,并不是因为从一开始就设计了一个庞大的DSL,而是因为在实际项目中不断积累所需功能的历史。

在添加了查询、聚合、查找、原子更新、批量、历史、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应用程序中,可以通过MongoExecutionContext适配器连接ReactiveMongoTemplate的集合命名、MongoConverter、自定义转换和反应式审计。核心保持独立于框架,只有需要的应用程序才继续使用现有的Spring设置。

同时,MongoDB Driver已经很好地提供的功能在DSL中不再重复实现的方向也更加明确。一般聚合可以直接接收Bson阶段,Search/Vector中提供了SearchOperatorVectorSearchQuerydriverOptions(...)stage(Bson)等逃生口。即使Driver首先提供新功能,也希望能够在不等待DSL下一个版本发布的情况下使用。

开发哲学

不隐藏MongoDB

我并不是想创建一个将MongoDB的功能转换为其他数据库概念的ORM。我的目标是将MongoDB的查询、聚合、事务、Atlas Search和Vector Search以更简短、易于发现的流程连接起来。

仅添加实际需要的功能。

这不是一个先设计功能列表再实现的库。由于在运营项目中需要查询,因此添加了查询功能;由于需要 upsert 和 bulk,因此添加了这些功能;为了使用搜索功能,添加了 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 原生聚合

如果需要从管道的第一个阶段开始直接控制,或者想要使用 Driver 新提供的聚合功能,则使用 aggregation().stage(Bson) 流程。DSL 可以直接传递 Driver 已提供的阶段,如 $score$scoreFusion,而不再实现。

Atlas 搜索

在没有先引入单独搜索服务的情况下,将 Atlas Search 应用到项目中时添加的流程。在 search(index) 后构建 text、compound、score、highlight 和 sequence token 等。

向量搜索

为了在 MongoDB 内部连接嵌入搜索而添加的流程。构建查询向量或自动嵌入、ANN/ENN、预/后过滤器和嵌套/数组嵌入选项。

1.0.0 的执行模型
text

将普通查询与搜索/向量分开是为了不掩盖 MongoDB 的实际管道限制。$search$vectorSearch 有第一个阶段的限制,调用者必须控制整个第一个阶段的功能可以降级到 aggregation()

在实际项目中使用的范围

将没有条件的对作为 null 传递,以组装屏幕特定的动态搜索条件。

通过 PageStream 将数据和 totalCount 分开,保持大规模处理流程为反应式发布者状态。

通过 atomicUpdate().upsertOne().document().setOnInsert(...) 创建安全的重复执行操作。

通过 ID 或业务键的批量 upsert 存储收集数据和外部集成数据。

通过 executeLookupAndCount 在一个管道中返回查找结果和总记录数。

使用 Atlas Search 的序列令牌和 Vector Search 的自动嵌入查询。

Driver 提供的新聚合阶段可以直接连接到 aggregation().stage(Bson) 或搜索/向量的 stage(Bson)

如果 DSL 操作之间需要原子性,可以通过 getTxJob(...) 明确 ClientSession 事务范围。

© 2026 Byeolnaerim. 保留所有权利.介绍隐私政策