一种语言无关的概念,将服务函数内部的业务流程分解为声明式块(sequence)。

问题

现有的SSOT仅覆盖函数外部:

现有SSOT覆盖范围函数内部?
OpenAPIAPI路径、参数、响应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 nilnull -> 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上查看。