架构笔记:Service 逻辑解耦与 Domain Interface 落地

1. 问题起因:Service 承担了太多转换逻辑

目前后端代码比较突出的问题是 Service 层里 Transform (Mapper) 偏多。Service 本应聚焦业务步骤,但现在需要处理各种类型转换:

  • Adapter 接口定义不清:比如 Service 在调用第三方 Client 拼 SOAP 报文时,新增一个 Adapter,它的入参应该用什么类型?是用第三方的格式,还是用我们自己定义的 Domain Model?

  • Domain Model 缺失

    • Entity 绑定 DB 结构,不适合充当 Domain Model。

    • DTO/Types 混合了前端请求和临时定义,也不适合充当 Domain Model。

  • 现状:缺少统一的业务模型,导致 Service 成了类型转换的汇集点。

2. DDD 的思路:以业务模型为中心

按照 DDD 的思路,基础设施层应适配业务模型(Domain Model):

  • 第三方 Adapter:应依赖 Domain Model,负责将我们的标准数据转换为第三方接口要求的格式。

  • Repository:应接收 Domain Model。Service 不需要负责构建 Entity,存取逻辑交由 Repo 内部处理。

  • Controller:在入口处将 DTO 转换为 Domain Model。

3. DDD 落地难题:技术与现实的双重阻力

A. 技术层面的“手动挡”痛点 (LB4 vs. Java)

  • 级联与脏检查缺失:Java 的 Hibernate 支持 Dirty Checking,开发者修改 Model 属性后,框架会自动同步到多张表。LB4 下需要手动处理 patch

  • 加载策略差异:Java 支持 Lazy Loading(延迟加载),可以定义大而全的聚合根且不影响性能;LB4 需要显式 include,否则就得在 Service 里写零散的查询。

  • 持久化细节溢出:LB4 强制一表一 Repo,导致更新聚合根时的事务编排、Diff 对比都堆积在 Service 里。

B. 现实层面的“深层阻力”(核心痛点)

  • 认知惯性与成本:团队习惯了 CRUD 的开发方式。引入 Domain Interface 意味着多写一层转换(Mapper),在交付压力大的时候,容易被看作“浪费时间”。

  • “改不动”的恐惧:老代码里 DTO、Entity 和业务逻辑深度耦合。要把这些混杂的 types 拆分出来,往往牵一发动全身,很难保证不出问题。

  • 收益隐形化:解耦带来的收益(易维护、测试友好)是长期的,而重构的成本(工时、回归测试)是眼前的。在“业务优先”的环境下,这类改动不容易争取到资源支持。

4. 最小改动方案:渐进式落地 (Touch-and-Go)

由于阻力较大,暂时不做大范围的架构升级,先采取以下务实步骤:

  • 第一步:确立主权 (Domain Interface)
    从现有 types 中提炼出标准的 Domain Interface。老逻辑暂不改动,先给新功能设定规范。

  • 第二步:建立防腐层 (Adapter & Mapper)
    针对痛点较明显的第三方服务(如 SOAP),强制其 Adapter 依赖 Domain Interface。把字段映射这类枯燥的工作从 Service 中移出去。

  • 第三步:持久化下沉 (Smart Repository)
    在 Repository 层封装多表操作。Service 只需调用 save\(domainInterface\),内部处理具体细节,减少 Service 的视觉干扰。

  • 第四步:增量渗透
    遵循 Touch-and-Go 原则:改到哪,修到哪。不为了重构而重构,而是在业务变动的间隙,顺带完成解耦。

5. 最终总结:架构主权的“防腐”与务实演进

1. 隔离变化:建立三道防火墙
我们的核心目标不是消除转换,而是通过物理隔离,把“体力活”从业务逻辑中抽离出来:
接口协议变了(DTO) → 只改 Mapper
数据库表结构变了(Entity) → 只改 Repository
核心业务规则变了(Logic) → 只改 Domain Service/Logic

2. 务实主义:承认限制,划出净土
不盲目遵循 DDD 教条(比如非要实现 Java 那种透明异步加载或脏检查),而是针对 LB4 的现状做折中:
手动挡的封装:既然框架没有“全自动”功能,就把多表 Diff 和事务编排这些脏活限制在 Smart Repository 内部,不溢出到 Service。
接口即主权:利用 TypeScript 的 Interface 特性,在不改动底层老旧 Entity 的前提下,定义出一套相对纯净、统一的业务语言。

3. 落地策略:Touch-and-Go(增量渗透)
真正的阻力来自“改不动”的恐惧和交付压力,所以暂时不做大开大合的调整:
改到哪,修到哪:不为了重构而重构。只在业务变动触及相关逻辑时,顺手把旧的映射逻辑移到 Mapper,把第三方报文拼接放进 Adapter。
从局部清爽开始:通过一个个“干净”的 Service 示范,让团队逐渐感受到解耦后维护成本的降低,慢慢从“搬运工”模式转向“指挥官”模式。

评论