用 import-linter 为 Python 项目声明分层契约并拦截架构腐化

举报
yd_250998817 发表于 2026/10/29 14:00:42 2026/10/29
【摘要】 在 Python 项目中用 import-linter 的 layers 契约声明 domain、application、infrastructure 的依赖方向,并通过 lint-imports 与 CI 拦截 domain 反向依赖和架构腐化。

问题场景

Python 项目初期常把领域模型、用例服务和数据库/HTTP 适配器放在同一包中。随着迭代,domain 里可能出现 from myapp.infrastructure.repo import InMemoryOrderRepo 这类导入,领域层被迫感知存储细节,单元测试变慢,替换实现时牵一发动全身。代码评审也很难持续发现这种反向依赖。

import-linter 可以把分层约定写成契约,在本地和 CI 中做静态导入检查,让 domain 不依赖 application 或 infrastructure。

分层契约

概念上的三层是:

  • domain:实体、值对象、领域规则,不依赖外层。
  • application:用例编排,可依赖 domain。
  • infrastructure:数据库、Web、消息等适配器,可依赖 application 和 domain。

在 .importlinter 中,layers 从上到下表示“上层可依赖下层,下层不可依赖上层”。因此不能把 domain 写在最上面,而应按“外层在前、内层在后”排列:

[importlinter]
root_package = myapp

[importlinter:contract:layers]
name = Clean architecture layers
type = layers
layers =
    myapp.infrastructure
    myapp.application
    myapp.domain

这样 infrastructure 可以导入 application 或 domain,application 可以导入 domain,而 domain 导入 application 或 infrastructure 会失败。如果团队只想先加一条最关键的规则,也可以改用 forbidden 契约,显式禁止 myapp.domain 导入外层模块。

最小可运行示例

目录结构:

myapp/
  __init__.py
  domain/
    __init__.py
    models.py
  application/
    __init__.py
    service.py
  infrastructure/
    __init__.py
    repo.py
.importlinter

myapp/domain/models.py:

from dataclasses import dataclass

@dataclass(frozen=True)
class Order:
    order_id: str
    amount: int

myapp/application/service.py:

from myapp.domain.models import Order

class OrderService:
    def total(self, order: Order) -> int:
        return order.amount

myapp/infrastructure/repo.py:

from myapp.domain.models import Order

class InMemoryOrderRepo:
    def __init__(self) -> None:
        self._orders: dict[str, Order] = {}

    def save(self, order: Order) -> None:
        self._orders[order.order_id] = order

上述代码符合契约:application 只依赖 domain,infrastructure 只依赖 domain。

运行方法

在项目根目录执行:

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
python -m pip install import-linter
lint-imports

正常情况下会输出类似 Contracts: 1 kept, 0 broken. 的结果。为验证拦截效果,可临时在 myapp/domain/models.py 顶部加入:

from myapp.infrastructure.repo import InMemoryOrderRepo

再次运行 lint-imports,会看到契约被破坏,命令以非零状态退出;删除该导入后恢复通过。

在 CI 中拦截

把检查放进测试阶段即可,常见脚本步骤为:

- run: |
    python -m pip install import-linter
    lint-imports

lint-imports 发现反向依赖时返回非零退出码,CI 会直接失败,从而在合并前拦截架构腐化。建议在 CI 中固定依赖版本,避免工具自动升级导致规则行为变化。

边界与限制

  • import-linter 做的是静态导入分析,动态导入、字符串拼接导入无法可靠识别。
  • 使用 src 布局时,要确保 myapp 可被导入,例如安装为可编辑包或设置 PYTHONPATH=src,否则契约无法正确解析模块。
  • 相对导入、命名空间包、条件导入和类型检查块中的导入,建议以实际运行结果为准,必要时补充测试。
  • 契约是开发期约束,不是运行时沙箱,不能替代单元测试和接口设计。
  • 分层不宜过细;小项目可以先用一条 forbidden 规则锁住 domain,再逐步扩展。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。