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

Go工程化如何在整潔架構(gòu)中使用事務(wù)?

開發(fā) 后端
事務(wù)的能力是在 repo 上提供的,所以我們需要在 repo 層提供一個(gè)事務(wù)接口,然后在 usecase 中進(jìn)行調(diào)用,保證是事務(wù)執(zhí)行的就行。

[[441824]]

回顧先簡(jiǎn)單回顧一下 《Go工程化(九) 項(xiàng)目重構(gòu)實(shí)踐》 如果還沒(méi)看過(guò)之前這篇文章可以先看一下:

在我們之前的項(xiàng)目目錄分層中,我們主要分為了五個(gè)塊:

  • cmd/appname 是我們服務(wù)的入口,只負(fù)責(zé)啟動(dòng)和依賴注入(使用 Wire)
  • domain 或者 model 是我們的實(shí)體定義 + 接口定義
  • server 負(fù)責(zé)實(shí)現(xiàn)我們?cè)?proto 中定義的接口,在這一層中我們只做數(shù)據(jù)轉(zhuǎn)換,不寫業(yè)務(wù)邏輯
  • usecase 負(fù)責(zé)實(shí)現(xiàn)我們的業(yè)務(wù)邏輯
  • repo 負(fù)責(zé)數(shù)據(jù)操作, 僅做數(shù)據(jù)操作,不實(shí)現(xiàn)業(yè)務(wù)邏輯

在之前的文章中僅僅提到了一個(gè)非常簡(jiǎn)單的示例,但是我們實(shí)際業(yè)務(wù)流程往往沒(méi)有那么簡(jiǎn)單,就一個(gè)非常常見的例子,我們現(xiàn)在需要?jiǎng)?chuàng)建一篇文章,文章上需要關(guān)聯(lián)分類或者是標(biāo)簽信息,這里至少就分兩步:

  • 創(chuàng)建文章
  • 關(guān)聯(lián)文章和標(biāo)簽

這兩個(gè)創(chuàng)建操作需要保證一致性,我們需要在數(shù)據(jù)庫(kù)中使用事務(wù),這時(shí)候我們的事務(wù)在哪里承載呢?

在 repo 層承載事務(wù)

其中最簡(jiǎn)單也最直接的辦法就是在 repo 的 CreateArticle 方法中我們就使用事務(wù)去同時(shí)創(chuàng)建文章以及標(biāo)簽之間的關(guān)聯(lián)關(guān)系。

  • 我們不是所有的業(yè)務(wù)場(chǎng)景都需要關(guān)聯(lián)創(chuàng)建,有的場(chǎng)景下我們只需要一個(gè)單純的方法又怎么辦呢?
  • 這么寫還有一個(gè)問(wèn)題,我們把業(yè)務(wù)邏輯下沉到了 repo 中,后面我們還有其它關(guān)聯(lián)也這么搞么?

針對(duì)第一個(gè)問(wèn)題,最簡(jiǎn)單的辦法就是我們提供一個(gè) CreateArticleWithTags 方法表示同時(shí)創(chuàng)建這兩者,如果我們需要一個(gè)獨(dú)立的 CreateArticle 再寫一個(gè)就好了。

但是隨著需求越來(lái)越多,可能后面還有需要和角色關(guān)聯(lián)的,和商品關(guān)聯(lián)的等等。

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

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

在 usecase 層承載事務(wù)

ok,所以直接在 repo 層里面來(lái)實(shí)現(xiàn)看上去好像行不通,那我們就把視線往上移動(dòng),我們?cè)?usecase 來(lái)解決這個(gè)問(wèn)題。

事務(wù)的能力是在 repo 上提供的,所以我們需要在 repo 層提供一個(gè)事務(wù)接口,然后在 usecase 中進(jìn)行調(diào)用,保證是事務(wù)執(zhí)行的就行。

使用 repo 層提供的事務(wù)接口

  1. // domain/article.go 
  2. // ArticleRepoTxFunc 事務(wù)方法 
  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 中,我們可以像上面這樣定義,提供一個(gè) Tx 方法,這個(gè)方法接受一個(gè) ArticleRepoTxFunc 作為參數(shù),這個(gè)函數(shù)中的 repo 是開啟了事務(wù)的 repo,通過(guò)這個(gè) repo 調(diào)用的所有方法都是在事務(wù)中執(zhí)行的。

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

然后我們?cè)?usecase 調(diào)用的時(shí)候就可以這樣。

  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.  }) 

這樣寫起來(lái)就整潔很多了,業(yè)務(wù)邏輯和我們最初的設(shè)計(jì)一樣,在 usecase 中實(shí)現(xiàn)了,repo 中我們也保持了簡(jiǎn)單的原則。

這樣是不是就萬(wàn)事大吉了呢?如果萬(wàn)事大吉了這篇文章到這兒也就應(yīng)該結(jié)束了,但是還沒(méi)有,說(shuō)明我在實(shí)踐的過(guò)程中還碰到了問(wèn)題。

問(wèn)題很簡(jiǎn)單,就是我們?cè)?usecase 中不僅僅需要復(fù)用 repo 中的代碼,還有可能需要復(fù)用 usecase 中的代碼,不然我們就可能在 usecase 中出現(xiàn)很多相同的邏輯代碼片段,代碼的重復(fù)率就很高。

我們來(lái)看下面一個(gè)例子會(huì)不會(huì)發(fā)現(xiàn)有點(diǎn)什么不對(duì)。

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

這個(gè)方法內(nèi)其實(shí)是開啟了兩個(gè)事務(wù),這兩個(gè)事務(wù)之間互不相關(guān),不符合我們需求。

在 usecase 層提供事務(wù)方法

  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.  }) 

我們?cè)?usecase 中也創(chuàng)建了一個(gè) tx 方法,和 repo 類似,在調(diào)用 tx 之后,handler 中的方法的需要都是用新的參數(shù) usecase 這個(gè)新的 usecase 可以保證里面的 repo 調(diào)用都是事務(wù)的。

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

  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) // 包含事務(wù) 
  5.    if err != nil { 
  6.      return err 
  7.    } 
  8.    return usecase.UpdateXXX(ctx, args.XXX) // 這個(gè)方法中也使用了事務(wù) 
  9.  }) 

這樣就沒(méi)有問(wèn)題了么?我們 UpdateXXX 方法中也調(diào)用 u.tx 方法,這樣就會(huì)導(dǎo)致反復(fù)開啟事務(wù),雖然在 gorm 的 Transaction 方法是支持嵌套事務(wù)的,但是我們還是不要濫用這個(gè)特性。

解決辦法很簡(jiǎn)單,我們只需要在執(zhí)行的時(shí)候判斷下就行了。

  1. // usecase/article.go 
  2. type article struct { 
  3.  repo domain.IArticleRepo 
  4.   isTx bool // 用于標(biāo)識(shí)是否開啟了事務(wù) 

然后我們?cè)?tx 方法內(nèi):

  1. func (u *article) tx(ctx context.Context, f handler) error { 
  2.   // 如果已經(jīng)開啟過(guò)事務(wù)了我們就直接復(fù)用就行了 
  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.  }) 

總結(jié)

文章到這里就到尾聲了,同樣的問(wèn)題,我們現(xiàn)在這么寫就可以了么?

對(duì)于我當(dāng)前所遇到的一些需求來(lái)說(shuō)已經(jīng)可以解決了,當(dāng)然這個(gè)方案并不完美,比如說(shuō)我們涉及到多個(gè) repo 的時(shí)候,當(dāng)前的方法就沒(méi)法直接用了,還得進(jìn)行一些改造,雖然我們要有遠(yuǎn)見但是也不要想的太多,進(jìn)化是優(yōu)于完美的。

 

責(zé)任編輯:姜華 來(lái)源: mohuishou
相關(guān)推薦

2021-03-19 07:23:23

Go架構(gòu)Go工程化

2023-06-28 08:25:14

事務(wù)SQL語(yǔ)句

2021-12-27 08:27:18

RepoGo 代碼

2022-04-18 09:41:14

Go架構(gòu)設(shè)計(jì)

2023-09-15 10:33:45

前端工程化commit

2016-02-22 15:02:57

GoRedis連接池

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變量

2020-11-30 11:55:07

Docker命令Linux

2014-07-02 09:47:06

SwiftCocoaPods

2020-04-09 10:18:51

Bash循環(huán)Linux

2024-09-06 11:34:15

RustAI語(yǔ)言

2019-10-11 10:44:30

Go語(yǔ)言數(shù)據(jù)庫(kù)軟件

2023-04-07 11:05:53

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 99精品国产一区二区三区 | 国产精品久久久久久久久图文区 | 成人h电影在线观看 | 精品一区二区三区av | 黄视频在线网站 | 午夜视频网站 | 日韩中文字幕视频在线观看 | 欧美中文一区 | 在线91 | 一区二区三区亚洲 | 女女百合av大片一区二区三区九县 | 91在线影院 | 国产精品久久久久久久久免费桃花 | 中文字幕 视频一区 | 国产精品久久久久久久久久久久 | 久久国产精品亚洲 | 三区四区在线观看 | 亚洲激情在线观看 | 国产区视频在线观看 | 国产高清免费 | 国产免费又黄又爽又刺激蜜月al | 中文字幕视频在线 | 欧美激情一区 | 日本在线小视频 | 久久中文字幕一区 | 精品一区二区av | 北条麻妃99精品青青久久 | 国产99久久久国产精品 | 综合久久国产 | 精品久久久久久 | 黑人成人网 | 久久电影一区 | 久久91av| 日本特黄a级高清免费大片 成年人黄色小视频 | 狠狠ri| 91免费视频观看 | 在线免费小视频 | 欧美伊人 | 黄色国产视频 | 91久久久精品国产一区二区蜜臀 | 日韩α片 |