成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go工程化如何在整潔架構中使用事務?

開發 后端
事務的能力是在 repo 上提供的,所以我們需要在 repo 層提供一個事務接口,然后在 usecase 中進行調用,保證是事務執行的就行。

[[441824]]

回顧先簡單回顧一下 《Go工程化(九) 項目重構實踐》 如果還沒看過之前這篇文章可以先看一下:

在我們之前的項目目錄分層中,我們主要分為了五個塊:

  • cmd/appname 是我們服務的入口,只負責啟動和依賴注入(使用 Wire)
  • domain 或者 model 是我們的實體定義 + 接口定義
  • server 負責實現我們在 proto 中定義的接口,在這一層中我們只做數據轉換,不寫業務邏輯
  • usecase 負責實現我們的業務邏輯
  • repo 負責數據操作, 僅做數據操作,不實現業務邏輯

在之前的文章中僅僅提到了一個非常簡單的示例,但是我們實際業務流程往往沒有那么簡單,就一個非常常見的例子,我們現在需要創建一篇文章,文章上需要關聯分類或者是標簽信息,這里至少就分兩步:

  • 創建文章
  • 關聯文章和標簽

這兩個創建操作需要保證一致性,我們需要在數據庫中使用事務,這時候我們的事務在哪里承載呢?

在 repo 層承載事務

其中最簡單也最直接的辦法就是在 repo 的 CreateArticle 方法中我們就使用事務去同時創建文章以及標簽之間的關聯關系。

  • 我們不是所有的業務場景都需要關聯創建,有的場景下我們只需要一個單純的方法又怎么辦呢?
  • 這么寫還有一個問題,我們把業務邏輯下沉到了 repo 中,后面我們還有其它關聯也這么搞么?

針對第一個問題,最簡單的辦法就是我們提供一個 CreateArticleWithTags 方法表示同時創建這兩者,如果我們需要一個獨立的 CreateArticle 再寫一個就好了。

但是隨著需求越來越多,可能后面還有需要和角色關聯的,和商品關聯的等等。

難道我們就一種邏輯寫一個方法么。想想就可怕。

還是在參數中加上很多可選的 options,然后在一個方法中不斷判斷。那我們還拿 usecase 做什么直接寫一起不更好么?

在 usecase 層承載事務

ok,所以直接在 repo 層里面來實現看上去好像行不通,那我們就把視線往上移動,我們在 usecase 來解決這個問題。

事務的能力是在 repo 上提供的,所以我們需要在 repo 層提供一個事務接口,然后在 usecase 中進行調用,保證是事務執行的就行。

使用 repo 層提供的事務接口

  1. // domain/article.go 
  2. // ArticleRepoTxFunc 事務方法 
  3. type ArticleRepoTxFunc = func(ctx context.Context, repo IArticleRepo) error 
  4. // IArticleRepo IArticleRepo 
  5. type IArticleRepo interface { 
  6.  Tx(ctx context.Context, f ArticleRepoTxFunc) error 
  7.  GetArticle(ctx context.Context, id int) (*Article, error) 
  8.  CreateArticle(ctx context.Context, article *Article) error 

在 repo 中,我們可以像上面這樣定義,提供一個 Tx 方法,這個方法接受一個 ArticleRepoTxFunc 作為參數,這個函數中的 repo 是開啟了事務的 repo,通過這個 repo 調用的所有方法都是在事務中執行的。

  1. // repo/article.go 
  2. func (r *article) Tx(ctx context.Context, f domain.ArticleRepoTxFunc) error { 
  3.  // 注意,這里的 r.db 是 *gorm.DB 
  4.   // 在 gorm 中提供了 Transaction 的工具方法用于執行事務,這里我們就不自己寫了 
  5.  return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { 
  6.   // 我們使用事務的 tx 重新初始化一個 repo 
  7.     // 這個 repo 后續的執行的數據庫相關的操作就都是事務的了 
  8.   repo := NewArticleRepo(tx) 
  9.   return f(ctx, repo) 
  10.  }) 

然后我們在 usecase 調用的時候就可以這樣。

  1. // usecase/article.go 
  2. func (u *article) CreateArticle(ctx context.Context, article *domain.Article, tagIDs []uint) error { 
  3.  return u.repo.Tx(ctx, func(ctx context.Context, repo domain.IArticleRepo) error { 
  4.   err := repo.CreateArticle(ctx, article) 
  5.   if err != nil { 
  6.    return err 
  7.   } 
  8.   var ats []*domain.ArticleTag 
  9.   for _, tid := range tagIDs { 
  10.    ats = append(ats, &domain.ArticleTag{ 
  11.     ArticleID: article.ID, 
  12.     TagID:     tid, 
  13.    }) 
  14.   } 
  15.   return repo.CreateArticleTags(ctx, ats) 
  16.  }) 

這樣寫起來就整潔很多了,業務邏輯和我們最初的設計一樣,在 usecase 中實現了,repo 中我們也保持了簡單的原則。

這樣是不是就萬事大吉了呢?如果萬事大吉了這篇文章到這兒也就應該結束了,但是還沒有,說明我在實踐的過程中還碰到了問題。

問題很簡單,就是我們在 usecase 中不僅僅需要復用 repo 中的代碼,還有可能需要復用 usecase 中的代碼,不然我們就可能在 usecase 中出現很多相同的邏輯代碼片段,代碼的重復率就很高。

我們來看下面一個例子會不會發現有點什么不對。

  1. // usecase/article.go 
  2. func (u *article) A(ctx contect, args args) error { 
  3.  err := u.CreateArticle(ctx, args.Article) // 包含事務 
  4.   if err != nil { 
  5.     return err 
  6.   } 
  7.   return u.UpdateXXX(ctx, args.XXX) // 這個方法中也使用了事務 

這個方法內其實是開啟了兩個事務,這兩個事務之間互不相關,不符合我們需求。

在 usecase 層提供事務方法

  1. // usecase/article.go 
  2. type handler func(ctx context.Context, usecase domain.IArticleUsecase) error 
  3. func (u *article) tx(ctx context.Context, f handler) error { 
  4.  return u.repo.Tx(ctx, func(ctx context.Context, repo domain.IArticleRepo) error { 
  5.   usecase := NewArticleUsecase(repo) 
  6.   return f(ctx, usecase) 
  7.  }) 

我們在 usecase 中也創建了一個 tx 方法,和 repo 類似,在調用 tx 之后,handler 中的方法的需要都是用新的參數 usecase 這個新的 usecase 可以保證里面的 repo 調用都是事務的。

所以我們之前的 A 函數可以修改為這樣:

  1. // usecase/article.go 
  2. func (u *article) A(ctx contect, args args) error { 
  3.  return u.tx(ctx, func(ctx context.Context, usecase domain.IArticleUsecase) error { 
  4.   err := usecase.CreateArticle(ctx, args.Article) // 包含事務 
  5.    if err != nil { 
  6.      return err 
  7.    } 
  8.    return usecase.UpdateXXX(ctx, args.XXX) // 這個方法中也使用了事務 
  9.  }) 

這樣就沒有問題了么?我們 UpdateXXX 方法中也調用 u.tx 方法,這樣就會導致反復開啟事務,雖然在 gorm 的 Transaction 方法是支持嵌套事務的,但是我們還是不要濫用這個特性。

解決辦法很簡單,我們只需要在執行的時候判斷下就行了。

  1. // usecase/article.go 
  2. type article struct { 
  3.  repo domain.IArticleRepo 
  4.   isTx bool // 用于標識是否開啟了事務 

然后我們在 tx 方法內:

  1. func (u *article) tx(ctx context.Context, f handler) error { 
  2.   // 如果已經開啟過事務了我們就直接復用就行了 
  3.  if u.isTx { 
  4.   return f(ctx, u) 
  5.  } 
  6.  return u.repo.Tx(ctx, func(ctx context.Context, repo domain.IArticleRepo) error { 
  7.   usecase := &article{ 
  8.    repo: repo, 
  9.    isTx: true
  10.   } 
  11.   return f(ctx, usecase) 
  12.  }) 

總結

文章到這里就到尾聲了,同樣的問題,我們現在這么寫就可以了么?

對于我當前所遇到的一些需求來說已經可以解決了,當然這個方案并不完美,比如說我們涉及到多個 repo 的時候,當前的方法就沒法直接用了,還得進行一些改造,雖然我們要有遠見但是也不要想的太多,進化是優于完美的。

 

責任編輯:姜華 來源: mohuishou
相關推薦

2021-03-19 07:23:23

Go架構Go工程化

2023-06-28 08:25:14

事務SQL語句

2021-12-27 08:27:18

RepoGo 代碼

2022-04-18 09:41:14

Go架構設計

2023-09-15 10:33:45

前端工程化commit

2016-02-22 15:02:57

GoRedis連接池

2019-10-11 10:44:30

Go語言數據庫軟件

2022-12-01 07:46:01

工程化工具

2022-05-17 08:25:10

TypeScript接口前端

2022-06-23 08:00:53

PythonDateTime模塊

2021-06-09 09:36:18

DjangoElasticSearLinux

2021-03-09 07:27:40

Kafka開源分布式

2015-08-27 09:46:09

swiftAFNetworkin

2024-01-18 08:37:33

socketasyncio線程

2011-08-10 09:31:41

Hibernateunion

2019-09-16 19:00:48

Linux變量

2014-07-02 09:47:06

SwiftCocoaPods

2020-11-30 11:55:07

Docker命令Linux

2024-09-06 11:34:15

RustAI語言

2020-04-09 10:18:51

Bash循環Linux
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲高清在线观看 | 精品欧美一区二区在线观看欧美熟 | 国产精品视频网址 | 一级全黄少妇性色生活免费看 | 自拍视频一区二区三区 | 亚洲免费观看视频网站 | 91免费在线看 | 久草视频观看 | 爱综合 | 国产日韩欧美中文字幕 | 国产日韩精品视频 | 日韩欧美一区二区三区免费看 | 伊人精品在线 | 国产精品久久久久久婷婷天堂 | 日韩一级免费电影 | 久久精品亚洲精品国产欧美kt∨ | 国产精品一区一区 | 美女视频网站久久 | 狠狠干五月天 | 亚洲午夜久久久 | 国产精品夜夜夜一区二区三区尤 | 久久色视频 | 91在线网 | 欧洲一区二区在线 | 日韩中文字幕在线观看 | 欧美成人免费 | 一区二区三区视频在线 | 久草热线| 日韩精品一区二区三区 | 国产成人免费视频网站视频社区 | 日韩不卡一二区 | 黄网站免费入口 | 中文字幕亚洲视频 | 国产精品美女在线观看 | 欧美日韩一区二区三区四区 | 91精品亚洲| 在线精品一区二区三区 | 久国久产久精永久网页 | 中文字幕亚洲一区二区三区 | a国产一区二区免费入口 | 国产三级网站 |