跳到主要内容

应用项目规范

本页定义 VEF 应用项目必须遵循的项目级规范。

它只面向基于 VEF Framework Go 构建的应用项目,不用于约束框架仓库自身的开发方式。

危险

所有新的应用代码都必须遵循本页规范。已有区域一旦被修改,改动范围内的文件也必须保持符合规范。这些要求不是建议,而是必须执行的项目标准。

提示

先阅读 项目结构 了解基础目录组织,再阅读本页掌握命名、API 组织和项目层面的强制规则。

这些规则综合了两部分来源:

  • 框架本身真实存在的 API 命名校验规则
  • apiinternal/api 中使用的 handler 解析规则,以及 Go 社区通用命名最佳实践

Go 通用命名

范围规则示例
package 名package 名必须简短且全小写。不得使用混合大小写和装饰性分隔符。默认应优先使用单数形式approvalsecuritystorageusermodel
文件名文件名必须全小写并按职责命名。多个单词时使用 snake_caseuser_resource.gouser_service.gouser_loader.go
导出类型导出类型名必须使用 PascalCaseUserResourceUserServiceLoginParams
非导出类型非导出类型名必须使用 lowerCamelCaseloginContextrouteEntry
导出函数和方法导出函数和方法必须使用 PascalCase;执行动作的名字应当用动词开头NewUserResourceCreateUserFindPage
非导出函数和方法非导出函数和方法必须使用 lowerCamelCasebuildQueryresolveHandlerparseRequest
接口接口名必须表达能力或职责。能自然读通时优先使用领域名词或 -er 风格UserLoaderTokenGeneratorPermissionChecker
变量变量名必须使用 lowerCamelCase。只有在作用域极小且语义非常明显时才允许很短的名字userIDrequestMetaauthManager
常量常量必须遵循 Go 风格命名。成组常量在有助于理解时保持统一领域前缀DefaultRPCEndpointAuthTypePasswordUserMenuTypeDirectory
结构体字段结构体字段在 Go 代码中必须使用 PascalCase。外部命名风格应通过 tag 映射CreatedAtUserIDPermTokens

补充要求:

  • 必须避免 package stutter,也就是类型名无意义地重复包名 例如在 approval 包里,应优先使用 InstanceFlowService,而不是所有类型都重复写一遍 Approval
  • package 名通常应保持单数,除非复数形式在语义上不可避免 优先使用 modelpayloadresourceservicequerycommand,而不是 modelspayloadsresourcesservicesqueriescommands
  • 常见缩写在可读性更好的前提下,必须保持正常 Go 写法 例如:IDURLHTTPJSONAPIJWTRPC
  • 命名必须稳定且面向领域 应优先使用 UserInfoLoader 这类有职责含义的名字,不得使用 Loader2DataHelper 这类模糊名称

API Resource 与 Action 命名

VEF 会对 resource name 和 action name 做校验。这不只是风格建议,命名不合法时资源注册会直接失败。

RPC resource name

RPC 资源名必须满足:

  • 使用斜杠分段
  • 每个分段都使用小写
  • 分段内部如果有多个单词,使用 snake_case
  • 不能有前导 /、尾随 /,也不能出现连续的 //

合法示例:

  • user
  • sys/user
  • sys/data_dict
  • approval/category

强制约定:

  • resource name 应表达业务边界或资源命名空间
  • resource name 应使用名词或名词短语,而不是动词

RPC action name

RPC action name 必须使用 snake_case

合法示例:

  • create
  • find_page
  • get_user_info
  • resolve_challenge

强制约定:

  • action name 应表达行为,因此优先使用动词或动词短语
  • 一旦对外暴露为 API 合同后,action name 应尽量保持稳定
  • get_find_create_update_delete_resolve_list_ 这类前缀风格应保持一致,不要无故混用同义词

动词选用规则:

动词或模式适用场景常见业务语义示例不适合用于
get_xxx返回单个确定对象、单个计算结果,或固定元数据当前用户信息、构建信息、Schema 明细、对象元数据、按明确主键取详情get_user_infoget_build_infoget_table_schemaget_presigned_url模糊搜索、分页查询、批量过滤
find_xxx带筛选、搜索、分页、树形/选项整形语义的查询列表分页、条件过滤、选项列表、树查询、按查询语义取单条find_onefind_allfind_pagefind_optionsfind_tree固定上下文下的单个对象读取,或命令式状态变更
list_xxx直接枚举一个相对简单的集合,不强调复杂查询语义列表出表、视图、触发器、某个前缀下的文件list_tableslist_viewslist_triggerslist_files更适合表述为检索/筛选的复杂查询
create_xxx创建一个新的业务对象创建用户、创建流程、创建草稿、创建令牌记录create_usercreate_flowcreate_draft更新已有对象,或带 upsert 含义的动作
create_manycreate_xxx_batch一次创建多条记录批量创建员工、批量创建标签、批量初始化create_manycreate_user_batch单条创建
update_xxx原地更新已有对象修改资料、更新流程配置、更新设置update_userupdate_flowupdate_settings审批、发布、启停等语义更强的状态动作
update_manyupdate_xxx_batch一次更新多条已有记录批量调整状态、批量更新标签update_manyupdate_user_batch单条更新
delete_xxx删除一个已有对象删除用户、删除草稿、删除文件记录delete_userdelete_draft更适合用 cancel / terminate / withdraw 表达的业务结束动作
delete_manydelete_xxx_batch一次删除多条记录批量删除用户、批量清理记录delete_manydelete_user_batch单条删除
enable_xxx / disable_xxx切换布尔型启用状态启用功能、禁用账号、禁用集成enable_userdisable_feature长流程启停或发布生命周期
start_xxx / stop_xxx启动或停止一个运行中的流程、任务、调度器、实例启动流程实例、停止同步任务、开始重放start_instancestop_jobstart_replay仅仅修改启用开关的场景
submit_xxx把草稿或请求推进到下一处理阶段提交表单、提交审批单、提交申请submit_formsubmit_instance单纯创建数据但没有阶段推进
approve_xxx / reject_xxx记录一个明确的业务决策审批流、审核流、内容审核approve_taskreject_taskapprove_comment不带决策语义的普通更新
cancel_xxx / terminate_xxx / withdraw_xxx以特定业务原因结束流程取消订单、终止实例、撤回申请cancel_orderterminate_instancewithdraw_request物理删除数据库或存储对象
assign_xxx / unassign_xxx改变负责人、归属人、处理人分派任务、取消分派审核人、指定部门assign_taskunassign_reviewer普通关联关系编辑但不涉及责任归属
add_xxx / remove_xxx增减成员、附件、子项、轻量关系添加 assignee、移除 assignee、增加抄送、移除成员add_assigneeremove_memberadd_cc父对象本身的创建或删除
bind_xxx / unbind_xxx建立或解除明确绑定关系绑定角色、解绑账号、绑定外部应用bind_roleunbind_account更适合用 add/remove 表达的松散成员关系
publish_xxx / unpublish_xxx改变外部可见性或发布状态发布版本、下线文章、发布模板publish_versionunpublish_article只是内部启用/禁用的状态变更
import_xxx / export_xxx进行数据导入导出、批量导入、报表导出导入用户、导出员工、导出报表import_userexport_employeeexport_report普通 create/list 动作
upload_xxx / download_xxx传输文件内容或二进制产物上传头像、下载附件、上传对象upload_avatardownload_attachment仅查询文件元数据而不传输内容
generate_xxx由服务端生成一个产物、令牌、编码、预览或 URL生成编码、生成令牌、生成预签名地址generate_codegenerate_tokengenerate_preview读取一个本来就已经存在的持久化值
resolve_xxx把 challenge、冲突、别名、待定状态解析成确定结果解析挑战、解决冲突、解析依赖resolve_challengeresolve_conflict普通更新但没有“解析/消解”语义
validate_xxx / verify_xxx校验正确性,但不提交状态变更校验配置、校验令牌、校验签名validate_configverify_token会修改状态的动作
refresh_xxx / sync_xxx / rebuild_xxx重新计算、同步、刷新派生状态刷新 token、同步部门、重建索引refreshsync_departmentrebuild_index首次创建对象,或普通只读查询

优先使用的框架对齐模式:

  • 标准 CRUD 读操作优先使用内置词汇:find_onefind_allfind_pagefind_optionsfind_treefind_tree_options
  • 标准 CRUD 写操作优先使用内置词汇:createcreate_manyupdateupdate_manydeletedelete_many
  • 除非语义明确不同,否则不要在同一个 bounded context 内混用近义词,例如 query_pagesearch_listremove_userdelete_user

常见反例:

错误写法正确写法错误原因
GetUserInfoget_user_infoRPC action name 必须使用 snake_case,不能用 PascalCase
get-user-infoget_user_infoRPC action name 不能使用 kebab-case
query_pagefind_page标准分页查询应优先对齐内置 CRUD 词汇
search_listfind_allfind_pagesearchlist 组合语义过泛,应改成能直接表达查询形态的命名
remove_userdelete_user同一 bounded context 内不要为同一删除语义混用近义词
get_user_listfind_allfind_page集合查询不应使用单对象语义的 get_xxx
update_status在真实语义明确时改为 enable_xxxdisable_xxxapprove_xxxreject_xxx当业务动作是明确状态迁移时,泛化的 update 语义太弱
handle_taskapprove_taskreject_taskassign_task 或其他明确领域动词handle 无法表达真实业务意图
process_ordersubmit_ordercancel_ordercomplete_order 或其他明确生命周期动词process 过于宽泛,不适合作为稳定 API 合同
sync_data_and_rebuild_index拆成 sync_datarebuild_index,或者只保留主职责动作名一个 action name 应只表达一个主要职责

RPC Action 命名决策顺序

为新的 RPC action 命名时,应按下面的顺序判断:

  1. 先判断它属于读、写、状态迁移、关系变更,还是集成/工具类动作。
  2. 如果它本质上是标准 CRUD 读写,优先使用框架现有 CRUD 词汇,不要新造近义词。
  3. 如果它不是标准 CRUD,选择最能准确表达业务意图的最窄动词。
  4. 如果两个动词都看起来能用,优先选择更能反映 API 最终可观察结果的那个。
  5. 如果一个 action 看起来同时承担多个职责,应拆分动作,或者按主职责重命名。

决策图:

RPC handler 方法名

对于 RPC 资源,如果没有显式指定 Handler,VEF 会把 action name 转成 PascalCase 后去查找对应方法。

例如:

  • find_page -> FindPage
  • get_user_info -> GetUserInfo
  • resolve_challenge -> ResolveChallenge

这意味着:

  • RPC action name 在转成 PascalCase 后也要保持可读
  • handler 方法名应使用 PascalCase 的动词或动词短语

REST resource name

REST 资源名必须满足:

  • 使用斜杠分段
  • 每个分段都使用小写
  • 分段内部如果有多个单词,使用 kebab-case

合法示例:

  • users
  • sys/user
  • sys/data-dict
  • user-profiles

强制约定:

  • 让资源路径表达集合或领域边界
  • 让 HTTP method 承担主要动作语义,而不是把动词塞进 resource name

REST action name

REST action name 必须使用以下两种格式之一:

  • <method>
  • <method> <sub-resource>

其中:

  • <method> 是小写 HTTP verb,例如 getpostputdeletepatch
  • <sub-resource> 使用 kebab-case

合法示例:

  • get
  • post
  • delete
  • get profile
  • post admin
  • get user-friends

强制约定:

  • method token 保持小写
  • sub-resource 使用名词化、路径化命名
  • REST sub-resource 不要使用 snake_case

API 版本号

资源版本号必须满足 v<number> 格式。

合法示例:

  • v1
  • v2
  • v10

只有外部接口契约发生真实变化时,才应当递增版本号。

Handler、Params 与 Meta 类型命名

对于应用自定义的 API 类型,名称必须直接表达它在请求链路中的角色。

类型类别推荐模式示例
Resource 结构体<Domain>ResourceUserResourceFlowResource
Params 结构体<Action>Params<Domain><Action>ParamsLoginParamsCreateUserParams
Meta 结构体<Domain>Meta<Action>MetaUserMetaExportMeta
Search 结构体<Domain>SearchUserSearchOrderSearch
Service 结构体/接口<Domain>Service<Capability>Loader<Capability>ResolverUserServiceUserLoaderDepartmentResolver

类型名必须足够具体,保证放到其他 package 里阅读时仍然自解释。

结构体字段、JSON 与标签命名

VEF 应用必须保持这组分层命名约定:

  • Go 结构体字段名使用 PascalCase
  • JSON 字段名使用 camelCase
  • 数据库列名使用 snake_case

示例:

type User struct {
ID string `json:"id" bun:"id,pk"`
UserName string `json:"userName" bun:"user_name"`
CreatedAt string `json:"createdAt" bun:"created_at"`
}

每一层都必须保持自己的命名风格一致,不得在同一层里混用多种风格。

测试命名

测试命名必须遵循项目测试规范:

元素模式示例
测试套件<Feature>TestSuiteUserResourceTestSuite
测试方法Test<Feature>TestLoginTestFindPage
子测试名PascalCasePasswordExpiredEmptyInputInvalidToken

不得在顶层测试方法名中用下划线编码子场景。

应当使用:

  • TestLogin,下面用 PasswordExpired 之类的子测试

不得写成:

  • TestLogin_PasswordExpired

延伸阅读