才云开源 Nirvana:Golang REST API 框架

栏目: Go · 发布时间: 5年前

内容简介:自 2009 年开源以来,Go 作为一种强大、高效、简洁、易上手的编程语言,在帮助阅读、调试和维护大型软件系统上发挥着越来越重要的作用。而依托其健康生态,Golang 社区也相继涌现出诸如 beego、gin、chi、go-restful 等知名框架,为 Go 提供额外功能支持。但选择过多,反受其乱。面对层出不穷的优秀框架,不同团队、不同开发者在框架选择上往往会出现分歧,不同框架之间也彼此壁垒高筑,导致业务与框架耦合,开发效率大大降低。为了解决这类问题,才云 Caicloud 实现了 Golang API

自 2009 年开源以来,Go 作为一种强大、高效、简洁、易上手的编程语言,在帮助阅读、调试和维护大型软件系统上发挥着越来越重要的作用。而依托其健康生态,Golang 社区也相继涌现出诸如 beego、gin、chi、go-restful 等知名框架,为 Go 提供额外功能支持。

但选择过多,反受其乱。面对层出不穷的优秀框架,不同团队、不同开发者在框架选择上往往会出现分歧,不同框架之间也彼此壁垒高筑,导致业务与框架耦合,开发效率大大降低。

为了解决这类问题,才云 Caicloud 实现了 Golang API 框架 Nirvana,把 API 从对框架的依赖中彻底解放出来。它专为提高生产力和可用性设计,可扩展、性能高,旨在成为才云 Caicloud 所有 Golang 服务的基石,助力业务的高速开发。

接轨 Kubernetes 这些痛点不可忽视

近年来,为了满足业务发展需要,应对日益激烈的市场竞争,大量企业开始使用容器部署云工作负载。而随着容器使用量的增长、Kubernetes 成为容器编排的事实标准,在 Kubernetes 上打造新一代容器云平台成了企业谋求发展的必由之路。

为了顺应时局,技术团队除了进行思维上的转变,还要应对团队和业务方向的调整,对团队项目和产品完成切割分化。这之中就涉及对 Go 框架变更的抉择。

两年前,才云 Caicloud 团队在构建基于 Kubernetes 的云平台时,在框架上遇到了不少麻烦:当时 Kubernetes 正值发展期,为了快速开发,工程师们往往倾向于选择自己熟悉的框架。因此,虽然大多数项目用的是 go-restful(Kubernetes 的选型),但用 beego、gin、chi 等框架的工程师也不在少数,这就给业务整合带来了很多问题:

  • 不同框架对 API 的描述形式不一致,这增加了不同团队间的沟通成本;
  • 不同框架 API 风格差距较大,文档自动化困难;
  • 错误定义和处理方式在不同项目之间存在差别,会导致客户端处理困难。

曾经开放的工程师文化成了变革的最大阻力。

Nirvana 的缘起

Nirvana 就是在这个背景下诞生的。

它借鉴了 Kubernetes 声明式 API 的设计,巧妙规避了框架对 API 的侵入。同时,为了实现业务和框架的隔离,它定义一个规范,让 API 按照规范书写,完全屏蔽了框架对 API 的影响。

一言以蔽之,Nirvana 框架的设计思路始终围绕工程师们亲历的种种痛点:

  • 构建风格一致的 API 项目;
  • 使用统一的 RESTful 路由与声明式 API 定义,避免业务对框架产生依赖;
  • 使用统一的错误生成和处理方式;
  • 提供开箱即用的基础功能,包括 Prometheus metrics、tracing、profiling 等;
  • 自动生成 API 文档和客户端代码。

在 Nirvana 中,我们用一套 API Definition 来声明式地描述业务 API。下面是一个列出消息列表 API 的例子:

复制代码

Definition{
// 这个 API 返回的是资源数组,所以使用 List 方法。
Method:def.List,
// Summary 是一个短语,用于描述这个 API 的用途。这个短语在生成文档和客户端的时候用于区分 API。
// 这个字符串去掉空格后会作为生成客户端时的函数名,因此请确保这个字符串是有意义的。
Summary:"List Messages",
// 详细描述这个 API 的用途。
Description:"Query a specified number of messages and returns an array",
// 业务函数
Function: message.ListMessages,
// 对应业务函数的参数信息。用于告知 Nirvana 从请求的那一部分取得数据,然后传递给业务函数。
Parameters: []def.Parameter{
{
// 参数来源
Source:def.Query,
// 参数名称,作为 key 从 Source 里取值。
// 与业务函数的参数名称无关。
Name:"count",
// 默认值
Default:10,
// 参数描述
Description:"Number of messages",
},
},

// 对应业务函数的返回结果。用于告知 Nirvana 业务函数返回结果如何放到请求的响应中。
Results:def.DataErrorResults("A list of messages"),
}

而对应业务 API 的形式则是:

复制代码

typeMessagestruct{
IDint`json:"id"`
Titlestring`json:"title"`
Contentstring`json:"content"`
}

funcListMessages(ctx context.Context, countint)([]Message, error){

messages :=make([]Message, count)
fori :=0; i < count; i++ {
messages[i].ID = i
messages[i].Title = fmt.Sprintf("Example %d", i)
messages[i].Content = fmt.Sprintf("Content of example %d", i)
}
returnmessages,nil
}

很显然,业务函数并不关心自身是如何暴露给外部的,实现方法也和其他内部函数没有差别(这只是一个简单的例子,更多详细内容请查阅 Nirvana Definition 文档 )。

在这个例子里,API Definition 描述了完整的 API 结构,包括 RESTful 路径、请求方法、请求描述、请求参数、请求响应和请求 handler。框架只需要解析 API Definition,就能得到业务逻辑的入口和出口处理方式。对开发者而言,API 的开发过程从“ 命令式路由 + 数据转换 + 业务逻辑” 变成了“API Definition + 业务逻辑”。框架与业务逻辑之间通过 API Definition 进行桥接。

Nirvana 框架

API Definition 作为声明式 API,除了让框架读取信息生成路由和对外提供服务,它本身也完整描述了一个 API 的工作方式。因此,我们还能用它生成 openapi 文档和客户端。在接下来的几个小节中,我们将详述 Nirvana 提供的这些能力。

路由工作流

任何一个 API 框架都具备基本的 HTTP Serve 能力( HTTP 接口服务能力)。在 Nirvana 中,HTTP Serve 由 Golang 基础库 http 提供。HTTP 请求的路由方式如下图所示:

才云开源 Nirvana:Golang REST API 框架

在这个请求的工作流中:

  • Filters 是请求过滤器,可以对不符合要求的请求进行提前响应,而不会进入路由匹配过程;
  • Middlewares 是围绕请求的中间件,能够控制请求的处理行为;
  • Executor 是最终处理请求的执行器,负责通过参数生成器(ParameterGenerators)和结果处理器(DestinationHandlers)将请求注入到业务逻辑之中,并将返回结果处理后返回。

在这里,Filters、Middlewares、ParameterGenerators 和 DestinationHandlers 都是可以被开发者重新定义的。这也意味着开发者可以改变 Nirvana 的原生处理行为,使之符合自身需求。

服务构建工作流

Nirvana 另一个工作流是将 API Definition 构建到路由中去,并完成整个服务的启动工作。

才云开源 Nirvana:Golang REST API 框架

在 Nirvana 启动时,除 API Definition 之外,它还能够通过插件往框架里注入一些功能,包括注册 Filters、Middlewares、添加其他路由 endpoint 等。目前 Nirvana 已经提供对 metrics、profiling、tracing、log 等常用插件的支持。

通过以上两个工作流,以及 Nirvana 中大量的模块方法注册接口,开发者可以自行扩展 API Definition 的能力,让 API Definition 与业务逻辑更加贴合。API Definition 单向依赖并描述了业务逻辑,并作为框架和业务之间的桥梁,既达到了声明式 API 的定义形式,也满足了业务不依赖框架的目标。

框架扩展

无论是路由还是服务构建过程,Nirvana 的实现都不是 hardcode 的。在框架的大部分包里,你能发现类似以下形式的代码结构:

复制代码

varconsumers = map[string]Consumer{
definition.MIMENone: &NoneSerializer{},
definition.MIMEText: NewSimpleSerializer(definition.MIMEText),
definition.MIMEJSON: &JSONSerializer{},
definition.MIMEXML: &XMLSerializer{},
definition.MIMEOctetStream: NewSimpleSerializer(definition.MIMEOctetStream),
definition.MIMEURLEncoded: &URLEncodedConsumer{},
definition.MIMEFormData: &FormDataConsumer{},
}

// RegisterConsumer register a consumer. A consumer must not handle "*/*".
func RegisterConsumer(c Consumer) error {
ifc.ContentType() == definition.MIMEAll{
return invalidConsumer.Error(definition.MIMEAll)
}
consumers[c.ContentType()] = c
return nil
}

在这种结构里,开发者可以自定义每一块的实现方式,甚至替换框架默认行为。帮助自己最大限度地利用 Nirvana,使它符合业务特定需求。

插件机制

在服务构建工作流中,我们提到了插件机制。下面我们会介绍三个重要插件 metrics、profiling 和 tracing。

  1. metrics 插件

Prometheus 作为 CNCF 的开源项目,为我们提供了非常适于描述业务指标的格式:Prometheus Metrics。Nirvana 提供的 metrics 插件可以在启动时默认暴露 /metrics 接口。对此,开发者可以将业务指标注册到 Prometheus 中,通过这个接口让 Prometheus 采集。也就是说,Nirvana 不仅能帮助开发者了解业务的运行状态,也为开发者定位业务问题提供了强有力的帮助。

  1. profiling 插件

Golang 提供了进程级别的 profiling 能力。Nirvana 通过 profiling 插件直接将这个能力注入到框架中。开发者只需要启用插件即可获得这些调试和性能测试能力,获得业务内部执行的状态信息。

  1. tracing 插件

在 CloudNative 时代,业务通常会以微服务或 Serverless 的形式进行架构。多个服务之间的交互通常是黑盒的,这易导致我们难以定位分布式架构中的服务问题。Tracing 作为解决分布式架构下的调用链方案,可以在分布式系统出现问题时为开发者提供大量的信息,帮助开发者排查问题。Nirvana 的 tracing 插件使用了 open-tracing 的接口规范,并借助 CNCF 开源项目 jaeger 的能力,为开发者提供一站式 tracing 方案。

文档和客户端生成

API Definition 和 Nirvana 的结合帮助我们完成了服务构建。在服务之外,通过 API Definition 的描述能力,Nirvana 还实现了基于 openapi 的文档生成和客户端生成。

Nirvana 的文档生成基于 openapi 2.0(即 swagger 2.0)规范,从 API Definition 中提取 API 信息,生成 API 描述文件 swagger.json。除了生成 API 描述文件之外,它还能通过 ReDoc 直接提供文档服务(更多信息请查看 文档 )。

在 Nirvana 中,生成文档的方式十分简单:

$ nirvana api --serve=":8081"

只需一条命令,API 文档即可生成,并通过 8081 端口提供服务。之后我们就能通过网页查看文档了:

才云开源 Nirvana:Golang REST API 框架

Nirvana 的客户端生成同样基于 API Definition ——读取类型信息,生成客户端包(目前仅支持生成 Golang 包)。客户可以直接引用这个包来调用服务,整个客户端的使用方式与本地调用一致(更多信息请查看 文档 )。

下面是一键生成客户端的示例:

$ nirvana client

这个命令会在当前目录下生成一个 client 目录,在这个目录内生成 Golang 客户端代码:

复制代码

typeClientstruct{
rest *rest.Client
}

// ListMessages returns all messages.
func (c *Client)ListMessages(ctxcontext.Context,countint)(messages[]Message, err error) {
err = c.rest.Request("GET", 200,"/apis/v1/messages").
Query("count",count).
Data(&messages).
Do(ctx)
return
}

如果您想进一步了解 Nirvana,可以参考才云 Caicloud 云原生 CI/CD 项目 Cyclone 。它包含一个 API 组件 Cyclone Server, 基于 Nirvana 对外提供简单易用的 API。

Nirvana:涅槃

Nirvana,梵语中的涅槃。

一如我们对它的期待:让 API 从对框架的依赖中涅槃重生。

经过一年多的开源,目前 Nirvana 在基础 API 服务能力上已经经过内部验证。由于数据传输层和转换层的复杂度被大大降低,才云 Caicloud 也切实体验到了这个开源框架带来的便捷,以及它在加速业务开发上的显著效果。

未来,Nirvana 将在以下几方面继续发力:

  • 持续优化扩展文档和客户端生成的能力,降低开发者在这两块上的心智负担;
  • 持续优化 metrics、profiling、tracing 的能力,并增加新的云原生能力,让这些能力成为云原生应用的标配;
  • 框架模块化加强,让 Nirvana 的每一块代码皆可定制;
  • 优化框架性能,降低反射对服务的影响;
  • 让 Nirvana 成为 Golang 的 CloudNative & SOA 框架。

Nirvana 正致力于成为让业务无感知的框架。目前,才云 Caicloud 正在这条路上踽踽独行,我们也真诚地希望将来能有更多开发者愿意加入进来,一起构建 Nirvana,一起建设 Golang 社区,一起拥抱开源的胜利。

也许你的加入,能让这场涅槃更加炫目!

感谢参与开源本项目的所有开发者!

Nirvana GitHub: https://github.com/caicloud/nirvana

Nirvana 文档: https://caicloud.github.io/nirvana/zh-hans/


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Boolean Reasoning

Boolean Reasoning

Brown, Frank Markham / 2003-4 / $ 19.15

A systematic treatment of Boolean reasoning, this concise, newly revised edition combines the works of early logicians with recent investigations, including previously unpublished research results. Th......一起来看看 《Boolean Reasoning》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具