练习目标
把一个“能跑但别扭”的课程平台模块,整理成更优雅的结构。
这一节重点不是重新讲本体论,而是应用第四章的美学标准:
- 简洁:少歧义
- 一致:少意外
- 局部:少牵连
- 可组合:部件能自然拼接
- 概念完整性:整体像一个系统
- Flow:改起来顺手
- 品味:提前感到未来会疼
起点:一个别扭系统
假设系统里有这些文件:
services/
UserService.ts
CourseManager.ts
PaymentProcessor.ts
utils/
accessHelper.ts
roleUtils.ts
api/
course.ts
admin.ts
workers/
emailWorker.ts
里面散落着这样的逻辑:
if (
user.role === "student" &&
user.status === "active" &&
user.purchasedCourseIds.includes(course.id)
) {
return true
}
另一个地方:
if (
user.isAdmin ||
user.role === "instructor" ||
user.assignedCourseIds.includes(course.id)
) {
allowAccess()
}
后台里:
if (admin.role === "super_admin") {
course.status = "active"
}
邮件 worker 里:
if (user.courseIds.includes(course.id)) {
sendWelcomeEmail(user.email)
}
这个系统可能能跑,但它不优雅。
它为什么别扭
1. 名字太宽
UserService
CourseManager
accessHelper
roleUtils
这些名字没有告诉我们边界。
2. 规则散落
课程访问规则出现在 API、后台、worker、utils 里。
改访问规则会牵动很多地方。
3. 概念混用
role
isAdmin
assignedCourseIds
purchasedCourseIds
courseIds
这些字段把身份、权限、访问权、购买事实混在一起。
4. 状态命名不一致
user.status = active
course.status = active
subscription.status = active
每个 active 含义都不同。
5. 调用方知道太多
每个调用方都要知道如何判断课程访问权。
这说明缺少可信边界。
目标结构
整理后的模块可以像这样:
modules/
accounts/
accounts.ts
credentials.ts
organizations/
memberships.ts
invitations.ts
course-catalog/
courses.ts
publication.ts
course-access/
grants.ts
policy.ts
revocation.ts
explanation.ts
tests/
learning/
enrollments.ts
progress.ts
billing/
purchases.ts
refunds.ts
subscriptions.ts
outbox.ts
notifications/
notifications.ts
audit/
audit-log.ts
这个结构不一定适合所有项目,但它展示了几个美学目标:
- 名字更具体
- 规则有归属
- 访问权有专门模块
- billing 不直接拥有 access 规则
- notification 只响应事件
- audit 是显式能力
第一步:建立统一入口
先不要大改数据模型。
先把散落判断收进统一函数:
async function canAccessCourse(accountId: AccountId, courseId: CourseId) {
// 暂时内部仍然可以调用旧逻辑
}
然后让调用方逐步改成:
const decision = await canAccessCourse(accountId, courseId)
而不是自己判断:
user.purchasedCourseIds.includes(course.id)
美学收益:
- 调用方更简洁
- 访问规则开始局部化
- 后续重构有边界
第二步:统一返回模型
不要只返回 boolean。
访问判断应该可解释:
type AccessDecision =
| { allowed: true; reason: "valid_grant" }
| {
allowed: false
reason:
| "no_active_grant"
| "account_suspended"
| "course_archived"
}
这样 API、后台、客服、日志都可以复用同一个 decision。
美学收益:
- 错误模型一致
- 调试路径清楚
- 用户提示和客服解释更稳定
第三步:把规则移到 course-access
目标:
modules/course-access/
policy.ts
grants.ts
revocation.ts
explanation.ts
policy.ts:
canAccessCourse(accountId, courseId)
grants.ts:
grantAccessFromPurchase(purchaseId)
grantAccessFromSubscription(subscriptionId)
grantAccessFromOrganizationAssignment(assignmentId)
revocation.ts:
revokeAccessAfterRefund(refundId)
revokeAccessForSuspendedAccount(accountId)
explanation.ts:
explainCourseAccess(accountId, courseId)
美学收益:
- 相关规则靠近
- 新增访问来源有自然落点
- support dashboard 可以复用 explanation
第四步:整理命名
把含糊命名替换成明确概念。
UserService -> accounts / profiles / memberships
CourseManager -> course-catalog / course-access / learning
accessHelper -> course-access/policy
roleUtils -> organizations/memberships or permissions
把状态名具体化:
user.status -> AccountStatus
course.status -> CoursePublicationStatus
subscription.status -> SubscriptionStatus
enrollment.status -> EnrollmentStatus
美学收益:
- 少歧义
- 少误用
- 更容易形成概念完整性
第五步:统一事件和副作用
购买成功后,不要让 billing 直接发送所有通知、更新所有报表。
更优雅:
billing creates purchase
-> course-access grants access
-> outbox emits course.purchase.completed
-> notifications sends email
-> analytics updates projection
-> audit records event
这不是为了上复杂事件架构。
而是让副作用有秩序。
美学收益:
- billing 不知道所有下游
- 副作用可追踪
- 失败可重试
- 模块更可组合
第六步:统一后台操作流程
后台操作不要各自为政。
例如课程下架、手动撤销访问权、手动退款,都应该有一致流程:
权限检查
-> 展示影响范围
-> reason 必填
-> 二次确认
-> 执行 command
-> audit log
-> 返回可追踪结果
这让系统既更安全,也更优雅。
第七步:改善开发 flow
整理后,新增“兑换码访问”应该是自然路径:
modules/course-access/grants.ts
-> add source coupon
modules/course-access/policy.ts
-> include valid coupon grant
modules/course-access/tests/
-> add coupon access tests
support dashboard
-> automatically shows grant source
如果这个需求不再需要全局搜索和到处 patch,说明美学改善成功。
最终结构的美学检查
整理后问:
- 名字是否更具体?
- 访问规则是否更局部?
- API 是否更一致?
- 错误和 reason 是否统一?
- 新访问来源是否有自然落点?
- 调用方是否知道更少内部细节?
- 测试是否更贴近规则边界?
- 后台操作是否更可追溯?
- 新人能否通过目录结构理解系统?
小结
- 别扭系统常见问题是名字宽、规则散、状态混、调用方知道太多。
- 优雅重构可以先从统一入口开始,不必大爆炸。
canAccessCourse这类函数能把规则收进可信边界。- 返回 decision 而不是 boolean,会改善解释、调试和客服体验。
- 目录结构应该让新需求有自然落点。
- 副作用要有秩序,不要散在主流程里。
- 美学改善的最终证据是:下一次变化更顺手。