关于 The Standard Versioning
The Standard Versioning 是一项技能,规定发布、文件和 API 版本,以及弃用标记和能力发现的统一方案。它面向设计或审查软件版本变更的团队,明确要求保留旧版本,并使路由与模型及服务行为对应。
主要功能
- 01定义四段式发布版本
- 02规定模型与服务文件命名
- 03使 API 路由对应版本
- 04指定弃用标记方式
- 05说明能力发现机制
适用场景
- 01规划模型变更
- 02为服务行为设定版本
- 03设计版本化 API 路由
- 04审查弃用与能力支持
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hassanhabib/ the-standard-skills --skill the-standard-versioning安装后在 Claude Code 中输入/the-standard-versioning确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsoncontracts/contracts.jsonenforcement/enforcement.jsonrules/rules.mdrules.jsonvalidations/checklist.md详细介绍
标准 — 版本控制
此技能的内容
此技能定义了必须采用的版本控制方式,适用于:
- 发布版本控制
- 文件版本控制(模型、服务、异常)
- API 版本控制
- 弃用标记
- 发现能力
此技能 MUST 遵循此处明确定义的版本控制模型,MUST NOT 替换为其他语义化版本策略、兼容性理论或推断出的惯例。
此技能旨在确保版本变更:
- 明确
- 结构化
- 非破坏性
- 可发现
- 对使用者安全
何时使用
在以下情形中使用此技能:
- 创建新发布版本
- 引入需要进行版本控制的模型变更
- 引入需要进行版本控制的服务或例程变更
- 创建新的版本化文件或文件夹
- 为版本化资源定义 API 路由
- 将代码或 API 标记为弃用
- 实现发现能力
- 在 PR 中审查与版本相关的变更
明确的覆盖范围
此技能涵盖以下主要领域:
- 发布版本控制
- 代码版本控制
- 弃用
- 发现能力
此技能中的代码版本控制包括:
- 文件版本控制
- API 版本控制
- 弃用标记
- 发现能力信号,帮助使用者了解受支持的行为
发布版本控制
发布格式
发布版本 MUST 使用以下确切格式:
v1.2.3.4
字段含义
| 字段 | 含义 |
|---|---|
| 1 | 模型变更 |
| 2 | 服务或例程变更 |
| 3 | 错误修复或配置变更 |
| 4 | 自动化构建版本 |
对于此技能,这些含义是固定的,MUST NOT 重新解释。
发布递增规则
-
发布版本 MUST 保持
v1.2.3.4的确切格式。 -
模型变更 MUST 递增字段
1,并 MUST 将字段2、3和4重置为零。 -
服务或例程变更 MUST 递增字段
2,并 MUST 将字段3和4重置为零。 -
错误修复或配置变更 MUST 递增字段
3,并 MUST 将字段4重置为零。 -
对相同代码进行新的自动化构建时,MUST 仅递增字段
4。 -
如果同时发生多种变更,MUST 以优先级最高的变更为准,并将所有较低字段重置为零。
发布示例
当前版本:
v1.2.3.4
预期的下一版本:
- 模型变更 →
v2.0.0.0 - 服务或例程变更 →
v1.3.0.0 - 错误修复或配置变更 →
v1.2.4.0 - 对相同代码进行自动化构建 →
v1.2.3.5 - 同时发生模型变更和服务变更 →
v2.0.0.0
发布指南
使用发布版本来描述发生了哪种变更,而不只是说明发生了变更。
这意味着:
- 模型变更绝不能隐藏在服务字段中
- 服务变更绝不能隐藏在错误修复/配置字段中
- 绝不能使用构建版本递增来表示代码变更
文件版本控制
V0 约定
没有版本文件夹的文件视为 V0。
示例:
Models/Foundations/Students/Student.cs
Services/Foundations/Students/StudentService.cs
Models/Foundations/Students/Exceptions/
这表示磁盘上的初始形式不带版本号,但逻辑上仍假定其版本为零。
模型变更
模型发生变更时,MUST 在原位置的子文件夹版本文件夹下创建新的版本化文件。
示例:
Models/Foundations/Students/V1/StudentV1.cs
此模式表示:
- 原始 V0 模型仍保留在原位置
- 新模型版本位于嵌套的
V1文件夹中 - 文件夹名称和类型名称中都明确标出版本号
由模型驱动的服务变更
模型发生变更时,相应服务 MUST 在同一模型版本文件夹下进行版本控制。
示例:
Services/Foundations/Students/V1/StudentV1Service.cs
相关异常 MUST 与对应版本化模型路径保持一致。
示例:
Models/Foundations/Students/V1/Exceptions/
服务行为版本控制
当服务的行为发生变化,而底层模型没有变化时,会对服务文件进行版本控制,以体现行为的演进。行为变化包括修改验证规则、将之前的可选字段设为必填,或更改业务逻辑。
如果模型仍为 V0,且只有服务行为发生变化,则带有行为版本后缀的服务文件会放在服务位置的根目录下——不会创建版本文件夹:
Services/Foundations/Students/StudentServiceV1.cs
如果模型已经有版本(例如 V1),且服务行为发生变化,则带有版本的服务文件会放在模型的版本文件夹中,文件名同时包含模型版本和行为版本:
Services/Foundations/Students/V1/StudentV1ServiceV1.cs
模型和行为同时变化
如果在同一版本发布中,模型和服务行为同时发生变化,则只递增模型版本。行为版本重置为 V0,并通过文件名中不带行为版本来表示。根据定义,新模型始终意味着新的服务行为,因此行为版本会回到其基准值:
Services/Foundations/Students/V2/StudentV2Service.cs
行为版本后缀会被省略,表示这个新模型版本的行为版本为 V0。后续引入更多行为变化时,可以继续重复这一周期。
文件规则
-
没有版本文件夹即表示默认为 V0。
-
引入新版本时,必须创建新的带版本文件和文件夹,不得覆盖较早的实现。
-
模型发生变化时,必须在
Vn子文件夹下创建带版本的模型文件。 -
模型发生变化时,必须在对应的
Vn子文件夹下创建带版本的服务文件。仅服务行为发生变化时,必须在服务位置的根目录下创建带版本的服务文件,并使用行为版本作为后缀——不得创建新的版本文件夹。 -
带版本的异常必须放在相应的带版本模型路径下。
-
早期版本的现有代码必须继续保留,除非在引入新版本之外,另有明确的弃用/移除策略。
命名约定
带版本的文件/类型名称必须遵循以下模式:
- 模型 →
{Entity}V{n}.cs - 仅有模型版本的服务 →
{Entity}V{n}Service.cs - 仅有行为版本的服务 →
{Entity}ServiceV{n}.cs - 同时有模型版本和行为版本的服务 →
{Entity}V{m}ServiceV{n}.cs
行为版本始终作为后缀,置于服务名称之后。当模型变化和行为变化在同一版本发布中同时发生时,行为版本会重置为 V0,并通过文件名中不带行为版本来表示。
| 场景 | 示例文件名 |
|---|---|
| 模型 V1,无行为版本 | StudentV1Service.cs |
| 无模型版本,行为 V1 | StudentServiceV1.cs |
| 模型 V1,行为 V1 | StudentV1ServiceV1.cs |
| 引入模型 V2(行为重置为 V0) | StudentV2Service.cs |
API 版本控制
默认 API 路由
初始 API 的路由不带版本前缀:
api/Students
这是 V0 的路由。
模型版本路由
如果模型发生变化,路由必须体现模型版本:
api/V1/Students
这里,V1 表示该端点对应的是版本 1 的模型。
服务行为路由
如果模型已经是版本 1,且该模型对应的服务行为发生变化,路由必须同时体现模型版本和行为版本:
api/V1.1/Students
在这种格式中:
- 点号前的
1表示模型版本 - 点号后的
1表示行为/服务版本
这是一种模型与行为的组合,必须按这种方式理解。
如果只有行为发生变化而模型保持不变,新路由将为 api/V0.1/Students——模型版本仍为 0,但行为版本已递增至 1。
API 规则
-
api/{resource}表示 V0。 -
api/Vn/{resource}表示模型版本n。 -
api/Vn.m/{resource}表示模型版本n,其行为版本为m。 -
路由版本 MUST 明确传达预期的模型与行为版本配对。
-
API 路由 MUST 与底层模型/服务组合的版本管理方案保持一致。
API 示例流程
起始状态:
api/Students
模型变更后:
api/V1/Students
V1 模型的服务行为进一步变更后:
api/V1.1/Students
弃用
弃用信息 MUST 足够明确,让使用者能够了解:
- API 已弃用
- 预计停止服务的时间
- 获取迁移指南的途径
必须给使用者足够的时间,以便在弃用版本停止服务前采取行动。
弃用信号
一种可行的方法是通过标头或会生成这些标头的特性来提供弃用元数据。
使用控制器操作特性的示例:
[ApiController]
[Route("api/[controller]")]
public class SampleController : ControllerBase
{
[HttpGet]
[DeprecatedApi(
Sunset = "2024-12-31",
Warning = "This API is deprecated. Please migrate to v2.",
Link = "https://example.com/deprecation-info")]
public IActionResult GetSampleData()
{
return Ok(new { message = "Sample data" });
}
}
关键库
| 包 | 用途 |
|---|---|
Attrify | 一个 NuGet 库,可通过应用于控制器和操作的特性来控制 API 的可见性和生命周期(例如弃用) |
代码弃用
不再维护或支持的代码 SHOULD 标记为:
[Obsolete("This version is deprecated and no longer maintained.")]
使用 obsolete 标记,让维护者和使用者在编译时就能看到弃用信息。
能力
建议提供能力信息,以便使用者在调用功能前了解各版本支持的内容。
当模型版本和服务行为因版本或提供程序而异时,这尤其有用。
能力目标
能力接口应帮助使用者了解:
- 支持哪些功能
- 支持哪些模型版本
- 支持哪些行为版本
- 哪些可选功能不可用
能力接口选项
能力 MAY 通过以下方式公开:
- Swagger
- 基于中间件的元数据
- 专用能力端点
- 提供程序专属的能力端点
提供程序示例
在基于提供程序的架构(例如 SPAL 提供程序)中,每个具体提供程序都可以公开一个能力端点,告知使用者支持哪些操作。
这种模式让使用者能够在调用操作之前安全地确定某个提供程序是否支持该操作,从而避免运行时故障。
1. 操作特性
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class StudentOperationAttribute : Attribute
{
public string OperationName { get; }
public StudentOperationAttribute(string operationName) =>
OperationName = operationName ?? string.Empty;
public StudentOperationAttribute() =>
OperationName = string.Empty;
}
2. 能力模型
public sealed class ResourceCapabilities
{
public string ResourceName { get; init; } = string.Empty;
public IReadOnlyCollection<string> SupportedOperations { get; init; } =
Array.Empty<string>();
}
public sealed class ProviderCapabilities
{
public string ProviderName { get; init; } = string.Empty;
public IReadOnlyCollection<ResourceCapabilities> SupportedResources { get; init; } =
Array.Empty<ResourceCapabilities>();
}
3. Student 提供程序契约
public interface IStudentProvider
{
string ProviderName { get; }
ProviderCapabilities Capabilities { get; }
IStudentResource Students { get; }
}
4. 资源契约
public interface IStudentResource
{
ValueTask<Student> AddAsync(Student student);
}
5. 提供程序基类(简化的能力声明)
public abstract class StudentProviderBase : IStudentProvider
{
public abstract string ProviderName { get; }
public abstract IStudentResource Students { get; }
public virtual ProviderCapabilities Capabilities =>
new ProviderCapabilities
{
ProviderName = ProviderName,
SupportedResources = new[]
{
new ResourceCapabilities
{
ResourceName = "Student",
SupportedOperations = GetSupportedOperations(Students)
}
}
};
private static IReadOnlyCollection<string> GetSupportedOperations(object resource)
{
if (resource is null)
return Array.Empty<string>();
return resource.GetType()
.GetMethods()
.Select(method =>
method.GetCustomAttributes(typeof(StudentOperationAttribute), true)
.FirstOrDefault() as StudentOperationAttribute)
.Where(attribute => attribute is not null)
.Select(attribute => attribute!.OperationName)
.Where(name => !string.IsNullOrWhiteSpace(name))
.Distinct()
.ToArray();
}
}
6. 实现示例
public sealed class StudentResource : IStudentResource
{
[StudentOperation("Add")]
public ValueTask<Student> AddAsync(Student student)
{
return ValueTask.FromResult(student);
}
}
public sealed class StudentProvider : StudentProviderBase
{
public override string ProviderName => "DefaultStudentProvider";
public override IStudentResource Students { get; } = new StudentResource();
}
7. 能力检查扩展
public static class StudentProviderCapabilitiesExtensions
{
public static bool SupportsOperation(
this IStudentProvider provider,
string resourceName,
string operationName)
{
var resource = provider.Capabilities.SupportedResources
.FirstOrDefault(r => r.ResourceName == resourceName);
if (resource is null)
return false;
return resource.SupportedOperations.Contains(operationName);
}
}
8. 用法
IStudentProvider provider = new StudentProvider();
if (!provider.SupportsOperation("Student", "Add"))
{
// avoid call
return;
}
await provider.Students.AddAsync(new Student());
核心原则
能力信息将运行时的不确定性转变为确定性的检查:
- 无能力信息 → 调用 → 失败 → 处理异常
- 有能力信息 → 检查 → 决策 → 安全调用
这符合 Standard 原则:
避免无效操作,而不是在操作后再做出反应