2. API目录与版本管理策略
任务说明
本案例面向数字银行开放平台,设计一套API目录与版本管理策略。你需要完成:定义API目录的分层结构、制定版本号规则与兼容性策略、实现一个可运行的版本管理核心模块(含目录注册、版本查询、废弃检测),并提供验证方法。
API目录结构设计
采用三级目录:领域(domain) → 服务(service) → API。领域代表业务域(如账户、支付、贷款),服务是领域内的具体能力集合,API是实际暴露的接口。每个API在目录中注册时携带当前版本号、废弃状态与废弃时间。
版本策略规则
- 版本号格式:
major.minor,如1.0、2.3。 - 兼容性规则:
major递增表示不兼容变更;minor递增表示向后兼容的增强或缺陷修复。 - 废弃策略:API进入废弃状态后,至少保留
deprecationGracePeriodDays(默认180天)才允许下线。 - 目录查询:支持按领域/服务过滤,支持查看指定API的全部版本历史。
核心实现
// api_catalog.go
package main
import (
"fmt"
"sort"
"strings"
"time"
)
// APIVersion 表示单个API版本
type APIVersion struct {
Version string // 如 "1.0"
Deprecated bool // 是否已废弃
Deprecation time.Time // 废弃时间(Deprecated=true时有效)
}
// APIEntry 表示目录中的一个API
type APIEntry struct {
Domain string
Service string
Name string
Versions []APIVersion
CreatedAt time.Time
}
// Catalog 是API目录
type Catalog struct {
entries []*APIEntry
}
// NewCatalog 创建空目录
func NewCatalog() *Catalog {
return &Catalog{}
}
// Register 注册新API或为新API添加版本
func (c *Catalog) Register(domain, service, name, version string) error {
// 查找是否已存在
for _, e := range c.entries {
if e.Domain == domain && e.Service == service && e.Name == name {
// 检查版本是否重复
for _, v := range e.Versions {
if v.Version == version {
return fmt.Errorf("version %s already exists for %s/%s/%s", version, domain, service, name)
}
}
e.Versions = append(e.Versions, APIVersion{Version: version})
return nil
}
}
// 新建条目
entry := &APIEntry{
Domain: domain,
Service: service,
Name: name,
Versions: []APIVersion{{Version: version}},
CreatedAt: time.Now(),
}
c.entries = append(c.entries, entry)
return nil
}
// Deprecate 将指定API的指定版本标记为废弃
func (c *Catalog) Deprecate(domain, service, name, version string) error {
for _, e := range c.entries {
if e.Domain == domain && e.Service == service && e.Name == name {
for i := range e.Versions {
if e.Versions[i].Version == version {
e.Versions[i].Deprecated = true
e.Versions[i].Deprecation = time.Now()
return nil
}
}
return fmt.Errorf("version %s not found", version)
}
}
return fmt.Errorf("API %s/%s/%s not found", domain, service, name)
}
// Query 按领域/服务过滤,返回匹配的API列表
func (c *Catalog) Query(domain, service string) []*APIEntry {
var result []*APIEntry
for _, e := range c.entries {
if domain != "" && e.Domain != domain {
continue
}
if service != "" && e.Service != service {
continue
}
result = append(result, e)
}
return result
}
// VersionHistory 返回指定API的全部版本(按版本号升序)
func (c *Catalog) VersionHistory(domain, service, name string) ([]APIVersion, error) {
for _, e := range c.entries {
if e.Domain == domain && e.Service == service && e.Name == name {
versions := make([]APIVersion, len(e.Versions))
copy(versions, e.Versions)
sort.Slice(versions, func(i, j int) bool {
return compareVersions(versions[i].Version, versions[j].Version) < 0
})
return versions, nil
}
}
return nil, fmt.Errorf("API %s/%s/%s not found", domain, service, name)
}
// compareVersions 比较两个版本号,返回 -1/0/1
func compareVersions(a, b string) int {
pa := strings.Split(a, ".")
pb := strings.Split(b, ".")
for i := 0; i < len(pa) && i < len(pb); i++ {
var na, nb int
fmt.Sscanf(pa[i], "%d", &na)
fmt.Sscanf(pb[i], "%d", &nb)
if na < nb {
return -1
} else if na > nb {
return 1
}
}
return 0
}
// CanRemove 判断某版本是否允许下线(废弃超过宽限期)
func (c *Catalog) CanRemove(domain, service, name, version string, gracePeriodDays int) (bool, error) {
versions, err := c.VersionHistory(domain, service, name)
if err != nil {
return false, err
}
for _, v := range versions {
if v.Version == version {
if !v.Deprecated {
return false, fmt.Errorf("version %s is not deprecated", version)
}
cutoff := v.Deprecation.AddDate(0, 0, gracePeriodDays)
return time.Now().After(cutoff), nil
}
}
return false, fmt.Errorf("version %s not found", version)
}
// 示例使用
func main() {
cat := NewCatalog()
// 注册账户查询API v1.0
cat.Register("account", "query", "getBalance", "1.0")
// 添加兼容版本 v1.1
cat.Register("account", "query", "getBalance", "1.1")
// 添加不兼容版本 v2.0
cat.Register("account", "query", "getBalance", "2.0")
// 注册支付API
cat.Register("payment", "transfer", "createTransfer", "1.0")
// 废弃 getBalance v1.0
cat.Deprecate("account", "query", "getBalance", "1.0")
// 查询账户领域所有API
fmt.Println("=== 账户领域API ===")
for _, e := range cat.Query("account", "") {
fmt.Printf("%s/%s/%s\n", e.Domain, e.Service, e.Name)
}
// 查看getBalance版本历史
fmt.Println("\n=== getBalance 版本历史 ===")
versions, _ := cat.VersionHistory("account", "query", "getBalance")
for _, v := range versions {
status := "active"
if v.Deprecated {
status = "deprecated since " + v.Deprecation.Format("2006-01-02")
}
fmt.Printf("v%s [%s]\n", v.Version, status)
}
// 检查v1.0是否可下线(宽限期30天)
canRemove, _ := cat.CanRemove("account", "query", "getBalance", "1.0", 30)
fmt.Printf("\nv1.0 是否可下线(30天宽限): %v\n", canRemove)
}
运行与验证
go run api_catalog.go
预期输出(示例,实际时间戳以运行为准):
=== 账户领域API ===
account/query/getBalance
=== getBalance 版本历史 ===
v1.0 [deprecated since 2025-01-01]
v1.1 [active]
v2.0 [active]
v1.0 是否可下线(30天宽限): false
判定标准:
- 输出中账户领域仅显示
getBalance,支付领域API不出现; - 版本历史按
1.0 → 1.1 → 2.0升序排列; - 刚废弃的
v1.0在30天宽限期内返回false;若将宽限期改为0天并重新运行,则返回true。
关键设计说明
- 目录结构:三级分层使API归属清晰,支持按领域或服务维度进行治理与统计。
- 版本策略:采用语义化版本子集(major.minor),通过
Deprecate()显式标记废弃,由CanRemove()强制执行宽限期,避免突然下线造成调用方故障。 - 查询能力:
Query()支持目录浏览,VersionHistory()提供完整版本轨迹,便于审计与调用方迁移规划。
本实现覆盖了API目录注册、版本管理、废弃控制与查询的核心闭环,可直接嵌入开放平台管理后台使用。