跳转至

layer-violation

Category: 模块结构
Severity: Configurable via architecture.rules[].severity
Triggered by: pyscn analyze, pyscn check --select deps

检测内容

当源模块所在层不允许依赖目标模块所在层时,标记该 import 语句。层的允许关系根据你配置的 [[architecture.rules]] 来判定。层通过匹配 [[architecture.layers]] 中定义的包名片段来分配给模块。

为什么这是一个问题

分层架构只有在层级保持完整时才有价值。从 presentationinfrastructure 的一次捷径就足以:

  • 破坏可测试性。 表示层现在只能在背后有真实数据库/HTTP 客户端的情况下才能进行测试。
  • 产生隐藏的耦合。 替换基础设施实现会悄悄破坏本不应知道其存在的 UI 代码。
  • 使违规正常化。 一旦存在一个捷径,下一个就更容易被合理化。

此规则是你在设计文档中已经画好的架构图的自动化执行。

示例

配置:

[[architecture.layers]]
name = "presentation"
packages = ["api", "handlers"]

[[architecture.layers]]
name = "application"
packages = ["services", "usecases"]

[[architecture.layers]]
name = "infrastructure"
packages = ["repositories", "db"]

[[architecture.rules]]
from = "presentation"
allow = ["application"]
deny = ["infrastructure"]

违规代码:

# myapp/api/orders.py  (presentation)
from myapp.repositories.orders import OrderRepository   # ← forbidden

def list_orders():
    return OrderRepository().all()

presentation 越过 application 直接访问了 infrastructure

修正示例

通过应用层路由调用:

# myapp/services/orders.py  (application)
from myapp.repositories.orders import OrderRepository

def list_orders():
    return OrderRepository().all()
# myapp/api/orders.py  (presentation)
from myapp.services.orders import list_orders

def get():
    return list_orders()

api 现在只依赖 services,基础设施可以在不触及表示层的情况下进行替换。

选项

选项 默认值 描述
[[architecture.layers]] 定义层及属于每层的包名片段。
[[architecture.rules]] from / allow / deny / 可选的每条规则 severity
architecture.validate_layers true 设为 false 可禁用此规则。
architecture.strict_mode true 严格模式下,未显式允许的依赖一律被拒绝。
architecture.fail_on_violations false 发现违规时以非零退出码退出。

未配置层时,分析器以宽松模式运行,此规则不会产生任何发现。

参考