架构设计
系统总览
理解 Legendary Invention 当前的单体微服务形态、请求主链路和架构演进方向。
当前架构是什么
当前主线不是“完全拆开的多进程微服务”,而是:
- 运行时采用单个后端入口
legendary-server - 代码仍保留模块化微服务边界
- 所有外部请求统一从
/api进入
这可以理解为“单体微服务”或“模块化单体的聚合运行形态”。
为什么这样设计
这样做的目标是同时得到两类收益:
- 运行更简单:本地和生产默认只维护一个后端进程
- 边界更清晰:认证、系统、文件、消息等模块仍能独立治理
它适合当前阶段的团队效率,也为未来重新拆分服务保留了演进空间。换句话说,系统不是因为“微服务失败了”才变成聚合运行,而是主动选择了一个更容易稳定交付、同时不牺牲边界治理的形态。
请求主链路
frontend -> /api -> api-proxy -> legendary-server -> module -> database/cache/outbox在这个链路里:
- 前端只关心
/api api-proxy负责统一代理和入口治理legendary-server负责实际承载业务模块
如果以后重新拆为物理微服务,前端这层调用路径仍然应该不变。变化只应该发生在代理层和后端部署拓扑里,而不是业务页面里。
主要模块
auth:登录、刷新 token、二次认证、Passkey、微信登录system:用户、角色、菜单、配置、审计、AI、监控file:上传下载、文件元数据、存储空间治理message:站内消息、推送、归档plugin:插件包与插件运行时localization:语言、命名空间、词条、发布job:后台任务与 outbox relay
模块之间应该怎么协作
当前仓库虽然聚合运行,但模块之间仍然要遵守边界。推荐的协作方式是:
- 共享通用基础设施:通过
libs/common-* - 共享跨模块契约:通过
libs/legendary-api - 共享异步扩展点:通过 Outbox 事件
- 共享用户、安全、租户、Trace 上下文:通过统一基础设施
不推荐的方式是:
- 一个模块直接修改另一个模块的表
- controller 绕过应用服务直接改别人数据
- 为了一个页面需求临时复制一套权限逻辑
- 前端根据猜测拼接后端内部地址
共享原则
- 公共能力应放到
libs/* - 跨模块通信优先通过
libs/legendary-api中的契约 - 不要把模块边界因为“都在一个进程里”就随意打穿
现阶段最重要的几个 owner 维度
1. 数据 owner
一张表由谁定义、迁移、维护、解释,谁就是 owner。即使聚合运行,也不要跨模块直接把别人的表当成自己表来用。
2. 权限 owner
登录态、菜单显隐、按钮权限、数据范围不是一回事。认证、IAM、业务服务分别承担不同职责,不能混写。
3. 生命周期 owner
文件、消息、插件、知识库文档、角色授权这些对象的生命周期应由 owner 模块决定,其他模块只能通过契约或事件协作。
4. 事件 owner
异步事件必须由业务事实发生的 owner 模块发布,不要由外围模块“代发一个差不多的事件”。
对开发最有影响的架构结论
结论 1:新增功能先找 owner,再找目录
不要先问“这个代码放哪个包最顺手”,先问它属于谁、谁维护它、谁校验它、谁发布它的事件。
结论 2:前端统一走 /api
这不是风格问题,而是为了把未来部署变化隔离在代理层。
结论 3:单入口不代表统一大泥球
当前架构最怕的不是“模块太多”,而是“因为只剩一个进程,就开始无边界地共享实现”。那样后续无论维护还是拆分都会更难。
演进方向
未来如果需要拆回物理微服务,建议按模块 owner 顺序逐步拆分,例如:
- 先挑一个高边界清晰、高独立性的模块
- 补齐它的表归属、权限边界、事件边界和内部 API
- 再把运行入口从聚合进程中独立出去
也就是说,当前架构不是终点,但它已经是一个可长期运行的稳定形态。