1. Overview
1.概述
In this tutorial, we’ll talk about how we can leverage the Micronaut Framework capabilities to implement evolving REST APIs.
在本教程中,我们将讨论如何利用 Micronaut Framework 功能来实现不断演进的 REST API。
In the ever-evolving landscape of software development projects, sometimes purely based on REST APIs, maintaining backward compatibility while introducing new features and improvements is a crucial challenge. One of the fundamental aspects of achieving this is that we must implement a technique called API versioning.
在不断发展的软件开发项目(有时纯粹基于 REST API)中,在引入新功能和改进的同时保持向后兼容性是一项至关重要的挑战。实现这一目标的一个基本方面是,我们必须实施一种称为API 版本控制的技术。
We’ll explore the concept of API versioning in the context of Micronaut, a popular microservices framework for building efficient and scalable applications. We’ll delve into the importance of API versioning, different strategies to implement it in Micronaut, and best practices to ensure smooth version transitions.
我们将在 Micronaut(一种用于构建高效、可扩展应用程序的流行微服务框架)的背景下探讨 API 版本控制的概念。我们将深入探讨 API 版本控制的重要性、在 Micronaut 中实施版本控制的不同策略以及确保版本平稳过渡的最佳实践。
2. Importance of API Versioning
2.应用程序接口版本管理的重要性
API versioning is the practice of managing and evolving an application programming interface (API) to allow clients to continue using the existing version while also adopting newer versions when they are ready. It is essential for several reasons.
应用程序接口(API)版本化是管理和发展应用程序接口(API)的一种做法,它允许客户继续使用现有版本,同时在新版本准备就绪时采用新版本。这一点至关重要,原因有以下几点。
2.1. Maintaining Compatibility
2.1.保持兼容性
As our application evolves, we may need to change our APIs to introduce new features, fix bugs, or improve performance. However, it’s also necessary to ensure that such changes do not disrupt existing clients. API versioning enables us to introduce changes while maintaining compatibility with previous versions.
随着应用程序的发展,我们可能需要更改 API 以引入新功能、修复错误或提高性能。但是,也有必要确保这些更改不会干扰现有客户端。API 版本控制使我们能够在引入更改的同时保持与以前版本的兼容性。
2.2. Allowing Gradual Adoption
2.2.允许逐步采用
The clients of our APIs may have different timelines for adopting new versions. Therefore, providing multiple versions of our APIs allows clients to update their code with reasonable adoption time, reducing the risk of breaking their applications.
我们 API 的客户采用新版本的时间表可能各不相同。因此,提供多个版本的应用程序接口可以让客户在合理的时间内更新代码,降低应用程序崩溃的风险。
2.3. Facilitating Collaboration
2.3.促进合作
It also facilitates collaboration between development teams. When different teams work on other parts of a system, or third-party developers integrate with our APIs, versioning allows each team to have a stable interface, even as changes are made elsewhere.
它还有利于开发团队之间的协作。当不同团队对系统的其他部分进行开发,或第三方开发人员与我们的应用程序接口进行集成时,版本控制可以让每个团队都拥有一个稳定的界面,即使其他地方进行了更改。
3. API Versioning Strategies in Micronaut
3.Micronaut 中的 API 版本策略
Micronaut offers different strategies for implementing API versioning. We aren’t going to discuss which one is the best one as it pretty much depends on the use case and the reality of the project. Nonetheless, we can discuss the specifics of each one of them.
Micronaut为实现API版本化提供了不同的策略。我们不打算讨论哪种策略最好,因为这主要取决于用例和项目的实际情况。不过,我们可以讨论每种策略的具体细节。
3.1. URI Versioning
3.1.URI 版本
In the URI versioning, the version of the API is defined in the URI. This approach makes it clear which version of the API the client is consuming. Although the URL may not be as user-friendly as it can be, it clarifies to the client which version it uses.
在 URI 版本控制中,API 的版本是在 URI 中定义的。这种方法清楚地表明了客户端正在使用哪个版本的 API。虽然 URL 可能不那么方便用户使用,但它向客户端说明了其使用的版本。
@Controller("/v1/sheep/count")
public class SheepCountControllerV1 {
@Get(
uri = "{?max}",
consumes = {"application/json"},
produces = {"application/json"}
)
Flowable<String> countV1(@Nullable Integer max) {
// implementation
Although it may not be practical, our clients are sure about the version used, which means transparency. From the development side, it’s easy to implement any business rules specific to a particular version, meaning a good level of isolation. However, one could argue it’s intrusive as the URI may change frequently. It may require hard coding from the client side and adds extra context not precisely specific to the resource.
虽然这可能不切实际,但我们的客户可以确定所使用的版本,这意味着透明度。从开发方面来说,很容易实现特定版本的业务规则,这意味着很好的隔离性。不过,也有人会说这是一种干扰,因为 URI 可能会经常变化。它可能需要客户端进行硬编码,并增加了额外的上下文,而这些上下文并不是资源所特有的。
3.2. Header Versioning
3.2 标头版本管理
Another option to implement API versioning is to leverage the header to route the request to the right controller. Here is an example:
实现 API 版本控制的另一种方法是利用标头将请求路由到正确的控制器。下面是一个示例:
@Controller("/dog")
public class DogCountController {
@Get(value = "/count", produces = {"application/json"})
@Version("1")
public Flowable<String> countV1(@QueryValue("max") @Nullable Integer max) {
// logic
}
@Get(value = "/count", produces = {"application/json"})
@Version("2")
public Flowable<String> countV2(@QueryValue("max") @NonNull Integer max) {
// logic
}
}
By simply using the @Version annotation, Microunat can redirect the request to the proper handler based on the header’s value. However, we still need to change some configurations, as we see next:
只需使用 @Version 注解,Microunat 就能根据头的值将请求重定向到适当的处理程序。不过,我们仍然需要更改一些配置,请看下一步:
micronaut:
router:
versioning:
enabled: true
default-version: 2
header:
enabled: true
names:
- 'X-API-VERSION'
Now we just enabled versioning via Micronaut, defining version 2 as the default one in case no version is specified. The strategy used will be header-based, and the header X-API-VERSION will be used to determine the version. Actually, this is the default header Micronaut looks at, so in this case, there wouldn’t be a need to define it, but in the case we want to use another header, we could specify it like this.
现在,我们通过 Micronaut 启用了版本控制,在没有指定版本的情况下,将版本 2 定义为默认版本。使用的策略将是基于头的,头 X-API-VERSION 将用于确定版本。事实上,这是 Micronaut 默认使用的标头,所以在这种情况下,没有必要定义它,但如果我们想使用其他标头,可以这样指定。
Using headers, the URI remains clear and concise, we can preserve backward compatibility, the URI is purely resource-based, and it allows for more flexibility in the evolution of the API. However, it’s less intuitive and visible. The client has to be aware of the version he wants to use, and it’s a bit more error-prone. There is another similar strategy that consists of the use of MineTypes for this.
使用标头,URI 将保持清晰简洁,我们可以保持向后兼容性,URI 纯粹基于资源,并允许 API 更灵活地发展。不过,这种方法不太直观和可见。客户必须知道他想要使用的版本,而且这种方法更容易出错。还有一种类似的策略,即使用 MineTypes 来实现这一点。
3.3. Parameter Versioning
3.3 参数版本化
This strategy leverages query parameters in the URI to do the routing. In terms of implementation in Mircronaut, it’s exactly like the previous strategy. We just need to add the @Version in our controllers. However, we need to change some properties:
该策略利用 URI 中的查询参数进行路由选择。就 Mircronaut 的实现而言,它与前一种策略完全相同。我们只需在控制器中添加 @Version。不过,我们需要更改一些属性:
micronaut:
router:
versioning:
enabled: true
default-version: 2
parameter:
enabled: true
names: 'v,api-version'
With this, the client only needs to pass either v or api-version as query parameters in each request, and Micronat will take care of the routing for us.
这样,客户端只需在每个请求中传递 v 或 api-version 作为查询参数,Micronat 就会为我们处理路由。
When using this strategy once again, the URI will have no resource-related information, although less than having to change the URI itself. Besides that, the versioning is less explicit and more error-prone as well. This is not RESTful, and documentation is needed to avoid confusion. However, we can also appreciate the simplicity of the solution.
再次使用这种策略时,URI 将不包含与资源相关的信息,尽管这比更改 URI 本身要少。除此之外,版本管理也不那么明确,而且更容易出错。这不是 RESTful 的做法,需要有文档来避免混淆。不过,我们也可以欣赏这种简单的解决方案。
3.4. Custom Versioning
3.4.自定义版本
Micronaut also offers a custom way to implement API versioning where we can implement a versioning route resolver and show Micronaut which version to use. The implementation is simple, and we only need to implement an interface, like in the following example:
Micronaut 还提供了一种自定义方式来实现 API 版本控制,我们可以在其中实现一个版本控制路由解析器,并向 Micronaut 显示要使用的版本。实现方法很简单,我们只需实现一个接口,就像下面的例子一样:
@Singleton
@Requires(property = "my.router.versioning.enabled", value = "true")
public class CustomVersionResolver implements RequestVersionResolver {
@Inject
@Value("${micronaut.router.versioning.default-version}")
private String defaultVersion;
@Override
public Optional<String> resolve(HttpRequest<?> request) {
var apiKey = Optional.ofNullable(request.getHeaders().get("api-key"));
if (apiKey.isPresent() && !apiKey.get().isEmpty()) {
return Optional.of(Integer.parseInt(apiKey.get()) % 2 == 0 ? "2" : "1");
}
return Optional.of(defaultVersion);
}
}
Here, we can see how we can leverage any information in the request to implement a routing strategy, and Micronaut does the rest. This is powerful, but we need to be cautious because this may lead to poor and less intuitive forms of implementing versioning.
在这里,我们可以看到我们是如何利用请求中的任何信息来实施路由策略的,而 Micronaut 会完成其余的工作。这很强大,但我们需要谨慎,因为这可能会导致实施版本控制的形式不完善、不直观。
4. Conclusion
4.结论
In this article, we saw how API versioning can be implemented using Micronaut. Moreover, we also discussed the different strategies existent for applying this technique and some of their nuances.
在本文中,我们了解了如何使用 Micronaut 实现 API 版本控制。此外,我们还讨论了应用这种技术的不同策略及其细微差别。
It’s also clear that selecting the right strategy involves weighing the importance of URI cleanliness, explicitness of versioning, ease of use, backward compatibility, RESTful adherence, and the specific needs of clients consuming the API. The optimal approach depends on our project’s unique requirements and constraints.
同样明显的是,选择正确的策略需要权衡 URI 清洁度、版本明确性、易用性、向后兼容性、RESTful 遵循性以及使用 API 的客户端的特定需求的重要性。最佳方法取决于我们项目的独特要求和限制。
As usual, all code samples used in this article are available over on GitHub.
与往常一样,本文中使用的所有代码示例均可在 GitHub 上获取。