跳转到内容

架构总览

OpSkool 是 一个底座 + 一组可拼装的能力积木。代码用 pnpm workspace + turbo 组成单仓库,分四层,层与层之间的依赖方向不是口头约定——是机器强制的。

apps/ 可执行进程
web/ Next.js App Router(含 modules-registry.ts 装配点)
jobs/ 定时任务 worker(含自己的 modules-registry.ts)
modules/ 底座与主数据积木(不得 import domains/*)
identity/ 登录、会话、成员、org、scope_units(校区的权威名源)
permissions/ 角色权限、withOrg 事务 + RLS 上下文
platform/ 日志、时钟 now()、错误映射等基础设施
entitlements/ 模块开通判定:来源仲裁 / registry / 额度 / 用量 / 过期
catalog/ 花名册积木:老师·学生·教室·学科 / 班级 / Excel 导入
dashboard/ 看板零件(scheduling 的一部分,不单列积木)
sources/ 数据源 CRUD(本地面;外部接入的实现不在社区版)
domains/ 能力积木(有独立业务闭环)
scheduling/ 排课:冲突、物化、学期节奏、打印导出
collection/ 收集:任务、受众、提交、催办
attendance/ 点名与课消:课时台账、点名扣课消、经营报表四区块、家长单
grades/ 成绩与曲线:考试、成绩录入、成绩序列、成绩单
packages/ 纯包(零应用层依赖)
shared-schema/ zod 契约、错误码、ModuleManifest
conflict-engine/ 纯算法冲突引擎
ui/ 共享控件库(自持皮肤)
db/ drizzle schema + 迁移 + seed
Dockerfile web 与 jobs 共用的多阶段镜像
deploy/ compose(demo / 生产 / 镜像·演示站 override)/ Caddyfile
scripts/ 边界检查等本地工装

逐层的职责:

里面放什么判别标准
apps/可执行进程:web 与 jobsmain、能被 docker run 起来的东西
modules/底座与主数据别的东西要用它,但它自己不构成一条业务闭环
domains/能力积木有独立业务闭环,能被整块开通或关闭
packages/纯包不认识任何应用层概念,可以原样搬到别的项目

五条依赖铁律

章节:五条依赖铁律

.dependency-cruiser.cjs 定义,在 make check 里执行,违反即红:

no-modules-import-domains——modules/* 不得 import domains/*。底座与主数据不能反过来依赖能力积木,否则「关掉排课」就会把花名册也拖下水,积木的可拆装性当场消失。

no-pure-packages-import-app-layers——packages/* 不得 import apps / modules / domains。纯包一旦认识了应用层概念就不再纯,conflict-engine 里就会长出「机构」「校区」这类只有本产品才有的词,再也没法单独测试和替换。

no-identity-import-business——modules/identity 不得 import 任何业务概念。它和 db/schema/identity.ts 合起来是一套永不认识业务的通用身份 + RBAC 核心:它知道「用户」「角色」「范围单元」,但不知道「班级」「课次」。这条守住了,身份核心才能在产品形态变化时原样活下来。

no-closed-package-imports——apps/modules/domains/packages/db/ 都不得静态 import 闭源积木的包名。闭源积木只能由各 apps/*/modules-registry.ts 在运行时按 OPSKOOL_CLOSED_MODULES(见环境变量参考)动态加载——这条守住了「开源核心的代码里找不到任何一行写死的闭源包引用」,即使本机装了闭源包也一样。

no-raw-db-client-outside-permissions——除 modules/permissions 外不得直接拿裸数据库客户端。所有业务查询必须走 withOrg 事务入口,事务内 SET LOCAL app.org_id 打开 RLS 上下文。绕过它 = 绕过租户隔离,见多租户与行级安全

为什么是机械强制而不是约定

章节:为什么是机械强制而不是约定

约定会腐化。赶工期的那天、新人入职的那周、「就这一次」的那个 PR,边界就破了,而破掉的边界不会自己报警——它只会在半年后表现为「这块拆不下来了」。

make check 里的 depcruise 让违反变成一次构建失败。规则不会累,不会赶工期,不会通融。

TypeScript 全栈 · Next.js App Router · PostgreSQL(行级安全多租户)· pnpm workspace + turbo 单仓库 · Docker Compose 部署。

社区版不随附测试用例,pnpm run check(lint + 类型检查 + 依赖方向检查)与 pnpm build 是社区版可用的质量门。


本页是 CE README「仓库结构」一节的展开;两处不一致时,以 README 为准。