mqtt-plus 架构解析(十):从内部项目到开源框架,mqtt-plus 的抽取过程与决策

mqtt-plus 架构解析(十):从内部项目到开源框架,mqtt-plus 的抽取过程与决策

摘要

很多框架的开源并不是"把内部代码推到 GitHub"这么简单。真正困难的地方在于:哪些能力是通用框架能力,哪些仍然带着业务包袱;哪些 API 在内部能凑合用,到了开源之后却必须重新定义;哪些范围应该先做稳,哪些需求应该刻意延后。mqtt-plus 的形成就是这样一次抽取与重设边界的过程。本文会基于整个系列前 9 篇已经拆开的模块与实现,回到更高层的问题:mqtt-plus 为什么能从 drone-framework 里的 MQTT 能力沉淀成一个独立开源框架,以及这一路上做了哪些关键取舍。

项目地址

项目地址:

https://github.com/mqttplus/mqtt-plus

配套的示例工程:

https://github.com/mqttplus/mqtt-plus-examples

如果你对这个方向感兴趣,欢迎关注、试用,也欢迎一起交流 issue 和 PR。

如果这篇文章对你有帮助,欢迎点赞、收藏,也欢迎给项目一个 Star。


到了第 10 篇,系列终于回到了起点。

前面 9 篇其实一直在做同一件事:

  • 把 mqtt-plus 现在的结构拆开
  • 把每个模块边界讲清楚
  • 把每一处实现取舍落到真实源码上

但这些文章还有一个更大的隐含问题:

  • 为什么 mqtt-plus 会长成现在这样?
  • 为什么它不是一个"顺手做出来的工具包",而是一组边界比较清楚的模块?
  • 为什么它的很多实现看起来都偏克制,像是刻意砍掉了一些"也许能做"的能力?

答案其实藏在它的来源里:

mqtt-plus 不是从空白画布上设计出来的理想框架,而是从内部项目里的重复能力、混杂边界和长期维护痛点里,一步步抽出来的。

这也决定了第 10 篇应该讲的,不是"起源故事",而是抽取方法本身。

一、这篇文章到底想回答什么?

这一篇只回答三个问题:

  • 哪些能力应该从内部项目里抽出来,成为通用框架能力
  • 哪些业务包袱必须留在原项目里,而不能带进开源框架
  • mqtt-plus 为什么会选择今天这种边界清楚、范围克制、模块分层明确的形态

如果只记住一句话,那就是:

mqtt-plus 的开源,不是把内部代码复制出来,而是先把"什么属于框架、什么属于业务"这条边界重新画了一遍。

二、先看从内部项目到开源框架的演进图

先把整个演进过程压成一张图。
drone-framework: 4 MQTT-related modules
Repeated connection / listener / bridge logic
Identify reusable framework concerns
Separate business logic from framework logic
Redesign stable public abstractions
Split into layered modules
mqtt-plus open-source framework

这张图里最重要的,不是"最后开源了",而是中间那三步:

  • 识别可复用能力
  • 把业务逻辑和框架逻辑切开
  • 重新设计稳定抽象

如果少了任何一步,最终都很容易变成:

  • 不是框架,而是一份"脱敏过的内部代码"
  • 看起来能复用,实际上边界还是内聚在原业务模型上
  • 开源之后 API 很快失稳,因为它本来就不是为外部用户设计的

这也是 mqtt-plus 现在很多"克制感"的真正来源。

三、第一步不是开源,而是先识别"什么算框架能力"

从第 1 篇开始我们就反复提到,mqtt-plus 的前身来自 drone-framework 里 4 个 MQTT 相关模块。

那一步真正暴露出来的问题,不是"模块数量多",而是:

  • 连接能力在重复
  • listener 注册和调用逻辑在重复
  • 订阅恢复逻辑在重复
  • 错误处理、转换链和 broker 管理都开始散落在不同模块里

一旦系统走到这里,就会出现一个很典型的信号:

这些东西已经不再是"某个业务场景的实现细节",而是开始表现出框架级共性。

也就是说,真正值得抽出来的,不是"某个设备模型"或者"某个领域 topic 规范",而是这些在不同模块里都要重复回答的问题:

  • broker 连接如何抽象
  • listener 如何注册和路由
  • payload 如何转换
  • 连接恢复和动态订阅如何协调
  • Spring 如何与 core 能力粘合

这一层识别非常关键,因为很多内部项目的开源失败,不是因为代码差,而是因为第一步就抽错了:

  • 把业务场景里的偶然性误当成了框架能力
  • 把内部项目里临时可用的 API 误当成了通用抽象

mqtt-plus 现在能维持比较稳定的模块边界,本质上就是因为第一步抽出来的,基本都是"跨场景重复成立"的问题。

四、第二步更难:哪些东西必须留在原项目里?

一个内部项目要开源,最难的地方往往不是"什么能抽出来",而是:

什么不能带出来。

如果把这一步做不好,开源项目很容易出现两种问题:

  • 明明是框架,却带着大量业务命名、业务默认值和场景耦合
  • 明明想做通用能力,却不断为原项目的历史兼容包袱服务

mqtt-plus 现在的边界其实已经很能说明这个取舍。

比如当前仓库里被明确放进"当前范围之外"的就有:

  • mqtt-plus-hivemq
  • MQTT 5.0 支持
  • 运行时动态修改 broker 连接信息

这些能力不是不重要,而是它们要么还没有达到稳定抽象状态,要么还不适合进入当前这一版框架的公共承诺。

同样,像设备领域对象、行业 topic 规范、无人机场景里的专属桥接逻辑,也没有跟着一起进入开源框架。

这说明 mqtt-plus 在抽取时其实做了一个很重要的动作:

  • 把"框架共性"抽出来
  • 把"原项目负担"留在原项目里

Yes
No
Yes
No
Existing internal MQTT code
Business-specific?
Keep in original project
Reusable framework concern?
Extract into mqtt-plus
Discard or redesign

这张图的价值在于,它说明了一个很现实的开源原则:

不是所有内部代码都值得开源;很多代码更适合被留在原项目里,或者干脆在抽取时重写。

设计决策: mqtt-plus 没有试图把内部项目里所有 MQTT 相关代码一并带出来,而是刻意把业务模型、场景特定桥接逻辑和尚未稳定的能力留在原项目或延后范围之外。这样做的重点,是让开源框架只对真正稳定、可复用的能力负责。

五、第三步:开源不是复制代码,而是重新设计公共抽象

真正决定 mqtt-plus 能不能成立的,其实不是"有没有代码",而是:

  • 有没有重新设计可以对外承诺的抽象
  • 有没有把内部实现里的偶然写法,收敛成稳定 API

这一点,从整个系列里已经很容易看出来。

比如这些对象与接口,本质上就不是"随手提炼"的结果,而是一次重新设计后的公共边界:

  • MqttClientAdapter
  • MqttClientAdapterFactory
  • MqttListenerRegistry
  • MqttMessageRouter
  • PayloadConverter / PayloadSerializer
  • MqttSubscriptionManager
  • MqttSubscriptionReconciler
  • MqttTemplate

这些抽象有一个共同特点:

  • 它们不带业务词汇
  • 它们不直接绑定某个具体客户端实现
  • 它们足够小,但能拼成完整链路

这其实就是"内部可用代码"和"开源框架代码"的本质区别。

内部代码很多时候只需要:

  • 先让当前项目跑起来
  • 兼容现有调用方
  • 对外部使用者没有稳定承诺压力

但开源框架不一样。它需要的是:

  • 抽象名字清楚
  • 依赖方向长期稳定
  • 模块边界足够清晰
  • 扩展点能让别人理解并接入

这也是为什么 mqtt-plus 最终会长成:

  • core
  • adapter
  • spring
  • starter
  • test

这类结构,而不是简单把原来那 4 个内部模块换个名字重新发布。

六、第四步:开源后的边界为什么反而更克制?

很多人对开源有一个误解,觉得一旦开源,就应该"把能做的都做进去"。

但对框架来说,真正更成熟的做法往往相反:

  • 越是对外承诺,就越要控制范围
  • 越是想稳定演进,就越要先收住边界

mqtt-plus 当前的范围其实就很能说明这一点。

从 README 可以看到,这一版明确写了:

  • 已包含:mqtt-plus-coremqtt-plus-pahomqtt-plus-spring-integrationmqtt-plus-springmqtt-plus-spring-boot-startermqtt-plus-test
  • 暂缓:mqtt-plus-hivemq、MQTT 5.0、运行时动态修改 broker 连接信息

这说明开源后的第一优先级并不是"覆盖一切场景",而是:

  • 先把当前边界内的能力做到结构稳定
  • 让使用者知道什么已经是承诺,什么还不是
  • 避免在还没稳定之前就把过多路线一口气暴露成公共 API

如果结合前 9 篇一起看,你会发现这种克制几乎贯穿全系列:

  • interceptor 只做前后钩子,不承担错误决策
  • ErrorAction 已经建模,但协议层动作闭环还没完全打通
  • 动态订阅恢复模型已经有了,但动态 qos 还没被完整持久化
  • starter 聚焦装配,不把所有策略都参数化

这些都不是"没做完"的简单表象,更像是一种很清楚的开源节奏:

先把边界定义对,再逐步扩张能力。

设计决策: mqtt-plus 开源后的范围比内部实现更克制,不是因为能力不够,而是因为对外 API 一旦形成承诺,后续演进成本会急剧上升。先收住范围、先稳住边界,是比"功能先做满"更理性的开源策略。

七、从整个系列回看,哪些取舍最能说明"这是一个框架",而不是"一份内部代码导出"?

如果把前 9 篇一起回看,我觉得最能体现这个转变的,至少有 5 个点。

1. core 零框架依赖

这说明开源后的第一承诺是稳定内核,而不是先服务某个生态接入方式。

2. adapter 可插拔

这说明 transport 选择不是框架公共语义的一部分,而是可替换实现的一部分。

3. Spring 层只做粘合

这说明框架没有把 Spring 经验直接写死进核心抽象里。

4. 测试体系单独成模块

这说明"如何验证框架"也被当成产品化能力,而不是只在项目内部临时写点测试。

5. 范围被明确写进 README

这说明框架已经开始对外管理预期,而不是无限延展。

换句话说,mqtt-plus 真正开源出来的,不只是若干实现类,而是一套对外可解释、可约束、可维护的结构。

八、这一篇真正想给读者留下什么?

第 10 篇如果只停留在"它来自内部项目",其实信息量是不够的。

我更希望它留下的是一个更通用的判断标准:

当你想把内部项目里的某部分能力抽成开源框架时,真正该先问的不是:

  • 现在这份代码能不能跑
  • 有没有人会 Star
  • 要不要先发一个版本

而是:

  • 哪些能力跨场景重复成立
  • 哪些能力只是当前业务的局部最优
  • 哪些边界已经稳定到值得对外承诺
  • 哪些范围还应该先留在项目内部慢慢长

如果用这个标准回头看 mqtt-plus,你会发现它最重要的地方其实不是"支持了哪些功能",而是:

它在抽取过程中先把边界想清楚了。

这也是为什么整个系列虽然讲了很多实现细节,但最后仍然会收束到一个更高层的结论:

  • 好框架不是功能堆出来的
  • 好框架往往是边界收出来的

九、小结

第 10 篇是整个系列的收束篇。

如果把它压缩成几句结论,大概就是:

  • mqtt-plus 的起点不是空白设计,而是内部项目里长期重复出现的 MQTT 共性问题
  • 真正被抽出来的,不是所有内部代码,而是那些跨场景可复用的框架能力
  • 真正被留下的,不是"不重要"的部分,而是那些仍带有业务负担、边界还不稳定、或者暂时不适合公共承诺的能力
  • 开源之后的 mqtt-plus 比内部实现更克制,这不是退步,而是为了让模块边界、API 承诺和后续演进都更稳

如果前 9 篇回答的是"mqtt-plus 现在为什么这样工作",那第 10 篇回答的就是:

它为什么值得以现在这种形态存在。

这也意味着整个系列到这里就闭环了。

  • 第 1 篇讲为什么这样分层
  • 第 2 到第 9 篇讲这些分层各自承载什么责任
  • 第 10 篇则回到源头,解释这些边界为什么会被画成现在这样

系列导航

本文是 mqtt-plus 架构解析 系列的第 10/10 篇。

# 主题 链接
1 总览:分层架构与设计哲学 链接
2 消息路由:一条 MQTT 消息如何到达你的 @MqttListener 链接
3 Payload 序列化与反序列化:双链设计的取舍 链接
4 拦截器链:MqttMessageInterceptor 的扩展点设计 链接
5 错误处理:ErrorAction 聚合策略的设计逻辑 链接
6 多 Broker 管理:如何让一个应用同时连接多个 MQTT 服务 链接
7 动态订阅与重连恢复:Reconciler 的协调机制 链接
8 Spring Boot 自动装配:零件是怎么被粘合起来的 链接
9 测试体系:MqttTestTemplateEmbeddedBroker 的设计 链接
10 从内部项目到开源框架:mqtt-plus 的抽取过程与决策 本文

上一篇:测试体系:MqttTestTemplateEmbeddedBroker 的设计

下一篇:无

相关推荐
冬奇Lab4 小时前
一天一个开源项目(第72篇):everything-claude-code - 最系统化的 Claude Code 增强框架
人工智能·开源·资讯
OpenTiny社区6 小时前
重磅预告|OpenTiny 亮相 QCon 北京,共话生成式 UI 最新技术思考
前端·开源·ai编程
CoovallyAIHub7 小时前
视频理解新范式:Agent不再被动看视频,LensWalk让它自己决定看哪里
算法·架构·github
CoovallyAIHub7 小时前
斯坦福丨AirVLA:将地面机械臂模型迁移至无人机实现空中抓取,成功率从23%提升至50%
算法·架构·github
一 乐8 小时前
酒店预订|基于springboot + vue酒店预订系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·酒店预订系统
竹之却8 小时前
【Agent-阿程】OpenClaw智能体架构深度解析与实战应用
架构·大模型应用·ai框架·openclaw
qq_454245038 小时前
通用引用管理框架
数据结构·架构·c#
独特的螺狮粉8 小时前
云隙一言:鸿蒙Flutter框架 实现的随机名言应用
开发语言·flutter·华为·架构·开源·harmonyos
格鸰爱童话9 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习