oh-my-muse/.agents/rules/bc-boundaries.md
lili d38260fc51 feat(p1-market): market 写路径整改——member 暴露写端口,消除 market→member.dal BC 违例(BC 门全绿)
market 5 类(MarketAccountProjectionProvider/AdminMarketReviewServiceImpl/MarketInstallServiceImpl/
MarketLicenseServiceImpl/MarketPublishServiceImpl)此前直接构建 member.dal 的 AccountRecordProjectionDO
并经 AccountRecordProjectionMapper upsert 进 member 表——跨 BC 写路径违例(原 BcBoundaryArchTest 单点豁免登记)。

本次整改(ultracode 工作流:设计→实现→对抗验证;JDK21 scoped 实跑验证):
- member-api 新增对外写端口 MuseAccountRecordProjectionApi + MuseAccountRecordProjectionSaveReqDTO;
  member-server MuseAccountRecordProjectionApiImpl 实现,读写 member 自有 DAL,整体平移原 upsert 语义
  (insert/update 分支、update rows!=1 抛 IllegalStateException 防伪成功)。
- 安全边界:DTO 刻意不含 tenantId,由实现侧从 TenantContextHolder 注入,杜绝调用方(他域)伪造租户。
- 事务红线:写端口为进程内 Bean、不加 @Transactional,沿用调用方(market 的 REQUIRES_NEW)事务上下文,
  market 业务回滚则投影一并回滚,原子性与原实现等价。
- market 5 类改消费写端口 + DTO 替代 DO,移除 member.dal 代码依赖;
  BcBoundaryArchTest.KNOWN_VIOLATION_EXEMPTIONS 清空 → 通用 BC 门全绿(0 Architecture Violation)。
- 测试:insert/update/租户隔离语义随实现迁移至 MuseAccountRecordProjectionApiImplTest(4/0F);
  provider 测试改 mock 写端口、保留"失败→写 blocked outbox→上抛 UNAVAILABLE"语义(5/0F);
  market 其余 4 类测试同步(AdminReview 16 / Publish 14 / License 9 / Install 5)。

附带修复 round-2 一处假绿:round-2 把 ContentKnowledgeWorkOwnerFacade 重构为消费 MuseContentWorkOwnerApi 后,
旧测试 KnowledgeWorkOwnerFacadeTest 仍断言旧 WorkMapper 行为(当时验证构建在平台 QiniuSmsClientTest
时区用例处中止、未真正跑到 knowledge 模块,故漏网=假绿)。删除该旧测试,其装配守卫
(@ConditionalOnBean 值应为 MuseContentWorkOwnerApi)与 Unavailable 兜底失败关闭两用例并入
ContentKnowledgeWorkOwnerFacadeTest(3→5/0F),覆盖不丢。

验证(JDK21;-Dtest scoped 避开预存红 + muse-server -am):BUILD SUCCESS,日志无任何 <<< FAILURE/ERROR;
BcBoundaryArchTest 1/0F 且 0 Architecture Violation、AgentsInfraIntegrity 3/0F、ContractFirst 2/0F、
P1rApiCoverage 7/0F、member 4/0F、knowledge 5/0F、content 端口 7/0F、market 5 类全绿,全 reactor 模块 SUCCESS。

注:本仓存在预存红测试(非本轮引入,启用真实测试 + CI 接电后将暴露,已登记 总账/AGENTS 后续):
MuseAiTaskServiceTest 桩 eventPublishOutboxService 缺失致 11 例 NPE、MuseAiEventPublishOutboxMapperTest
需真实 PostgreSQL、平台 QiniuSmsClientTest 硬编码北京时区在非 +8 机器失败。故全量 reactor / CI-on-main
当前仍会因这些预存红呈 RED——本轮只声明 market 整改切片与 BC/契约/loop/覆盖门全绿,不声称全仓全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 06:22:09 -07:00

4.5 KiB

规则:BC 边界 —— 禁跨域 DAL 直连(ArchUnit 机械门禁)

类型:硬约束(rules) · 状态:生效 2026-06-14 · 门禁:ArchUnit(机械阻断,非自觉) 上位:verification-and-anti-false-green.md §二.6 由来:对抗复盘 A4——软约束下 AI 模块 ContentMuseWorkOwnerFacade 直连 content 的 WorkMapper/ChapterMapper/BlockMapper + WorkDO/ChapterDO/BlockDO,实质破了"模块化单体 + 清晰 BC 边界"(ADR)的根基,且会随时间蔓延、拆服务时代价爆炸。


一、规则

  • 一个业务域(BC)模块不得 import / 依赖他域的 DAL(.dal..,含 dataobject 与 mysql Mapper)。
  • 跨 BC 读写只能走对外 API 或 facade-api 契约(本域定义端口,他域提供适配器;或经对外 API)。
  • 本域读自己的 DAL 不受限。

正面范例:P1 增量 2 的 AiSuggestionMergeProjectionFacade —— AI 实现 content 的 ContentAiSuggestionFacade 端口、只读 AI 自有 suggestion 库,不碰 content DAL,即合规的跨 BC 接缝。


二、机械门禁(不是自觉)

  • 测试:muse-server/src/test/java/cn/iocoder/muse/server/framework/arch/BcBoundaryArchTest.java(ArchUnit 1.3.0)。
  • CI:随 muse-cloud/.github/workflows/maven.yml(P0 已打开测试门、JDK21)在 PR/push 阻断。
  • 任何新增跨域 DAL 依赖 → 测试红 → 阻断合入。

三、违例登记与整改

规则现为通用条件(BcBoundaryArchTest.no_business_bc_may_depend_on_another_bc_dal):覆盖全部业务 BC 间方向(ai/content/knowledge/market/meta/member/events),任一域依赖他域 .dal 即红;已知违例在测试的 KNOWN_VIOLATION_EXEMPTIONS 单点显式登记(2026-06-14 评审整改:此前只守 AI 单向,放过了 knowledge/market 同构活违例)。

已整改(历史)

违例类 曾经内容 整改 验收
module.ai.application.muse.facade.ContentMuseWorkOwnerFacade 直连 content Work/Chapter/Block Mapper+DO(字节码 31 处) content 暴露只读端口 MuseContentWorkOwnerApi(content-api 定义、content-server 实现读自有 DAL);AI 适配器改消费该端口 反向删豁免+旧码→红报 31 例;正向整改→绿,适配器 13/0F、端口实现 7/0F
module.knowledge.application.muse.facade.ContentKnowledgeWorkOwnerFacade 直连 content WorkMapper/WorkDO(与 AI 同构) 复用同一 MuseContentWorkOwnerApi.getActiveOwnedWorkRevision 端口,移除 content.dal 依赖 通用门禁绿(0 Architecture Violation)、新增适配器单测 3/0F
module.market.application.muse.{MarketAccountProjectionProvider, AdminMarketReviewServiceImpl, MarketInstallServiceImpl, MarketLicenseServiceImpl, MarketPublishServiceImpl}(5 类) 写路径:构建 member.dal.AccountRecordProjectionDO、经 AccountRecordProjectionMapper upsert 进 member 表 member 暴露写端口 MuseAccountRecordProjectionApi.upsertRecordProjection(DTO)(member-api 定义、member-server 实现读写自有 DAL,整体平移原 upsert 语义且事务上下文沿用调用方);market 5 类改消费端口 + MuseAccountRecordProjectionSaveReqDTO 替代 DO,移除 member.dal 依赖 反向:删豁免 5 类即门禁收紧;正向整改→通用门禁绿(0 Architecture Violation),新增 member 端口实现单测 4/0F、market provider 测试迁移为 mock 写端口

活跃登记(待整改,单点豁免)

违例类 内容 处置 整改方向
(空) —— —— 当前无活跃跨域 DAL 违例;BcBoundaryArchTest.KNOWN_VIOLATION_EXEMPTIONS 已清空

豁免一律临时、显式、可见(测试源码点名)。违例消除后必须从 KNOWN_VIOLATION_EXEMPTIONS 删除该类——收紧即整改验收。新违例不在豁免内,一律红。

遗留(更深一层 BC 收口,不在本次范围):market-server 的 pom 仍依赖 muse-module-member-server(可见 member 全部 internal);本次只消除 member.dal 的代码 import(ArchUnit 校验 import、不校验 pom 坐标),pom 坐标收口可登记后续。


四、扩展方式

  • 规则已是通用条件,自动覆盖所有业务 BC 间的 .dal 跨域依赖,无需为新方向加 @Test
  • 新增业务 BC:加入 BcBoundaryArchTest.BUSINESS_BCS 即纳入约束。
  • 收紧已知违例:整改后从 KNOWN_VIOLATION_EXEMPTIONS 删除该类,门禁随即收紧。
  • 更强边界(如禁跨域 .application 非 facade-api 包耦合):在条件里追加判断,但须为合规 facade 端口留通道(见评审 P1-3,属后续)。