日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

基于Go-Kit的Golang整潔架構實踐

來源: 責編: 時間:2023-12-25 17:29:29 263觀看
導讀簡介Go是整潔架構(Clean Architecture)的完美選擇。整潔架構本身只是一種方法,并沒有告訴我們如何構建源代碼,在嘗試用新語言實現時,認識到這點非常重要。自從我有了使用Ruby on Rails的經驗后,嘗試了好幾次編寫第一個服

簡介

Go是整潔架構(Clean Architecture)的完美選擇。整潔架構本身只是一種方法,并沒有告訴我們如何構建源代碼,在嘗試用新語言實現時,認識到這點非常重要。GDr28資訊網——每日最新資訊28at.com

自從我有了使用Ruby on Rails的經驗后,嘗試了好幾次編寫第一個服務,而且我讀過的大多數關于Go的整潔架構的文章都以一種非Go慣用的方式介紹結構布局。部分原因是這些例子中的包是根據層命名的——controller、model、service等等……如果你有這些類型的包,這是第一個危險信號,告訴你應用程序需要重新設計。在Go中,包名[2]應該描述包提供了什么,而不是包含了什么。GDr28資訊網——每日最新資訊28at.com

GDr28資訊網——每日最新資訊28at.com

然后我開始了解go-kit,特別是它提供的發貨示例[3],并決定在應用程序中實現相同的結構。后來,當我深入研究整潔架構(Clean Architecture)時,驚喜的發現go-kit方法是多么完美。GDr28資訊網——每日最新資訊28at.com

本文將介紹使用Go-Kit方法編寫服務是如何符合整潔架構理念的。GDr28資訊網——每日最新資訊28at.com

整潔架構(Clean Architecture)

整潔架構(Clean Architecture)是由Bob大叔(Robert Martin)創建的一種軟件架構設計。目標是分離關注點[4],允許開發人員封裝業務邏輯,并使其獨立于交付和框架機制。許多架構范例(如Onion和Hexagon架構)也有相同的目標,都是通過將軟件劃分成層來實現解耦。GDr28資訊網——每日最新資訊28at.com

GDr28資訊網——每日最新資訊28at.com

圓圈中的箭頭表示依賴規則。如果在外部循環中聲明了某些內容,則不得在內部循環代碼中引用。它既適用于實際的源代碼依賴關系,也適用于命名。內層不依賴于任何外層。GDr28資訊網——每日最新資訊28at.com

外層包含低級組件,如UI、DB、傳輸或任何第三方服務,都可以被認為是應用程序的細節或插件。其思想是,外層的變化一定不會引起內層的任何變化。GDr28資訊網——每日最新資訊28at.com

不同模塊/組件之間的依賴關系可以描述如下:GDr28資訊網——每日最新資訊28at.com

GDr28資訊網——每日最新資訊28at.com

請注意,跨越邊界的箭頭只指向一個方向,邊界后面的組件屬于外層,包括controller、presenter和database。Interactor是實現BL的地方,可以將其視為用例層。GDr28資訊網——每日最新資訊28at.com

請注意Request Model和Response Model。這些對象分別描述了內層需要和返回的數據。controller將請求(在web的情況下是HTTP請求)轉換為請求模型(Request Model),presenter將響應模型(Response Model)格式化為可以由視圖模型(View Model)呈現的數據。GDr28資訊網——每日最新資訊28at.com

還要注意接口,用于反轉控制流以與依賴規則相對應。Interactor通過Boundary接口與presenter對話,并通過Entity Gateway接口與數據層對話。GDr28資訊網——每日最新資訊28at.com

這是整潔架構的主要思想,通過依賴注入分離不同的層,使用依賴反轉反轉控制流。Interactor(BL)和實體對傳輸和數據層一無所知。這一點很重要,因為如果我們改變了外層細節,內層就不會發生級聯變化。GDr28資訊網——每日最新資訊28at.com

什么是Go-Kit?

Go kit[5]是包的集合,可以幫助我們構建健壯、可靠、可維護的微服務。GDr28資訊網——每日最新資訊28at.com

對于來自Ruby on Rails的我來說,重要的是Go-Kit不是MVC框架。相反,它將應用程序分為三層:GDr28資訊網——每日最新資訊28at.com

  • Transport(傳輸)
  • Endpoint(端點)
  • Service(服務)

1.Transport

傳輸層是唯一熟悉交付機制(HTTP、gRPC、CLI…)的組件,這一點非常強大,因為我們可以通過提供不同的傳輸層來同時支持HTTP和CLI。GDr28資訊網——每日最新資訊28at.com

稍后我們將看到傳輸層是如何對應于上圖中的controller和presenter的。GDr28資訊網——每日最新資訊28at.com

2.Endpoint

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

端點層表示應用程序中的單個RPC,將交付連接到BL。這是根據輸入和輸出實際定義用例的地方,在整潔架構術語中是Request Model和Response Model。GDr28資訊網——每日最新資訊28at.com

注意,端點是接收請求并返回響應的函數,都是interface{},是RequestModel和ResponseModel。理論上也可以用類型參數(泛型)來實現。GDr28資訊網——每日最新資訊28at.com

GDr28資訊網——每日最新資訊28at.com

3.Service

服務層(interactor)是實現BL的地方。服務層不知道端點層,服務層和端點層都不知道傳輸域(比如HTTP)。GDr28資訊網——每日最新資訊28at.com

Go-Kit提供了創建服務器(HTTP服務器/gRPC服務器等)的功能。例如HTTP:GDr28資訊網——每日最新資訊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,解碼器和編碼器在傳輸中定義,端點在運行時初始化。GDr28資訊網——每日最新資訊28at.com

簡短示例:(基于發貨示例[6])GDr28資訊網——每日最新資訊28at.com

簡易服務

我們將描述一個具有兩個API的簡單服務,用于從數據層創建和讀取文章,傳輸層是HTTP,數據層只是一個內存映射。可以在這里找到GitHub源代碼[7]。GDr28資訊網——每日最新資訊28at.com

注意文件結構:GDr28資訊網——每日最新資訊28at.com

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

我們看看如何表示整潔架構的不同層。GDr28資訊網——每日最新資訊28at.com

  • article —— 這是實體層,不包含BL、數據層或傳輸層的知識。
  • inmem —— 這是數據層。
  • transport —— 這是傳輸層。
  • endpoint+service —— 組成了邊界+交互器。

從服務開始:GDr28資訊網——每日最新資訊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,這里的服務可能是冗余的,但需要記住這只是一個簡單示例。GDr28資訊網——每日最新資訊28at.com

實體

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

實體只是一個DTO,如果有業務策略或行為,可以添加到這里。GDr28資訊網——每日最新資訊28at.com

端點

endpoint.go定義了服務接口:GDr28資訊網——每日最新資訊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)定義一個端點。例如,對于獲取文章::GDr28資訊網——每日最新資訊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方法。GDr28資訊網——每日最新資訊28at.com

傳輸

transport.go注冊HTTP路由:GDr28資訊網——每日最新資訊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函數在運行時創建端點,并提供用于反序列化請求的解碼器和用于格式化和編碼響應的編碼器。GDr28資訊網——每日最新資訊28at.com

例如:GDr28資訊網——每日最新資訊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標記?GDr28資訊網——每日最新資訊28at.com

這是個非常重要的問題。在article實體上添加JSON標記意味著article知道它是如何格式化的。雖然沒有顯式導入到HTTP,但打破了抽象,使實體包依賴于傳輸層。GDr28資訊網——每日最新資訊28at.com

例如,假設你想將對客戶端的響應從"title"更改為"header",此更改僅涉及傳輸層。但是,如果此需求導致需要更改實體,則意味著該實體依賴于傳輸層,這就破壞了簡潔架構原則。GDr28資訊網——每日最新資訊28at.com

我們看看這個簡單應用的依賴關系圖:GDr28資訊網——每日最新資訊28at.com

GDr28資訊網——每日最新資訊28at.com

哇,你一定注意到了它們的相似性!article實體沒有依賴關系(只有向內箭頭)。外層,transport和inmem,只有指向BL和實體內層的箭頭。GDr28資訊網——每日最新資訊28at.com

一切都和轉換有關

跨界就是不同層次語言之間的轉換。GDr28資訊網——每日最新資訊28at.com

BL層只使用應用語言,也就是說,只知道實體(沒有HTTP請求或SQL查詢)。為了跨越邊界,流中的某個組件必須將應用語言轉換為外層語言。GDr28資訊網——每日最新資訊28at.com

在傳輸層,有解碼器(將HTTP請求轉換為RequestModel的應用語言)和編碼器(將應用語言ResponseModel轉換為HTTP響應)。GDr28資訊網——每日最新資訊28at.com

數據層實現了repo,在我們的例子中是inmem。在另一種情況下,我們可能會讓sql包負責將應用語言轉換為SQL語言(查詢和原始結果)。GDr28資訊網——每日最新資訊28at.com

"ing"包

你可能會說傳輸和服務不應該在同一個包中,因為它們位于不同的層,這是一個正確的論點。我從go-kit的shipping例子中取了一個例子,含有這種設計,ing包包含了傳輸/端點/服務,我發現從長遠來看非常方便。話雖如此,如果我現在寫的話,可能會用不同的包。GDr28資訊網——每日最新資訊28at.com

最后關于"尖叫架構(Screaming Architecture)"的一句話

Go非常適合簡潔架構的另一個原因是包的命名及其思想。尖叫架構(Screaming Architecture) 和構建應用程序有關,以便應用程序的意圖顯而易見。在Ruby On Rails中,當查看結構時,就知道它是用Ruby On Rails框架編寫的(控制器、模型、視圖……)。在我們的應用程序中,當查看結構時,可以看出這是一個關于文章的應用程序,有發布用例,并使用inmem數據層。GDr28資訊網——每日最新資訊28at.com

總結

簡潔架構只是一種方法,并不會告訴你如何構建源代碼,其實現藝術在于了解所用語言的使用慣例和工具。希望這篇文章對你有所幫助,重要的是要意識到,那些爭論設計問題解決方案的文章并不總是對的,當然也包括這篇GDr28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-54023-0.html基于Go-Kit的Golang整潔架構實踐

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Graalvm 替代 JVM 真的可以帶來巨大的性能優勢嗎?

下一篇: 別再亂用了,Java 21 將棄用、刪除這些功能!

標簽:
  • 熱門焦點
  • 得物效率前端微應用推進過程與思考

    一、背景效率工程隨著業務的發展,組織規模的擴大,越來越多的企業開始意識到協作效率對于企業團隊的重要性,甚至是決定其在某個行業競爭中突圍的關鍵,是企業長久生存的根本。得物
  • 這款新興工具平臺,讓你的電腦效率翻倍

    隨著信息技術的發展,我們獲取信息的渠道越來越多,但是處理信息的效率卻成為一個瓶頸。于是各種工具應運而生,都在爭相解決我們的工作效率問題。今天我要給大家介紹一款效率
  • 微軟邀請 Microsoft 365 商業用戶,測試視頻編輯器 Clipchamp

    8 月 1 日消息,微軟近日宣布即將面向 Microsoft 365 商業用戶,開放 Clipchamp 應用,邀請用戶通過該應用來編輯視頻。微軟于 2021 年收購 Clipchamp,隨后開始逐步整合到 Microsof
  • 本地生活這塊肥肉,拼多多也想吃一口

    出品/壹覽商業 作者/李彥編輯/木魚拼多多也看上本地生活這塊蛋糕了。近期,拼多多在App首頁“充值中心”入口上線了本機生活界面。壹覽商業發現,該界面目前主要
  • “又被陳思誠騙了”

    作者|張思齊 出品|眾面(ID:ZhongMian_ZM)如今的國產懸疑電影,成了陳思誠的天下。最近大爆電影《消失的她》票房突破30億斷層奪魁暑期檔,陳思誠再度風頭無兩。你可以說陳思誠的
  • 東方甄選單飛:有些鳥注定是關不住的

    作者:彭寬鴻來源:華爾街科技眼‍‍‍‍‍‍‍‍‍‍東方甄選創始人俞敏洪帶隊的“7天甘肅行”直播活動已在近日順利收官。成立后一
  • iQOO Neo8系列新品發布會

    旗艦雙芯 更強更Pro
  • Android 14發布:首批適配機型公布

    5月11日消息,谷歌在今天凌晨舉行了I/O大會,本次發布會谷歌帶來了自家的AI語言模型PaLM 2、谷歌Pixel Fold折疊屏、谷歌Pixel 7a手機,同時發布了Androi
  • 回歸OPPO兩年,一加贏了銷量,輸了品牌

    成為OPPO旗下主打性能的先鋒品牌后,一加屢創佳績。今年618期間,一加手機全渠道銷量同比增長362%,憑借一加 11、一加 Ace 2、一加 Ace 2V三款爆品,一加
Top 主站蜘蛛池模板: 日喀则市| 巴青县| 余姚市| 元阳县| 桃园市| 丰都县| 平利县| 昌都县| 新民市| 乌兰县| 理塘县| 黄石市| 乌拉特中旗| 江油市| 庆安县| 朝阳区| 湟中县| 贺州市| 苏州市| 尼木县| 湘阴县| 汉沽区| 山东省| 翼城县| 高州市| 都江堰市| 晋城| 利川市| 边坝县| 子洲县| 塔城市| 英超| 兰溪市| 瑞昌市| 綦江县| 渝北区| 塔河县| 靖西县| 青龙| 平罗县| 汤原县|