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目录与版本管理状态流转 领域 Domain account / payment 服务 Service query / transfer API 名称 getBalance 版本列表 v1.0 (废弃) v1.1 (活跃) v2.0 (活跃) 版本生命周期 活跃 (Active) 可被调用 废弃 (Deprecated) 宽限期倒计时 下线 (Removed) 不可访问 Deprecate() 宽限期结束 CanRemove() 检查宽限期 注册与查询流程 Register() 新增API或新版本 Query() 按领域/服务过滤 VersionHistory() 查看全部版本 活跃 废弃 下线

关键设计说明

  • 目录结构:三级分层使API归属清晰,支持按领域或服务维度进行治理与统计。
  • 版本策略:采用语义化版本子集(major.minor),通过 Deprecate() 显式标记废弃,由 CanRemove() 强制执行宽限期,避免突然下线造成调用方故障。
  • 查询能力:Query() 支持目录浏览,VersionHistory() 提供完整版本轨迹,便于审计与调用方迁移规划。

本实现覆盖了API目录注册、版本管理、废弃控制与查询的核心闭环,可直接嵌入开放平台管理后台使用。