当前位置:首页 > 科技  > 软件

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

来源: 责编: 时间:2023-12-25 17:29:29 338观看
导读简介Go是整洁架构(Clean Architecture)的完美选择。整洁架构本身只是一种方法,并没有告诉我们如何构建源代码,在尝试用新语言实现时,认识到这点非常重要。自从我有了使用Ruby on Rails的经验后,尝试了好几次编写第一个服

简介

Go是整洁架构(Clean Architecture)的完美选择。整洁架构本身只是一种方法,并没有告诉我们如何构建源代码,在尝试用新语言实现时,认识到这点非常重要。DSQ28资讯网——每日最新资讯28at.com

自从我有了使用Ruby on Rails的经验后,尝试了好几次编写第一个服务,而且我读过的大多数关于Go的整洁架构的文章都以一种非Go惯用的方式介绍结构布局。部分原因是这些例子中的包是根据层命名的——controller、model、service等等……如果你有这些类型的包,这是第一个危险信号,告诉你应用程序需要重新设计。在Go中,包名[2]应该描述包提供了什么,而不是包含了什么。DSQ28资讯网——每日最新资讯28at.com

DSQ28资讯网——每日最新资讯28at.com

然后我开始了解go-kit,特别是它提供的发货示例[3],并决定在应用程序中实现相同的结构。后来,当我深入研究整洁架构(Clean Architecture)时,惊喜的发现go-kit方法是多么完美。DSQ28资讯网——每日最新资讯28at.com

本文将介绍使用Go-Kit方法编写服务是如何符合整洁架构理念的。DSQ28资讯网——每日最新资讯28at.com

整洁架构(Clean Architecture)

整洁架构(Clean Architecture)是由Bob大叔(Robert Martin)创建的一种软件架构设计。目标是分离关注点[4],允许开发人员封装业务逻辑,并使其独立于交付和框架机制。许多架构范例(如Onion和Hexagon架构)也有相同的目标,都是通过将软件划分成层来实现解耦。DSQ28资讯网——每日最新资讯28at.com

DSQ28资讯网——每日最新资讯28at.com

圆圈中的箭头表示依赖规则。如果在外部循环中声明了某些内容,则不得在内部循环代码中引用。它既适用于实际的源代码依赖关系,也适用于命名。内层不依赖于任何外层。DSQ28资讯网——每日最新资讯28at.com

外层包含低级组件,如UI、DB、传输或任何第三方服务,都可以被认为是应用程序的细节或插件。其思想是,外层的变化一定不会引起内层的任何变化。DSQ28资讯网——每日最新资讯28at.com

不同模块/组件之间的依赖关系可以描述如下:DSQ28资讯网——每日最新资讯28at.com

DSQ28资讯网——每日最新资讯28at.com

请注意,跨越边界的箭头只指向一个方向,边界后面的组件属于外层,包括controller、presenter和database。Interactor是实现BL的地方,可以将其视为用例层。DSQ28资讯网——每日最新资讯28at.com

请注意Request Model和Response Model。这些对象分别描述了内层需要和返回的数据。controller将请求(在web的情况下是HTTP请求)转换为请求模型(Request Model),presenter将响应模型(Response Model)格式化为可以由视图模型(View Model)呈现的数据。DSQ28资讯网——每日最新资讯28at.com

还要注意接口,用于反转控制流以与依赖规则相对应。Interactor通过Boundary接口与presenter对话,并通过Entity Gateway接口与数据层对话。DSQ28资讯网——每日最新资讯28at.com

这是整洁架构的主要思想,通过依赖注入分离不同的层,使用依赖反转反转控制流。Interactor(BL)和实体对传输和数据层一无所知。这一点很重要,因为如果我们改变了外层细节,内层就不会发生级联变化。DSQ28资讯网——每日最新资讯28at.com

什么是Go-Kit?

Go kit[5]是包的集合,可以帮助我们构建健壮、可靠、可维护的微服务。DSQ28资讯网——每日最新资讯28at.com

对于来自Ruby on Rails的我来说,重要的是Go-Kit不是MVC框架。相反,它将应用程序分为三层:DSQ28资讯网——每日最新资讯28at.com

  • Transport(传输)
  • Endpoint(端点)
  • Service(服务)

1.Transport

传输层是唯一熟悉交付机制(HTTP、gRPC、CLI…)的组件,这一点非常强大,因为我们可以通过提供不同的传输层来同时支持HTTP和CLI。DSQ28资讯网——每日最新资讯28at.com

稍后我们将看到传输层是如何对应于上图中的controller和presenter的。DSQ28资讯网——每日最新资讯28at.com

2.Endpoint

type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)

端点层表示应用程序中的单个RPC,将交付连接到BL。这是根据输入和输出实际定义用例的地方,在整洁架构术语中是Request Model和Response Model。DSQ28资讯网——每日最新资讯28at.com

注意,端点是接收请求并返回响应的函数,都是interface{},是RequestModel和ResponseModel。理论上也可以用类型参数(泛型)来实现。DSQ28资讯网——每日最新资讯28at.com

DSQ28资讯网——每日最新资讯28at.com

3.Service

服务层(interactor)是实现BL的地方。服务层不知道端点层,服务层和端点层都不知道传输域(比如HTTP)。DSQ28资讯网——每日最新资讯28at.com

Go-Kit提供了创建服务器(HTTP服务器/gRPC服务器等)的功能。例如HTTP:DSQ28资讯网——每日最新资讯28at.com

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
  • DecodeRequestFunc将HTTP请求转换为Request Model,并且
  • EncodeResponseFunc格式化Response Model并将其编码到HTTP响应中。
  • 返回的*server实现http.Server(有ServeHTTP方法)。

传输层使用这个函数来创建http.Server,解码器和编码器在传输中定义,端点在运行时初始化。DSQ28资讯网——每日最新资讯28at.com

简短示例:(基于发货示例[6])DSQ28资讯网——每日最新资讯28at.com

简易服务

我们将描述一个具有两个API的简单服务,用于从数据层创建和读取文章,传输层是HTTP,数据层只是一个内存映射。可以在这里找到GitHub源代码[7]。DSQ28资讯网——每日最新资讯28at.com

注意文件结构:DSQ28资讯网——每日最新资讯28at.com

- inmem  - articlerepo.go- publishing  - transport.go   - endpoint.go  - service.go  - formatter.go- article  - article.go

我们看看如何表示整洁架构的不同层。DSQ28资讯网——每日最新资讯28at.com

  • article —— 这是实体层,不包含BL、数据层或传输层的知识。
  • inmem —— 这是数据层。
  • transport —— 这是传输层。
  • endpoint+service —— 组成了边界+交互器。

从服务开始:DSQ28资讯网——每日最新资讯28at.com

import (  "context"  "fmt"  "math/rand"   "github.com/OrenRosen/gokit-example/article")type ArticlesRepository interface {   GetArticle(ctx context.Context, id string) (article.Article, error)   InsertArticle(ctx context.Context, thing article.Article) error}type service struct {   repo ArticlesRepository}func NewService(repo ArticlesRepository) *service {   return &service{      repo: repo,   }}func (s *service) GetArticle(ctx context.Context, id string) (article.Article, error) {   return s.repo.GetArticle(ctx, id)}func (s *service) CreateArticle(ctx context.Context, artcle article.Article) (id string, err error) {   artcle.ID = generateID()   if err := s.repo.InsertArticle(ctx, artcle); err != nil {      return "", fmt.Errorf("publishing.CreateArticle: %w", err)   }      return artcle.ID, nil}func generateID() string {  // code emitted}

服务对交付和数据层一无所知,它不从外层(HTTP、inmem…)导入任何东西。BL就在这里,你可能会说这里没有真正的BL,这里的服务可能是冗余的,但需要记住这只是一个简单示例。DSQ28资讯网——每日最新资讯28at.com

实体

package articletype Article struct {   ID    string   Title string   Text  string}

实体只是一个DTO,如果有业务策略或行为,可以添加到这里。DSQ28资讯网——每日最新资讯28at.com

端点

endpoint.go定义了服务接口:DSQ28资讯网——每日最新资讯28at.com

type Service interface {   GetArticle(ctx context.Context, id string) (article.Article, error)   CreateArticle(ctx context.Context, thing article.Article) (id string, err error)}

然后为每个用例(RPC)定义一个端点。例如,对于获取文章::DSQ28资讯网——每日最新资讯28at.com

type GetArticleRequestModel struct {   ID string}type GetArticleResponseModel struct {   Article article.Article}func MakeEndpointGetArticle(s Service) endpoint.Endpoint {   return func(ctx context.Context, request interface{}) (response interface{}, err error) {      req, ok := request.(GetArticleRequestModel)      if !ok {         return nil, fmt.Errorf("MakeEndpointGetArticle failed cast request")      }            a, err := s.GetArticle(ctx, req.ID)      if err != nil {         return nil, fmt.Errorf("MakeEndpointGetArticle: %w", err)      }            return GetArticleResponseModel{         Article: a,      }, nil   }}

注意如何定义RequestModel和ResponseModel,这是RPC的输入/输出。其思想是,可以看到所需数据(输入)和返回数据(输出),甚至无需读取端点本身的实现,因此我认为端点代表单个RPC。服务具有实际触发BL的方法,但是端点是RPC的应用定义。理论上,一个端点可以触发多个BL方法。DSQ28资讯网——每日最新资讯28at.com

传输

transport.go注册HTTP路由:DSQ28资讯网——每日最新资讯28at.com

type Router interface {   Handle(method, path string, handler http.Handler)}func RegisterRoutes(router *httprouter.Router, s Service) {   getArticleHandler := kithttp.NewServer(      MakeEndpointGetArticle(s),      decodeGetArticleRequest,      encodeGetArticleResponse,   )      createArticleHandler := kithttp.NewServer(      MakeEndpointCreateArticle(s),      decodeCreateArticleRequest,      encodeCreateArticleResponse,   )      router.Handler(http.MethodGet, "/articles/:id", getArticleHandler)   router.Handler(http.MethodPost, "/articles", createArticleHandler)}

传输层通过MakeEndpoint函数在运行时创建端点,并提供用于反序列化请求的解码器和用于格式化和编码响应的编码器。DSQ28资讯网——每日最新资讯28at.com

例如:DSQ28资讯网——每日最新资讯28at.com

func decodeGetArticleRequest(ctx context.Context, r *http.Request) (request interface{}, err error) {   params := httprouter.ParamsFromContext(ctx)   return GetArticleRequestModel{      ID: params.ByName("id"),   }, nil}func encodeGetArticleResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {   res, ok := response.(GetArticleResponseModel)   if !ok {      return fmt.Errorf("encodeGetArticleResponse failed cast response")   }      formatted := formatGetArticleResponse(res)   w.Header().Set("Content-Type", "application/json")   return json.NewEncoder(w).Encode(formatted)}func formatGetArticleResponse(res GetArticleResponseModel) map[string]interface{} {  return map[string]interface{}{    "data": map[string]interface{}{      "article": map[string]interface{}{        "id":    res.Article.ID,        "title": res.Article.Title,        "text":  res.Article.Text,      },    },  }}

你可能会问,为什么要使用另一个函数来格式化article,而不是在article实体上添加JSON标记?DSQ28资讯网——每日最新资讯28at.com

这是个非常重要的问题。在article实体上添加JSON标记意味着article知道它是如何格式化的。虽然没有显式导入到HTTP,但打破了抽象,使实体包依赖于传输层。DSQ28资讯网——每日最新资讯28at.com

例如,假设你想将对客户端的响应从"title"更改为"header",此更改仅涉及传输层。但是,如果此需求导致需要更改实体,则意味着该实体依赖于传输层,这就破坏了简洁架构原则。DSQ28资讯网——每日最新资讯28at.com

我们看看这个简单应用的依赖关系图:DSQ28资讯网——每日最新资讯28at.com

DSQ28资讯网——每日最新资讯28at.com

哇,你一定注意到了它们的相似性!article实体没有依赖关系(只有向内箭头)。外层,transport和inmem,只有指向BL和实体内层的箭头。DSQ28资讯网——每日最新资讯28at.com

一切都和转换有关

跨界就是不同层次语言之间的转换。DSQ28资讯网——每日最新资讯28at.com

BL层只使用应用语言,也就是说,只知道实体(没有HTTP请求或SQL查询)。为了跨越边界,流中的某个组件必须将应用语言转换为外层语言。DSQ28资讯网——每日最新资讯28at.com

在传输层,有解码器(将HTTP请求转换为RequestModel的应用语言)和编码器(将应用语言ResponseModel转换为HTTP响应)。DSQ28资讯网——每日最新资讯28at.com

数据层实现了repo,在我们的例子中是inmem。在另一种情况下,我们可能会让sql包负责将应用语言转换为SQL语言(查询和原始结果)。DSQ28资讯网——每日最新资讯28at.com

"ing"包

你可能会说传输和服务不应该在同一个包中,因为它们位于不同的层,这是一个正确的论点。我从go-kit的shipping例子中取了一个例子,含有这种设计,ing包包含了传输/端点/服务,我发现从长远来看非常方便。话虽如此,如果我现在写的话,可能会用不同的包。DSQ28资讯网——每日最新资讯28at.com

最后关于"尖叫架构(Screaming Architecture)"的一句话

Go非常适合简洁架构的另一个原因是包的命名及其思想。尖叫架构(Screaming Architecture) 和构建应用程序有关,以便应用程序的意图显而易见。在Ruby On Rails中,当查看结构时,就知道它是用Ruby On Rails框架编写的(控制器、模型、视图……)。在我们的应用程序中,当查看结构时,可以看出这是一个关于文章的应用程序,有发布用例,并使用inmem数据层。DSQ28资讯网——每日最新资讯28at.com

总结

简洁架构只是一种方法,并不会告诉你如何构建源代码,其实现艺术在于了解所用语言的使用惯例和工具。希望这篇文章对你有所帮助,重要的是要意识到,那些争论设计问题解决方案的文章并不总是对的,当然也包括这篇DSQ28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-54023-0.html基于Go-Kit的Golang整洁架构实践

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Graalvm 替代 JVM 真的可以带来巨大的性能优势吗?

下一篇: 别再乱用了,Java 21 将弃用、删除这些功能!

标签:
  • 热门焦点
  • 一加Ace2 Pro官宣:普及16G内存 引领24G

    一加官方今天继续为本月发布的新机一加Ace2 Pro带来预热,公布了内存方面的信息。“淘汰 8GB ,12GB 起步,16GB 普及,24GB 引领,还有呢?#一加Ace2Pro#,2023 年 8 月,敬请期待。”同时
  • JavaScript学习 -AES加密算法

    引言在当今数字化时代,前端应用程序扮演着重要角色,用户的敏感数据经常在前端进行加密和解密操作。然而,这样的操作在网络传输和存储中可能会受到恶意攻击的威胁。为了确保数据
  • 雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    来源 | 烈酒商业观察编 | 肖海林今年以来,威士忌市场开始出现了降温迹象,越来越多不断暴涨的网红威士忌也开始悄然回归市场理性。近日,LVMH集团旗下苏格兰威士忌品牌雅柏(Ardbeg
  • 拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    Tech星球(微信ID:tech618)文 | 陈桥辉 Tech星球独家获悉,拼多多在其APP内上线了“本地生活”入口,位置较深,位于首页的“充值中心”内,目前主要售卖美食相关的
  • 本地生活这块肥肉,拼多多也想吃一口

    出品/壹览商业 作者/李彦编辑/木鱼拼多多也看上本地生活这块蛋糕了。近期,拼多多在App首页“充值中心”入口上线了本机生活界面。壹览商业发现,该界面目前主要
  • 中国家电海外掘金正当时|出海专题

    作者|吴南南编辑|胡展嘉运营|陈佳慧出品|零态LT(ID:LingTai_LT)2023年,出海市场战况空前,中国创业者在海外纷纷摩拳擦掌,以期能够把中国的商业模式、创业理念、战略打法输出海外,他们依
  • ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大“烤”之下的除了众生,还有各大企业发布的ESG报告。ESG是“环境保
  • 国行版三星Galaxy Z Fold5/Z Flip5发布 售价7499元起

    2023年8月3日,三星电子举行Galaxy新品中国发布会,正式在国内推出了新一代折叠屏智能手机三星Galaxy Z Fold5与Galaxy Z Flip5,以及三星Galaxy Tab S9
  • 7月4日见!iQOO 11S官宣:“鸡血版”骁龙8 Gen2+200W快充加持

    上半年已接近尾声,截至目前各大品牌旗下的顶级旗舰都已悉数亮相,而下半年即将推出的顶级旗舰已经成为了数码圈爆料的主流,其中就包括全新的iQOO 11S系
Top