基于Go-Kit的Golang整洁架构实践

简介Go是整洁架构(Clean Architecture)的完美选择 。整洁架构本身只是一种方法 , 并没有告诉我们如何构建源代码,在尝试用新语言实现时,认识到这点非常重要 。
自从我有了使用Ruby on RAIls的经验后,尝试了好几次编写第一个服务,而且我读过的大多数关于Go的整洁架构的文章都以一种非Go惯用的方式介绍结构布局 。部分原因是这些例子中的包是根据层命名的——controller、model、service等等……如果你有这些类型的包,这是第一个危险信号 , 告诉你应用程序需要重新设计 。在Go中,包名[2]应该描述包提供了什么,而不是包含了什么 。

基于Go-Kit的Golang整洁架构实践

文章插图
然后我开始了解go-kit,特别是它提供的发货示例[3],并决定在应用程序中实现相同的结构 。后来,当我深入研究整洁架构(Clean Architecture)时,惊喜的发现go-kit方法是多么完美 。
本文将介绍使用Go-Kit方法编写服务是如何符合整洁架构理念的 。
整洁架构(Clean Architecture)整洁架构(Clean Architecture)是由Bob大叔(Robert Martin)创建的一种软件架构设计 。目标是分离关注点[4],允许开发人员封装业务逻辑,并使其独立于交付和框架机制 。许多架构范例(如Onion和Hexagon架构)也有相同的目标,都是通过将软件划分成层来实现解耦 。
基于Go-Kit的Golang整洁架构实践

文章插图
圆圈中的箭头表示依赖规则 。如果在外部循环中声明了某些内容,则不得在内部循环代码中引用 。它既适用于实际的源代码依赖关系,也适用于命名 。内层不依赖于任何外层 。
外层包含低级组件,如UI、DB、传输或任何第三方服务,都可以被认为是应用程序的细节或插件 。其思想是,外层的变化一定不会引起内层的任何变化 。
不同模块/组件之间的依赖关系可以描述如下:
基于Go-Kit的Golang整洁架构实践

文章插图
请注意,跨越边界的箭头只指向一个方向 , 边界后面的组件属于外层,包括controller、presenter和database 。Interactor是实现BL的地方,可以将其视为用例层 。
请注意Request Model和Response Model 。这些对象分别描述了内层需要和返回的数据 。controller将请求(在web的情况下是HTTP请求)转换为请求模型(Request Model),presenter将响应模型(Response Model)格式化为可以由视图模型(View Model)呈现的数据 。
还要注意接口 , 用于反转控制流以与依赖规则相对应 。Interactor通过Boundary接口与presenter对话,并通过Entity Gateway接口与数据层对话 。
这是整洁架构的主要思想,通过依赖注入分离不同的层,使用依赖反转反转控制流 。Interactor(BL)和实体对传输和数据层一无所知 。这一点很重要,因为如果我们改变了外层细节,内层就不会发生级联变化 。
什么是Go-Kit?Go kit[5]是包的集合,可以帮助我们构建健壮、可靠、可维护的微服务 。
对于来自Ruby on Rails的我来说,重要的是Go-Kit不是MVC框架 。相反,它将应用程序分为三层:
  • Transport(传输)
  • Endpoint(端点)
  • Service(服务)
1.Transport传输层是唯一熟悉交付机制(HTTP、gRPC、CLI…)的组件 , 这一点非常强大,因为我们可以通过提供不同的传输层来同时支持HTTP和CLI 。
稍后我们将看到传输层是如何对应于上图中的controller和presenter的 。
2.Endpointtype Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)端点层表示应用程序中的单个RPC,将交付连接到BL 。这是根据输入和输出实际定义用例的地方,在整洁架构术语中是Request Model和Response Model 。
 
注意,端点是接收请求并返回响应的函数 , 都是interface{},是RequestModel和ResponseModel 。理论上也可以用类型参数(泛型)来实现 。
 
3.Service服务层(interactor)是实现BL的地方 。服务层不知道端点层 , 服务层和端点层都不知道传输域(比如HTTP) 。
Go-Kit提供了创建服务器(HTTP服务器/gRPC服务器等)的功能 。例如HTTP:
package http // under go-kit/kit/transport/httptype DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) errorfunc NewServer(e endpoint.Endpoint,dec DecodeRequestFunc,enc EncodeResponseFunc,options ...ServerOption,) *Server


推荐阅读