- CI/CD
- DevOps
【免费下载链接】woodpecker
Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.
导读
本文以 Woodpecker CI/CD 引擎开发文档中的「Conventions(开发约定)」章节为骨架,深入讲解其持久化层最核心的一条规范——数据库表名统一使用复数、列名一律不带前缀。无论你是打算为 Woodpecker 提交代码的贡献者,还是想理解其数据模型以便二次开发、排查问题的使用者,读完本文都能掌握 Woodpecker 数据模型的组织方式、命名规则的落地形式(Go 结构体与 xorm 标签)、以及这套约定在数据库迁移历史中的演变脉络。
一、约定原文与核心要义
原文档(docs/versioned_docs/version-2.8/92-development/06-conventions.md)对该约定的描述非常精炼,全文核心仅两条:
- 表名使用复数(Database tables are named plural)
- 列名不带任何前缀(columns don't have any prefix)
并给出示例:表agent(复数规范下应为agents),列包含id、name。
需要留意的是,v2.8 版本的示例行写作「Table nameagent」,与「复数表名」规则在字面上并不完全一致,这更像是一处笔误;同仓库的最新文档(docs/docs/92-development/06-conventions.md)已修正为「Model nameAgentwith table nameagentsand columnsid,name」,与代码实现完全吻合。下文将基于当前仓库源码逐一验证这两条规则。
二、复数表名:从模型到建表语句的全链路验证
Woodpecker 的持久化层位于 server/store/datastore,其数据模型定义在 server/model,每个实体对应一个 Go 文件(agent.go、repo.go、pipeline.go、secret.go、registry.go、cron.go、org.go等),并通过 xorm 框架映射到数据库。
复数表名的第一个直接证据是各模型实现的TableName()方法。例如 server/model/agent.go#L52-L55 中:
// TableName return database table name for xorm. func (Agent) TableName() string { return "agents" }同样的写法遍布全部模型文件,从源码可以统计出一份完整的「模型 → 表名」映射表(表格名为源码中TableName()的返回值,可通过 server/model 目录逐一核对):
| 模型(Go struct) | 表名 | 定义位置 |
|---|---|---|
Agent | agents | server/model/agent.go#L52-L55 |
Pipeline | pipelines | server/model/pipeline.go#L81 |
Repo | repos | server/model/repo.go#L96 |
Config/PipelineConfig | configs/pipeline_configs | server/model/config.go#L27 / server/model/config.go#L37 |
Cron | crons | server/model/cron.go#L39 |
Forge | forges | server/model/forge.go#L42 |
LogEntry | log_entries | server/model/log.go#L40 |
Org | orgs | server/model/org.go#L28 |
Perm | perms | server/model/perm.go#L31 |
Redirection | redirections | server/model/redirection.go#L23 |
Registry | registries | server/model/registry.go#L40 |
Secret | secrets | server/model/secret.go#L59 |
ServerConfig | server_configs | server/model/server_config.go#L24 |
可见规则执行得非常彻底:哪怕cron这种本身已是单数形态的词,也统一改写作复数crons;LogEntry对应log_entries(entry 的复数形式),Registry对应registries(y 变 ies)。
第二个证据来自数据库迁移的测试夹具:仓库维护了一份 PostgreSQL 的完整建表 dump(server/store/datastore/migration/test-files/postgres.sql),其中CREATE TABLE语句全部使用复数表名,与上述映射表一一对应:
CREATE TABLE public.agents ( CREATE TABLE public.pipelines ( CREATE TABLE public.configs ( CREATE TABLE public.crons ( CREATE TABLE public.forges ( CREATE TABLE public.log_entries ( CREATE TABLE public.orgs ( CREATE TABLE public.perms ( CREATE TABLE public.pipeline_configs ( CREATE TABLE public.redirections ( CREATE TABLE public.registries ( CREATE TABLE public.repos ( CREATE TABLE public.secrets ( CREATE TABLE public.server_configs ( CREATE TABLE public.tasks ( CREATE TABLE public.users ( CREATE TABLE public.workflows ((见 server/store/datastore/migration/test-files/postgres.sql#L30-L615)
三、无前缀列名:以agents表为典型样本剖析
「列名不带前缀」指的是:列名只描述字段本身的含义,不再叠加所属实体名。以下面这份来自 PostgreSQL dump 的agents表定义为例(server/store/datastore/migration/test-files/postgres.sql#L30-L46):
CREATE TABLE public.agents ( id bigint NOT NULL, created bigint, updated bigint, name character varying(255), owner_id bigint, token character varying(255), last_contact bigint, platform character varying(100), backend character varying(100), capacity integer, version character varying(255), no_schedule boolean, last_work bigint, org_id bigint, custom_labels json );可以看到,绝大多数列(id、name、created、updated、token、platform、backend、capacity、version)都没有任何实体名前缀,直接以字段语义命名。
这一命名在 Go 模型侧同样以 xorm 标签的形式显式声明,server/model/agent.go#L27-L46 中:
type Agent struct { ID int64 `json:"id" xorm:"pk autoincr 'id'"` Created int64 `json:"created" xorm:"created"` Updated int64 `json:"updated" xorm:"updated"` Name string `json:"name" xorm:"name"` OwnerID int64 `json:"owner_id" xorm:"'owner_id'"` Token string `json:"token" xorm:"token"` LastContact int64 `json:"last_contact" xorm:"last_contact"` LastWork int64 `json:"last_work" xorm:"last_work"` Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"` Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"` Capacity int32 `json:"capacity" xorm:"capacity"` Version string `json:"version" xorm:"'version'"` NoSchedule bool `json:"no_schedule" xorm:"no_schedule"` CustomLabels map[string]string `json:"custom_labels" xorm:"JSON 'custom_labels'"` OrgID int64 `json:"org_id" xorm:"INDEX 'org_id'"` Filters map[string]string `json:"filters" xorm:"'filters' json"` }两条规则在实现层面的关系可以归纳为:
- 列名默认取 Go 字段名的小写下划线形式:
Name→name,LastContact→last_contact,CustomLabels→custom_labels; - 只在需要显式声明时使用 xorm 标签:主键用
pk autoincr 'id',时间戳字段用created/updated(xorm 会自动填充),OrgID需要建索引所以标注了INDEX 'org_id'。
外键列的命名例外:前缀语义来自被引用实体
需要特别澄清的是:agents表中的owner_id、org_id看起来「带了前缀」,但这并不违反约定。这里的owner、org描述的是被引用对象的身份(该 agent 属于哪个用户、哪个组织),是外键语义的自然表达,而非给 agent 自身字段添加的冗余前缀。对比pipelines表的建表语句(server/store/datastore/migration/test-files/postgres.sql#L76-L109):repo_id、parent、reviewer、sender等列同样遵循「字段语义直接命名」的原则,自身的业务字段(event、status、commit、branch、ref、title、message、author)一律干净无前缀。
四、约定如何被维护:unify-columns-tables迁移与命名收敛
命名约定并非天然存在,而是通过数据库迁移逐步收敛的。仓库的迁移历史保存在 server/store/datastore/migration,以NNN_描述.go的序号文件组织,并借助xormigrate按 ID 幂等执行(参见 server/store/datastore/migration/000_legacy_to_xormigrate.go)。
其中最具代表性的就是编号009的迁移unify-columns-tables(server/store/datastore/migration/009_unify_columns_tables.go),它专门用来统一各表的列命名。从迁移内部定义的旧结构体可以看到,历史上曾存在大量带实体名前缀的列名,例如pipelines表的pipeline_id、pipeline_repo_id、pipeline_author、pipeline_status、pipeline_commit(server/store/datastore/migration/009_unify_columns_tables.go#L62-L89),configs表的config_id、config_repo_id、config_hash(同文件 L27-L33),registry的registry_id、registry_repo_id、registry_username(同文件 L95-L101)等。
这些带前缀的旧列名在迁移中被逐一改名为无前缀形式,最终对齐到当前「列名无前缀」的约定。这从侧面说明了两点:
- 该约定是有意识地强制执行的规范,而非巧合;
- 后续新增字段(如
agents表在 v3 中加入的custom_labelsjson 列、filters列,见 server/model/agent.go#L41-L45)会继续遵循同一命名方式。
五、贡献者实践指南:如何为 Woodpecker 新增数据模型
结合上述源码证据,若你要为 Woodpecker 新增一张表,建议按以下步骤对齐约定:
- 模型文件:在 server/model 下新建
xxx.go,定义 Go 结构体,字段名遵循驼峰命名; - 列映射:通过 xorm 标签显式声明列名,列名取字段的小写下划线形式且不加实体名前缀;主键统一
pk autoincr 'id',审计时间戳统一使用 xorm 的created/updated特性(参考 server/model/pipeline.go#L26-L36 中Created/Updated的写法); - 表名:为模型实现
TableName()方法,返回复数形式的表名,例如Cron返回crons(server/model/cron.go#L39); - 迁移脚本:在 server/store/datastore/migration 下新增下一个序号的迁移文件,用
xormigrate.Migration包裹MigrateSession,其中临时结构体中的列名同样要遵守无前缀约定; - 数据访问层:在 server/store/datastore 下新增对应的
xxx.go查询实现,并与同目录的xxx_test.go测试保持命名一致(现有实体均遵循此文件组织,如agent.go/agent_test.go、repo.go/repo_test.go); - 验证建表:最终的表结构应与 server/store/datastore/migration/test-files/postgres.sql 中对应的
CREATE TABLE片段保持一致。
六、小结:命名约定是数据模型的可读性基石
Woodpecker 的数据库命名约定看似只有两行字,实则是整套持久化层设计的一致性原则:复数表名让表与「集合」语义对齐,无前缀列名让列在跨表阅读、编写 SQL、对照 Go 结构体时无需记忆额外映射。无论是agents、repos、pipelines这样的核心表,还是crons、log_entries这类特殊形态,全部一视同仁;而unify-columns-tables迁移则证明了这套约定在项目演进中被主动贯彻。对于希望深入 Woodpecker 源码或为其贡献代码的开发者而言,先读懂这条约定,再阅读 server/model 与 server/store/datastore 两个目录下的文件,就能快速建立起对整套数据模型的心智地图。
- CI/CD
- DevOps
【免费下载链接】woodpecker
Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.
相关推荐
Android数据库命名规范终极指南:表名与列名最佳实践详解
Android数据库命名规范终极指南:表名与列名最佳实践详解 在Android应用开发中,数据库设计是至关重要的环节,而合理的命名规范直接影响代码的可读性、可维
数据库ORM移动开发Unleash 前端接口命名规范:以 `I` 前缀的 TypeScript 接口与 Props 命名约定(ADR 实践指南)
Unleash 前端接口命名规范:以 I 前缀的 TypeScript 接口与 Props 命名约定(ADR 实践指南) Unleash 开源特性管理平台的前端
后端Authelia 数据库 Schema 设计指南:表、列与键的命名规范及源码实践
Authelia 数据库 Schema 设计指南:表、列与键的命名规范及源码实践 本篇指南围绕 Authelia 官方开发文档中的数据库 Schema(Data
后端认证鉴权单点登录身份认证应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考