一种语言无关的概念,将服务函数内部的业务流程分解为声明式块(sequence)。
问题
现有的SSOT仅覆盖函数外部:
| 现有SSOT | 覆盖范围 | 函数内部? |
|---|---|---|
| OpenAPI | API路径、参数、响应schema | 否 |
| SQL DDL | 表结构、索引、约束 | 否 |
函数内部——“查询 -> 验证 -> 创建 -> 响应"这样的业务流程没有地方可以声明。只有阅读实现代码才能理解。sequence填补了这一空白。
SSOT层级结构
SSaC是构建在现有SSOT之上的第三层。
OpenAPI → API边界(路径、参数、响应)
SQL DDL → 数据边界(表、约束、索引)
SSaC → 函数内部(业务流程、sequence声明)
─────────────────────────────────────────────────
实现代码 → 由codegen生成(错误处理、样板代码)
以上三层负责声明,实现代码从声明派生。人只需编写声明。
核心概念
sequence
将函数内部的执行块类型化的声明单元。
只声明what(做什么),how(怎么做)由codegen填充。
10种固定类型
| 类型 | 作用 | 示例 |
|---|---|---|
| authorize | 权限验证 | 基于action/resource/ID的策略查询 |
| get | 资源查询 | Model.FindByID(id) -> result |
| guard nil | 结果为null时退出 | 不存在则报错并退出 |
| guard exists | 结果存在时退出 | 已存在则报错并退出 |
| post | 资源创建 | Model.Create(fields…) -> result |
| put | 资源更新 | Model.Update(id, fields…) |
| delete | 资源删除 | Model.Delete(id) |
| password | 密码比较 | hash比较失败时退出 |
| call | 外部调用 | 无法用CRUD表达的资源操作,失败时退出 |
| response | 返回响应 | json, view, redirect |
由于类型是封闭的,可以通过按类型模板匹配来生成代码。这是无需LLM即可实现符号化codegen的依据。
model的范围
model不局限于数据库表。任何可以通过CRUD操作的资源都是model。数据库查询、文件存在性检查、外部API调用都可以通过get/post/put/delete统一表达。
语法示例(Go PoC)
虽然SSaC是语言无关的概念,但参考实现使用Go注释语法。由于AI已经理解Go AST,因此没有额外的学习成本。
简单(创建)
// @sequence get
// @model Project.FindByID
// @param ProjectID request
// @result project Project
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence post
// @model Session.Create
// @param ProjectID request
// @param Command request
// @result session Session
// @sequence response json
// @var session
func CreateSession(w http.ResponseWriter, r *http.Request) {}
复合(删除 + 权限 + 验证 + 外部调用)
// @sequence authorize
// @action delete
// @resource project
// @id ProjectID
// @sequence get
// @model Project.FindByID
// @param ProjectID request
// @result project Project
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence get
// @model Session.CountByProjectID
// @param ProjectID request
// @result sessionCount int
// @sequence guard exists sessionCount
// @message "하위 세션이 존재하여 삭제할 수 없습니다"
// @sequence call
// @component notification
// @param project.OwnerEmail
// @param "프로젝트가 삭제됩니다"
// @sequence call
// @func cleanupProjectResources
// @param project
// @result cleaned bool
// @sequence delete
// @model Project.Delete
// @param ProjectID request
// @sequence response json
func DeleteProject(w http.ResponseWriter, r *http.Request) {}
codegen结果
由上述sequence声明派生的实现:
func CreateSession(w http.ResponseWriter, r *http.Request) {
// get
project, err := projectModel.FindByID(projectID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// guard nil
if project == nil {
http.Error(w, "프로젝트가 존재하지 않습니다", http.StatusNotFound)
return
}
// post
session, err := sessionModel.Create(projectID, command)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// response json
json.NewEncoder(w).Encode(map[string]interface{}{
"session": session,
})
}
token成本比较
| sequence声明 | 实现代码 | |
|---|---|---|
| 行数 | 10~15行 | 30~100行 |
| 表达内容 | what(做什么) | how(怎么做) |
| 错误处理 | 无(由codegen生成) | 全部包含 |
| 库依赖 | 无 | import、初始化、调用 |
| 变更时机 | 业务流程变更时 | 重新运行codegen自动反映 |
人只需维护sequence声明。实现代码在声明变更时重新运行codegen即可覆盖。
设计原则
early return——所有类型的通用契约
这是sequence能够以线性排列成立的前提。除response外,所有sequence在失败时立即退出。call也不例外。只需从上到下阅读,无需分支。
| 类型 | 失败条件 |
|---|---|
| authorize | 无权限 -> return |
| get | 查询错误 -> return |
| guard nil | null -> return |
| guard exists | 存在 -> return |
| post/put/delete | 数据库错误 -> return |
| password | 不匹配 -> return |
| call | 失败 -> return |
| response | 最后(返回) |
由于call遵循与guard相同的契约,call之后无需附加单独的guard。codegen会为每个call自动生成错误检查和early return。
@transaction
函数级元数据。不是10种sequence类型之一,而是声明整个函数是否需要事务包装。
// @transaction
// @sequence get
// @model Account.FindByID
// @param AccountID request
// @result account Account
// ...
结合early return结构,事务边界自然形成:
@transaction 已声明
→ guard失败 → rollback + return
→ 最后一个sequence成功 → commit
→ 发生错误 → rollback + return
由于没有分支,事务边界是单一的:“整个函数”。无需指定begin/end位置,codegen仅根据@transaction的有无来决定是否包装。如果需要两个事务,说明有两个职责。请拆分函数。
call——model的特殊形式
call与model本质相同。如果model是用CRUD(get/post/put/delete)表达的资源操作,那么call就是无法用CRUD表达的资源操作。两者都调用具有外部依赖的工作,失败时return。
资源操作
+-- model (CRUD) → get/post/put/delete
+-- call (非CRUD) → component 或 func
@component——注册制。重复模式出现3次以上时晋升。@func——独特逻辑。由人或AI直接实现。
分支后合流、循环内条件处理等复杂逻辑不通过扩展sequence来处理,而是委托给call。委托但契约相同——失败时退出。
@message
可以选择性附加到所有sequence类型的元数据。指定失败时传递给用户的消息。
// @sequence guard nil project
// @message "프로젝트가 존재하지 않습니다"
// @sequence post
// @model Session.Create
// @param ProjectID request
// @param Command request
// @result session Session
// @message "세션 생성에 실패했습니다"
省略@message时,codegen会根据类型+模型名自动生成默认消息。只有需要自定义消息时才声明。
纯函数不存在于服务层
sequence中声明的所有块都是具有外部依赖的工作。格式转换、值计算等纯功能不属于服务层,而是在model内部或被调用的函数内部处理。服务函数仅负责编排(orchestration),纯计算属于被调用方。
类型引用
SSaC不定义自己的类型。它引用从现有SSOT来源派生的类型。
| 来源 | 示例 |
|---|---|
| 数据库schema(SQL DDL) | Project, Session |
| API规范(OpenAPI) | CreateSessionRequest |
| 接口定义 | FileSystem, Cache |
命名规则
| 分类 | 规则 | 示例 |
|---|---|---|
| 类型 | PascalCase,从SSOT获取 | Project, Session |
| 变量 | camelCase,通过@result声明 | project, sessionCount |
| request字段 | PascalCase,从请求中提取 | ProjectID, Command |
| 保留变量 | camelCase,框架提供 | currentUser, config |
类型和变量的大小写不同,因此仅通过声明即可区分。project是变量,Project是类型。
语言扩展
SSaC是语言无关的概念。Go是参考实现(PoC),同一sequence声明可以生成其他语言的代码。
sequence声明(通用)
|
+-- Go codegen
+-- Python codegen
+-- TypeScript codegen
+-- ...
规范(类型定义、语法、验证规则)与codegen是分离的。
Go参考实现可在GitHub上查看。