Files
ShadowsocksX-NG/docs/adr/0029-source-layout-declares-layer-and-theme.md
T
Qiu Yuzhou a31e2fa4bf docs(adr): 记录源码目录即分层与主题及守卫约定
App/ 按分层、Domain/ 按主题组织,两个根层只放子目录、不放平铺文件;边界由
Tests/SkeletonTests.swift 的守卫正面圈定断言,而不是禁令加豁免登记。

ADR-0029 只记录组织原则本身:分层与主题划分的依据(Domain 主题名取自
GLOSSARY.md 的章节划分)、四条被否决的备选(平铺加豁免登记、feature-first、
Domain 照搬分层、守卫锁定目录集合),以及两条在代码里不可见的 XcodeGen
约束和跨进程契约按文件引用的代价。

ADR 不维护目录与文件清单——清单由 ShadowsocksX-NG2/README.md 的工程结构
一节维护,避免记录随事实漂移。

project.yml 的两处分层注释与 README 的工程结构一节各加一处引用:前者按该
文件既有写法引用编号,后者用完整链接。
2026-10-05 20:27:11 +08:00

3.5 KiB
Raw Blame History

源码目录即分层与主题,边界由守卫断言

Status: Accepted (2026-10-05)

App/ 按分层组织(Composition / Application / Presentation / UI / PlatformEffects / Resources),Domain/ 按主题组织,主题名取自 GLOSSARY.md 的章节划分(配置与订阅、代理规则、代理控制与网络、设置、凭据与诊断),避免同一概念在文档与目录里有两个名字。两个目录的根层只放子目录、不放平铺文件——目录本身就是架构声明。

边界由 Tests/SkeletonTests.swift 的守卫断言,且一律正面圈定而非禁令加豁免登记:视图层的守卫只扫 App/UI/(不得引用运行时控制器、工作流实现协作者、原始目录/凭据存储类型),Domain/ 的守卫只断言根下无平铺 .swift、树下只有 .swift。把文件放进 Application/、PlatformEffects/ 或某个主题目录,即自动落在扫描范围之外,无需登记豁免;新增主题目录也不需要改断言。

各目录的划分与各自职责由 ShadowsocksX-NG2/README.md 的工程结构一节维护——本 ADR 只固定组织原则,不维护清单。

Considered options

  • 平铺 + 禁令表 + 豁免登记:守卫默认扫描整个目录,再逐个登记要放行的例外。每加一个运行时适配器都要改守卫,豁免清单本身成了需要维护的知识,而「这个文件属于哪一层/哪个主题」仍然无处声明。
  • feature-first(按功能纵向切分):把某个功能的领域类型、工作流与视图放进同一个目录。与既有分层冲突——工作流依赖的运行时控制器与目录存储会变成视图的同层邻居,边界退化成不可断言的约定。
  • Domain/ 也照搬分层:领域内部不存在 App/ 那样的层间依赖方向(没有「领域视图」),照搬只会得到一个叫 Services 的杂物箱。
  • 让守卫锁定主题目录集合:即正向列举「允许的目录」。未采纳——断言应当固定「不留平铺文件」这条不变量,而不是把主题划分冻结成一份需要同步维护的清单;新增主题目录是正常演进。

Consequences

XcodeGen 带来两条在代码里不可见的约束:

  • 源路径的 excludes glob 不跨 /:裸文件名只匹配被扫描目录的根层。Core 测试镜像因此必须用 **/MainApp.swift 这类递归形式;文件下移后旧写法会静默失效,把 @main 组合根编进测试镜像。
  • App/、Domain/ 整目录是 source path,其中任何非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 app bundle。因此文档不放 App/(project.yml 排除 **/*.md),Domain/ 则由守卫禁止出现非 .swift 文件。

跨进程契约按文件引用而非按目录:wrapper 与 system proxy helper 两个 target 各自显式列出所需的 Domain/ 文件,因为它们只该拿到跨进程契约,而不是整个领域层。代价是目录内移动文件必须同步 project.yml;漏改时 xcodegen 以 missing source directory 直接失败,不静默。

在目录内移动文件因此是低代价操作:模块名、类型名与 import 均不变,task format 与 lint 基线不变,版本历史可继续追溯。这条约定分散记录在 project.yml 的分层注释、ShadowsocksX-NG2/README.md 的工程结构一节与本 ADR;新增主题目录时只需同步前两处(DomainLayoutTests 失败信息里列举的目录名会过期,断言本身不会失败)。