1

我们的产品是一个client/server应用程序,它在现场有多个版本的客户端,但只有一个运行最新版本的服务器来服务所有API呼叫。我们有/将有数百个API端点,我正在尝试如何最好地处理版本控制。我想做的是能够避免应用attributes到每一个方法的繁重任务,或者每次我们进行微小的更改时复制整个控制器。

我可能会误解大多数关于此的文件/实践,但似乎每次你碰到你的时候API你都必须经历并完成所有这些工作,这似乎充其量是低效的。

相反,我想做的是使用编写时attribute的版本对每个端点应用一个,然后客户端找到等于或小于客户端版本的最接近的版本

例如,如果端点被写入,[ApiVersion("1.0")]那么这就是它获得的属性。如果我们必须修改它,我会复制方法,重命名它,应用 aRoutePrefix attribute以便正确命中并应用attribute我们整体版本的新方法API(在这个例子中,我放了1.5)。

这是一个简单的例子:

[HttpGet]
[ApiVersion("1.0")]
[Route("GetHeartBeat")]
public bool GetHeartBeat()
{
    return true;
}

[HttpGet]
[ApiVersion("1.5")]
[Route("GetHeartBeat")]
public bool GetHeartBeat2()
{
    return false;
}

当我使用 url 版本控制时,这没有问题:

  • /api/v1.0/GetHeartBeat 或者
  • /api/v1.5/GetHeartBeat

/api/v1.3/GetHeartBeat没有,因为那个版本不存在..

我想要发生的是,如果我有一个正在运行的客户端1.3,那么它将找到等于或小于最新版本的最接近的版本。所以/api/v1.3/GetHeartBeat会收到,因为1.3不存在,然后它会查看最接近/早期的版本,在这种情况下是1.0.

我可以写一堆路由逻辑来完成这个,但我觉得必须有一个开箱即用的解决方案,因为我不能成为第一个尝试这个的人。有没有一个nuget包可以做到这一点?

4

1 回答 1

1

你真的问了两个问题。如何在服务器端映射事物是一个实现细节,有很多选择。属性不是应用 API 版本控制元数据的硬性要求。您可以使用约定,包括您自己的约定。API 版本必须是离散的。那是设计使然。API 版本更像是一种媒体类型。您不能随意添加媒体类型或 API 版本,并且必须期望客户端能够理解它。

由于您拥有双方,因此您有一些很好的途径可以让事情按照您想要的方式进行。服务器永远不应该假设客户端想要什么,客户端应该总是明确地询问服务器它想要什么。实现目标的最简单方法是协商API 版本。好,很好。如何?

我怀疑今天没有很多人这样做,但 API 版本控制在很早的时候就包含了必要的机制来实现这一点。有很多用例,但最常见的是工具(例如:客户端代码生成)和客户端版本协商。第一步是启用报告 API 版本:

services.AddApiVersioning(options => options.ReportApiVersions = true);

您还可以应用于[ReportApiVersions]特定的控制器操作

这将允许通过api-supported-versionsapi-deprecated-versionsHTTP 标头报告可用的 API 版本。请记住,弃用并不意味着它不存在,它只是意味着它会在某个时候消失;你控制政策。您的客户可以使用此信息来记录有关过时版本的警告,或者它会影响您的客户选择合适版本的决定。

您面临的部分挑战是按 URL 段进行版本控制。是的,它非常流行,但它违反了统一接口约束。v1.api.com是一个端点v1.0/GetHeartBeat并且v1.5/GetHeartBeat标识符。这两个识别的资源几乎可以肯定不是不同的资源,而是具有不同的表示。为什么这很重要?更改每个版本的标识符(例如 URL)会导致客户端移动目标。其他所有版本控制方法都将使用 always use GetHeartBeat。我敢肯定,您做出改变的路太远了,但这会导致解决方案。

您使用哪个控制器实现并不重要,但您基本上需要一个执行以下操作的操作:

[ApiController]
[Route("api/[controller]")]
public class GetHeartBeatController : ControllerBase
{
    [ReportApiVersions] // ← instead of ApiVersioningOptions.ReportApiVersions = true
    [ApiVersionNeutral] // ← allow any and all versions, including none at all
    [HttpOptions]
    public IActionResult Options()
    {
        // Allow is required by spec; you may need addition information
        Response.Headers.Add("Allow", new StringValues(new[]{"GET", "OPTIONS"}));
        Response.GetTypedHeaders().CacheControl = new()
        {
            MaxAge = TimeSpan.FromDays(1d),
        };
        return Ok();
    }
}

现在,如果您的客户发送:

OPTIONS api/getheartbeat HTTP/2
Host: localhost

你会得到类似的东西:

HTTP/2 200 OK
Cache-Control: max-age=86400
Api-Supported-Versions: 1.0, 1.5

如果您的客户端正在运行1.3,它现在具有1.0从列表中选择最合适的 API 版本所需的知识。标Cache-Control头可以用作服务器告诉客户端它可以缓存结果多长时间的一种方式(但它不是必须的)。我认为 API 版本的更改频率不会超过每天一次,因此这似乎是一种合理的方法。

你没有提到你有什么类型的客户。如果它是基于浏览器的客户端,您可能需要对此设置进行一些额外的工作,以使其与 CORS 配合使用(如果需要)。或者,您可以使用该HEAD方法获得相同的结果。我认为这OPTIONS更合适,但如果您遇到任何复杂情况,您可能会发现让它与 CORS 一起玩的工作不值得。

于 2021-09-30T17:12:34.400 回答