通过基于 Go 的微服务框架 goa,实现对服务 API 的定义、审查与实现

  • Daniel Bryant
  • 邵思华

2016 年 1 月 5 日

话题:语言 & 开发架构

Raphael Simon 是来自于 RightScale 的一位高级系统架构师,他创建了一种基于 Go 语言的 HTTP 微服务框架,名为“goa”。这一框架允许开发者通过领域特定语言(DSL)定义服务的 API,并且通过自动代码生成功能创建“样板”式的服务端和客户端代码以及文档。

Raphael 在他的博客Gopher Academy上发表了一篇介绍 goa 框架的帖子,他提到 RightScale 的工程师团队正在将一个一体性的 Ruby on Rails 应用迁移为一系列基于 Go 语言创建的微服务应用。这次迁移工作的一个主要挑战在于如何创建设计良好的服务 API,为了支持整个微服务 API 的设计、审查以及实现,他们创建了一系列的工具。这一项目所产生的结果之一就是基于 Go 语言的框架 goa 的诞生,它的出现受到了在 RightScale 中所应用的一个基于 Ruby 的 API 开发框架Praxis的启发。

InfoQ 与Simon进行了一次访谈,他既是RightScale的高级系统架构师,也是 goa 的主要贡献者之一。这次访谈主要问及了关于 goa 这一微服务框架的设计目标、使用方式以及未来的发展计划。

InfoQ:欢迎你 Raphael!请你为读者说明一下“goa”是个怎样的产品,以及创建这一架构的动力是什么。

Simon:RightScale 正在向微服务风格的架构进行迁移,这项工作已经开展了一段时间了,而最近一段时间团队开始使用 Go 语言编写部分服务。总体来说,无论在开发还是运维方面,Go 都证明了它是一个优秀的选择。这门语言很容易上手,并且所创建的服务表现也很出色。

不过,采用 Go 语言进行开发也意味着在开发与部署阶段要引入一种新的技术栈,因为直至最近,RightScale 基本上都是使用 Ruby 进行开发的。团队在创建与运行 REST API(RightScale 已经将这一使用 Ruby 语言打造、用于创建 REST API 的Praxis 框架开源了)方面已经具备了丰富的经验。由于目前还没有一种现成的解决方案可以应对我们的需求,因此有理由运用这些经验去打造基于一种 Go 语言的服务开发栈。

这项工作的成果就是 goa 的诞生,它是一个微服务的开发框架,通过它能够以代码的方式描述 API 的设计。与 goa 框架一同推出的还有一个 goagen 工具,它能够通过设计代码生成各种输出,包括 http 服务器的封装、代码脚手架、文档、客户端,甚至是自定义的输出。goa 包还包括大量的支持性模块,服务本身与生成的代码都能够利用这些模块进行服务的实现。

InfoQ:与使用标准的 Go 类库(或者使用 Peter Bourgon 开发的 go-kit)相比,“goa”通过怎样的方式简化了微服务的开发?

Simon:goa 框架能够描述 API 的意图。通过使用 goa 设计语言,开发者能够定义 API 所暴露的资源与行为(即 API 的终结点)。对于每种行为的描述包括所期待的请求状态,以及各种可能产生的响应。这种方式能够带来许多益处:举例来说,它在设计阶段就能够起到很大的作用,各个团队能够通过 Swagger UI 生成 Swagger 的规格说明,从而提出反馈意见。这一反馈循环能够为负责生成 UI 或编写面向用户的文档的团队带来极大的便利。最大的优点在于,这一切都发生在实际编写代码实现之前。

这种方式的另一优点在于,一旦设计完成之后,goa 就将负责生成所有样板逻辑,包括请求的验证以及用于描述请求状态的自定义数据结构。这样一来,设计就变成了实现所必需的一部分。这就体现出了这一方式具有很高价值的一面,否则的话,维持实现与设计之间的同步就成了非常一项非常繁琐的流程。对于每个暴露了访问功能的行为来说,goa 能够生成特定的请求处理逻辑的上下文,从中返回符合设计所描述的数据结构。这种做法减少了对“绑定”方式的需求,也不必手动地验证所传入的请求的格式。

goagen 也能够为 API 生成相应的客户端,包括一个命令行工具、Go 包和 JavaScript 的客户端。这种方式显而易见的好处是免去了手动进行这一过程所花的时间,除此之外,它还有助于维护多个客户端之间的一致性,并且保持他们与 API 的同步。在微服务的世界中,服务的数量是持续增长的,因此保持他们之间的一致性是一种非常有价值的做法,它大大地简化了构建整合的流程。

InfoQ:“goa”看起来为开发者提供了很多内容,包括一个运行时“引擎”、支持日志记录、能够对“中间件”进行自定义、还支持错误处理。你是否打算为基于 Go 的微服务架构提供一个单一的解决方案?

Simon:goa 中的请求上下文是运行时引擎的核心,它引用了 golang 这个包的开发者所做的工作,主要是跨接口传递上下文的功能,从而实现一些强大的特性,例如设置 deadline,以及在 goroutine 之间共享状态信息。举例来说,上下文信息能够一路传递至负责生成外部请求的软件模块,因此,在整个周期中的任何阶段,都能够获取包括 deadline 在内的整个请求状态。生成的代码将以一种特定于行为的数据结构对上下文进行封装,通过在设计代码中所描述的字段暴露已验证的请求的状态。

而其余部分的行为都是能够进行替换的:goa 的设计遵循了“提供内置支持”的模型。举例来说,如果你对于如何进行错误处理并没有非常确定的想法,那么 goa 将为你提供一种恰当的默认实现(在这种情况下所返回的响应将包括 500 状态码)。不过,开发者也能够方便地覆盖默认的错误处理逻辑,以实现任何自定义的逻辑,goa 还提供了一种备选的错误处理逻辑,在响应中将不包括调用栈的信息。另一个例子是对中间件的支持:goa 定义了一种专用类型的中间件,可以通过它利用丰富的上下文信息,但同时也能够使用普通的 http 中间件。

目前来说,并非所有特性都能够被轻易地替换(例如日志记录),因为 goa 最初的关注点在于代码的生成。总体目标是提供一个完整的、模块化的微服务开发框架,因此,曾使用过 go-kit 或其它 Go 包的开发者依然能够继续使用它们,而同时又能够受益于 goa 所提倡的基于设计的 API 设计方式。

InfoQ:我们很有兴趣了解一下关于生成能够调用服务 API 的客户端代码的特性。通常来说,有关代码生成的讨论总是会得到一些消极的回应,例如生成的代码过于复杂,或是生成过程不够透明、也无法进行自定义。你能否为我们解释一下你选择这种实现方式的原因?

Simon:这里的诀窍在于所生成的代码实现的功能只是一些繁琐的样板代码,这些生成代码的结构与你“手写”这些代码的结果是相同的。goagen 尽了最大努力以试图遵循“尽量减少令人惊讶的部分”的原则。你甚至可以说生成的最终代码看起来更“标准”(并且更高效)。举例来说,它减轻了对反射的依赖,否则的话,实现相同的功能可能要写上几千行代码。我们可以以客户端的 Go 包为例,你恐怕很难想到它居然是自动生成的。好吧,或许有些本地变量的命名看起来有点古怪,这可能会使你查觉到真相 :)

goa 的代码生成还有一个十分重要的特点,即所生成的代码始终处于一个不同的包中,而无需进行手动修改,从而清晰地区分了用户代码与生成的代码。goagen 将始终重新生成整个包,因此不可能出现用户代码与生成代码相混合的情况。这里有一种例外情况,即 goagen 可以在编码的起始阶段生成一种一次性的脚手架代码。

最后,goagen 只会生成必要的代码,所生成的代码将使用 goa 包实现各种通用的功能,例如日志记录、错误处理等等。它提供了一种优秀的方式对生成代码的行为进行自定义,而无需进行手动修改。举例来说,你可以实现一种自定义的服务错误处理逻辑,并且让所有生成代码自动调用这段自定义逻辑。你可以将这种生成代码想象为一种 goa 包的“插件”,并暴露相应的调节器以修改它的行为。

goagen 也支持插件系统,它实质上就是一种标准的 Go 包,其中包含了一个公开的 Generate 函数。由 Brian Ketelsen 所编写的gorma 插件是对这一项目最早的贡献之一,它能够通过 API 设计中所描述的类型生成gorm模型。整个项目最令人惊讶的部分就在于通过代码(以及其他功能)生成所带来的各种可能性。在我看来,这一设想已经很明显地在 Go 语言中得到了很好的表现。

InfoQ:如果 InfoQ 的读者有兴趣加入“goa”项目,或是为其贡献代码,最好的途径是什么?

Simon:在 GitHub 库上的 wiki 中包括一份路线图的文档,其中可以找到许多建议,但这些仅仅是建议而已。这一项目最值得赞扬的一点在于它在社区中得到了快速的应用,而且在我所没有考虑到的方面也得到了许多代码贡献。goa 依然是一个很新的构架,整个项目的发展方向存在于许多可能性。此外,在gophers.slack.com上还有一个活跃的 #goa slack 频道。因此,建议大家去访问GitHub 库、加入 slack 频道,并且开始尝试!

本项目确实有一个地方或许需要大家的帮助:它需要一个 logo!它的网站页面可能也需要动一下整形手术 ;)

InfoQ:Raphael,感谢你参加今日的访谈。对于 InfoQ 的读者,你还有什么内容想同大家分享的吗?

Simon:感谢你的邀请!我推荐所有希望创建 API 的读者们去尝试一下 goa,我真的相信它能够帮助你们更高效地打造服务。微服务风格的架构如今已经快速地成长为事实上的标准,这使得设计优秀的 API 这一任务显得更为关键,而优秀的工具能够起到极大的作用。随着 goa 的使用率不断增长,我对于它的发展充满期待,并且迫不及待地想看到用户能够通过它实现哪些功能。

如果想了解有关 goa 微服务框架的更多信息,可以阅读 Gopher Academy 博客上的“goa: Untangling Microservices”这篇帖子,并访问goa 的 GitHub 库

查看英文原文:Defining, Reviewing and Implementing Service APIs with “goa”, a Go-based Microservice Framework

语言 & 开发架构