第八章

架构与治理:当代码变成大泥球

第十一层 · 分层/DDD/微服务/架构评审 加一个功能要动十几个文件

一、泥球定律:代码是怎么烂掉的

小陈的预感没错。进入第八个月,云间书店的代码库已经变成了一个"大泥球":

  • 结算、库存、会员、支付、报表……逻辑全都缠在一起;
  • 一个功能改一处,崩三个地方;
  • 新人入职三个月,还没摸清代码全貌;
  • 每次"小改动"都要全组心惊胆战。

老周带小陈看代码:

# 结算模块(第 872 行)——为什么这里会 import 库存模块?!
from inventory import check_stock
from members import get_member_level
from reports import gen_report   # 报表也混进来了??

def checkout(cart, user):
    # 结算逻辑里硬塞了会员积分、库存扣减、甚至报表生成……
    check_stock(cart)
    level = get_member_level(user)
    points = level * 10
    gen_report(cart, points)
    return "ok"

"看出来了吗?结算模块不该知道库存怎么扣、积分怎么算、报表怎么生成。 但它全都知道——这就是耦合(Coupling)过高。模块之间纠缠不清,牵一发而动全身。"

"这就是 Big Ball of Mud(大泥球) 架构:没有边界、没有分层、所有东西互相依赖。 泥球不是一天形成的,是一年一年'顺手加功能'堆出来的。"

小陈:"那怎么办?推倒重写?"

老周摇头:"推倒重写是最大的陷阱——重写期间业务还在跑,你边写边补,最后写出第二个泥球。正确姿势是'演进式重构':把泥球一点一点切开。这一章教你怎么切。"


二、架构治理的第一原则:边界

"架构(Architecture)的本质是什么?"老周问,"很多人以为是'用微服务'、'用高并发'……都不对。架构的本质是划边界——决定什么该在一起,什么该分开。"

好架构的标志:
  - 模块之间边界清晰(这个模块不该知道那个模块的内部)
  - 改动局部化(改库存,不动结算)
  - 依赖单向(上层依赖下层,不循环)
  - 可测试(每个模块能独立测试)

坏架构的标志(泥球):
  - 到处 import,循环依赖
  - 改一处崩三处
  - 没人能说清系统全貌
  - "这个函数为什么在这?"——没人知道

"架构治理,就是一套'防止泥球继续变大的规矩'。"


三、分层架构:最简单有效的第一刀

"切开泥球的第一步:分层(Layering)。"老周画了个经典的三层架构:

         ┌─────────────────────┐
         │  表现层(界面/API)    │  ← 接收请求,展示数据
         ├─────────────────────┤
         │  业务逻辑层(服务)    │  ← 核心业务规则(打折/满减/积分)
         ├─────────────────────┤
         │  数据访问层(数据库)  │  ← 只管读写数据库
         └─────────────────────┘
             依赖方向:从上往下,单向!

"铁律:上层可以调用下层,下层绝不能反向调用上层;同层之间也要松耦合。 表现层不知道数据库长什么样,数据层不知道页面长什么样。"

# 分层后的结算模块
# 表现层:只管收请求、返回结果
@app.route("/checkout")
def checkout_api(cart, user_id):
    result = checkout_service.checkout(cart, user_id)   # 调业务层
    return jsonify(result)

# 业务层:只管业务规则,不知道 HTTP、不知道数据库
class CheckoutService:
    def checkout(self, cart, user_id):
        total = self.calc_total(cart)          # 业务规则
        if not self.inventory.check(cart):     # 通过接口依赖,不直接碰库存表
            raise OutOfStock()
        return total

# 数据层:只管读写数据库
class InventoryRepo:
    def query(self, book_id):
        return db.fetchone("SELECT stock FROM books WHERE id=?", book_id)

"分层是最古老也最实用的架构模式。对云间书店这种体量,三层就够了,别一上来就上微服务。"


四、领域驱动设计(DDD):按"业务"划边界,不是按"技术"划

"分层是按技术切(界面/业务/数据)。但还有另一种切法:按业务领域切。"老周说,"这就引出 DDD(Domain-Driven Design,领域驱动设计)。"

"DDD 的核心思想:软件的边界,应该跟业务的边界对齐。 书店的业务天然分成几个'域':"

云间书店的领域划分:
  ├── 订单域(下单、结算、退款)
  ├── 库存域(入库、出库、盘点)
  ├── 会员域(等级、积分、优惠券)
  ├── 支付域(收款、对账、退款)
  └── 报表域(统计、导出)

每个领域内部自成一体:
  - 有自己的模型(订单、库存快照、会员等级……)
  - 有自己的数据(不随便读别人域的表!)
  - 通过明确的"接口"与其他域通信(领域服务/事件)

"关键概念:限界上下文(Bounded Context)——'会员'这个词,在会员域和订单域里含义不同(会员域管等级积分,订单域只关心'这个用户有没有会员折扣')。每个域在自己的边界内定义自己的模型,别互相渗透。"

"DDD 的实战价值:当业务很复杂时,代码的边界跟着业务走,业务专家和程序员用同一套语言沟通。 但对简单业务别过度设计——DDD 是为复杂业务准备的武器。"


五、微服务:不是银弹,是"分布式泥球"

"媒体天天吹微服务,但我要给你泼冷水。"老周说,"微服务(Microservices)就是把大泥球切成很多个小泥球,再用网络连起来。 它解决'大团队并行开发'和'独立扩缩容'的问题,但引入了一大堆新问题:"

微服务的代价:
  - 分布式事务(跨服务扣款,怎么保证原子性?)
  - 服务间调用失败(网络会断,怎么重试/降级?)
  - 调试困难(一个请求跨 5 个服务,日志怎么串起来?)
  - 运维复杂度爆炸(部署、监控、版本兼容……)

所以行业共识:
  - 团队 < 15 人:别碰微服务,模块化单体(Modular Monolith)更合适
  - 单体 → 微服务,应该是"业务自然长成这样",不是为了赶时髦

"云间书店现在这个规模,老老实实把'模块化单体'做好——代码按领域分包,边界清晰,但还是一个应用一起部署。等哪天某个域(比如支付)流量爆炸、需要单独扩容,再把它拆出去。"


六、架构治理机制:怎么防止泥球复活

"架构不光是设计,更是治理——没有机制,过两个月又糊回去。"老周列出四个机制:

6.1 架构评审:大改动先过审

架构评审规则:
  - 涉及跨模块的重大改动 → 提前拉架构评审会
  - 评审看什么:边界是否清晰?依赖方向对不对?有没有引入泥球?
  - 结论要记录(ADR),防止同样的问题吵第二次

6.2 依赖规则检查:让机器守边界

"人的评审会漏,让机器盯!用工具强制依赖方向:"

# 依赖检查工具(如 Python 的 import-linter)
# 规则:reports 模块禁止 import models.inventory 内部的类
# 违反规则 → CI 红灯,合并被拦

"把架构规则写进 CI,让机器强制执行——这是架构治理最硬的一招。"

6.3 演进式架构:小步重构,不搞革命

"切泥球不搞'周末大重构',要小步演进:"

演进式重构步骤:
  1. 找最痛的一小块(如:结算模块里 import 报表那行)
  2. 用"绞杀者模式":新代码走新路径,旧代码暂时保留
  3. 新路径稳定后,删掉旧代码
  4. 每次改动都跑测试(第四章的测试兜底)
  5. 一次只切一小块,两周内可完成,风险可控

"就像给老房子改造:一堵墙一堵墙地改,改完一堵再改下一堵,房子一直在住。 千万别'推倒重建'——那叫拆迁,不叫改造。"

6.4 架构决策记录(ADR):为什么这么定

"最后,每个重大架构决策,写一份 ADR(Architecture Decision Record):"

ADR-007:结算模块采用分层架构
  背景:结算逻辑混入库存/积分/报表,耦合过高,改动频繁崩
  决策:按表现层/业务层/数据层分层,业务层通过接口依赖数据层
  后果:改动局部化,可测试性提升;代价是需要逐步迁移(约 6 周)
  替代方案:直接上微服务 —— 被否,团队 8 人,复杂度不匹配

"ADR 是团队的'为什么档案'——半年后新人问'为什么这里这么设计',不用猜,看文档。"


七、章末:老周的第十一层总结

第十一层:架构与治理
├── 边界        → 架构的本质是划边界(什么该在一起,什么该分开)
├── 分层架构    → 表现层/业务层/数据层,依赖单向
├── 领域驱动(DDD) → 边界跟着业务走,限界上下文
├── 微服务      → 不是银弹,小团队用模块化单体
└── 治理机制    → 架构评审 / 依赖检查进CI / 演进式重构 / ADR

"小陈,这一层讲完,软件工程管理的'人的部分'就基本齐了:需求、协作、质量、流程、风险、架构……每一层都是在回答同一个问题:怎么让一群人长期、稳定、高质量地做软件。"

老周正要收白板,手机响了。他看了一眼,表情变得微妙起来。

"师傅,怎么了?"

"王姐说,她要给技术部买一个'AI 编程助手',让大家都用上。"老周放下手机,"她还说,隔壁公司已经在用 AI 自动写代码、自动修 bug 了,我们再不跟上就落后了。"

小陈眼睛发光:"AI 写代码!师傅,这会不会把咱们都替代了?"

老周笑了:"问得好。这正是我们要讲的最后一层——AI 时代的软件工程。走吧,这一章,我们聊聊:当 AI 加入团队,管理会发生什么变化。"

✌ 语言