你不是 Model,就是 Harness——代码角色的清醒划分

2026-06-10 33 预计阅读时间: 1 分钟
来源: my.oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

一句简短的话把系统里的每一行代码逼到了墙角:"If you're not the Model, you're the Harness." 要么你是核心领域逻辑,要么你是为它服务的脚手架。没有第三种身份。

这个判断看似粗暴,实际上是一把裁纸刀——沿缝一划,架构里那些模棱两可的"既像业务又像基础设施"的代码立刻露出真面目。问题随之而来:谁主导谁?是代码去设计 Harness(先搭架子再填业务),还是 Model 驱动 Harnesses(先定领域再配基础设施)?

每个模块只有一张工牌

Model 是做出决策、承载规则、表达意图的部分。Harness 是让 Model 能跑起来的所有外围支撑:配置加载、日志埋点、数据库连接、HTTP 路由、测试夹具……它们不产生业务判断,只负责把 Model 的判断送到正确的地方。

模糊地带最危险。一段代码既读配置又做业务计算,既管事务又管折扣规则——它同时挂两张工牌,结果两张都挂不稳。重构时你不知道该把它归到 domain 包还是 infra 包,测试时你不知道该 mock 外部依赖还是构造纯领域输入。

Harness 先行:架子搭好,业务来填

很多项目从脚手架开始:选框架、建目录、配路由、写基类,然后"业务逻辑往里塞"。这种路径的标志是——你先决定了 Harness 的形状,Model 只能削足适履。

# Harness 先行:框架基类决定了 Model 的形状
class BaseController:
    """框架提供的基类,所有业务 Controller 必须继承"""
    def __init__(self, db_session, logger, config):
        self.db = db_session
        self.logger = logger
        self.config = config

    def get_request_param(self, key: str):
        # 框架从 HTTP request 中提取参数
        ...

    def respond_json(self, data, status=200):
        # 框架负责 JSON 序列化与 HTTP 响应
        ...

class OrderController(BaseController):
    """业务被迫继承框架基类,领域逻辑与基础设施交织"""
    def apply_discount(self, order_id: str):
        discount_rate = float(self.config.get("discount_rate", "0"))
        order = self.db.query(Order).filter_by(id=order_id).first()
        if order is None:
            self.logger.warning(f"Order {order_id} not found")
            return self.respond_json({"error": "not found"}, 404)
        order.total = order.original_price * (1 - discount_rate)
        self.db.commit()
        self.logger.info(f"Discount applied: {order_id}")
        return self.respond_json({"order_id": order_id, "total": order.total})

apply_discount 里,折扣规则、数据库查询、日志、HTTP 响应全部挤在一起。Model 的判断(折扣怎么算)被 Harness 的形状(基类注入了 db、logger、config)牢牢框住。换数据库、换传输协议、换日志库,都得改这段"业务代码"。

Model 驱动:领域先立,Harness 随形

反过来,先把 Model 写成纯领域对象——不依赖任何基础设施,只依赖它自己需要的概念。然后 Harness 围绕 Model 的接口来适配。

# ── Model:纯领域逻辑,零基础设施依赖 ──
from dataclasses import dataclass
from decimal import Decimal

@dataclass
class Order:
    id: str
    original_price: Decimal

    def apply_discount(self, rate: Decimal) -> Decimal:
        """折扣规则完全在 Model 内部,不依赖任何外部服务"""
        if rate < 0 or rate > 1:
            raise ValueError(f"Invalid discount rate: {rate}")
        self.total = self.original_price * (1 - rate)
        return self.total


# ── Harness:适配层,把 Model 接到外部世界 ──
from flask import Flask, request, jsonify

app = Flask(__name__)

# Harness 负责加载配置、连接数据库、构造 Model 实例
DISCOUNT_RATE = Decimal("0.10")  # 实际项目从 config/env 读取

def get_order_from_db(order_id: str) -> Order | None:
    """Harness 把数据库行映射为纯领域对象"""
    # 假设使用 SQLAlchemy;此处省略 session 管理
    row = db_session.query(OrderRow).filter_by(id=order_id).first()
    if row is None:
        return None
    return Order(id=row.id, original_price=Decimal(row.original_price))


@app.post("/orders/<order_id>/discount")
def apply_discount_endpoint(order_id: str):
    """Harness 负责协议细节,Model 只管业务判断"""
    order = get_order_from_db(order_id)
    if order is None:
        return jsonify({"error": "not found"}), 404
    try:
        total = order.apply_discount(DISCOUNT_RATE)
        db_session.commit()  # Harness 管事务
        return jsonify({"order_id": order.id, "total": str(total)})
    except ValueError as e:
        return jsonify({"error": str(e)}), 400


if __name__ == "__main__":
    app.run(debug=True)

关键变化:Order.apply_discount 只知道价格和折扣率,不知道数据库、HTTP、日志。Harness(Flask 路由 + DB 映射)围绕 Model 的公开方法来编排。换框架?重写 Harness,Model 一字不改。

用测试验证角色归属

最直接的检验方式是看测试能不能跑。Model 的测试不需要任何基础设施 mock——纯领域对象在内存里就能验证全部规则:

# Model 测试:零 mock,零外部依赖
from decimal import Decimal

def test_apply_discount_normal():
    order = Order(id="A1", original_price=Decimal("100.00"))
    result = order.apply_discount(Decimal("0.20"))
    assert result == Decimal("80.00")

def test_apply_discount_rejects_invalid_rate():
    order = Order(id="A1", original_price=Decimal("100.00"))
    try:
        order.apply_discount(Decimal("1.5"))
        assert False, "Should have raised"
    except ValueError:
        pass  # 预期异常

如果一段"业务测试"需要 mock 数据库、mock HTTP 请求、mock 配置中心——说明你的 Model 已经泄漏进了 Harness 的领地。该拆了。

采纳建议与自检清单

检查项 Model 应该 Harness 应该
依赖方向 只依赖其他 Model 或标准库 依赖 Model + 外部基础设施
测试方式 纯内存,无需 mock 需要 mock/stub 外部服务
改动触发 业务规则变化 框架/协议/基础设施变化
包位置 domain/ / core/ infra/ / adapters/ / handlers/

实操路线:

  1. 拿一个最核心的业务规则,把它从 Controller/Service 里抽成纯函数或纯类。 不带 db、不带 logger、不带 config 参数——只接收领域输入,返回领域输出。
  2. 为它写一个无 mock 的单元测试。 如果写不出来,说明抽取不彻底,回去再拆。
  3. 把原来混在一起的数据库/HTTP/日志代码推到 Harness 层。 Harness 调用 Model 的方法,Model 不调用 Harness 的任何东西。
  4. 新需求来时,先问:这是 Model 的变化还是 Harness 的变化? 答案决定你改哪个文件、加在哪个包下。

"If you're not the Model, you're the Harness" 不是一句口号,是一把尺子——量每一行代码,它到底在决策还是在服务。量清楚了,谁驱动谁就不再是争论:Model 定规则,Harness 做搬运,方向只有一个。


相关推荐