欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang在整潔架構基礎上實現(xiàn)事務操作

 更新時間:2024年08月08日 08:59:15   作者:白澤talk  
這篇文章在 go-kratos 官方的 layout 項目的整潔架構基礎上,實現(xiàn)優(yōu)雅的數(shù)據(jù)庫事務操作,需要的朋友可以參考下

前言

大家好,這里是白澤,這篇文章在 go-kratos 官方的 layout 項目的整潔架構基礎上,實現(xiàn)優(yōu)雅的數(shù)據(jù)庫事務操作。

本期涉及的學習資料:

在開始學習之前,先補齊一下整潔架構 & 依賴注入的前置知識。

預備知識

整潔架構

kratos 是 Go 語言的一個微服務框架,github ?? 23k,https://github.com/go-kratos/kratos

該項目提供了 CLI 工具,允許用戶通過 kratos new xxxx,新建一個 xxxx 項目,這個項目將使用 kratos-layout 倉庫的代碼結構。

倉庫地址:https://github.com/go-kratos/kratos-layout

kratos-layout 項目為用戶提供的,配合 CLI 工具生成的一個典型的 Go 項目布局看起來像這樣:

application
|____api
| |____helloworld
| | |____v1
| | |____errors
|____cmd
| |____helloworld
|____configs
|____internal
| |____conf
| |____data
| |____biz
| |____service
| |____server
|____test
|____pkg
|____go.mod
|____go.sum
|____LICENSE
|____README.md

依賴注入

?? 通過依賴注入,實現(xiàn)了資源的使用和隔離,同時避免了重復創(chuàng)建資源對象,是實現(xiàn)整潔架構的重要一環(huán)。

kratos 的官方文檔中提到,十分建議用戶嘗試使用 wire 進行依賴注入,整個 layout 項目,也是基于 wire,完成了整潔架構的搭建。

service 層,實現(xiàn) rpc 接口定義的方法,實現(xiàn)對外交互,注入了 biz。

// GreeterService is a greeter service.
type GreeterService struct {
   v1.UnimplementedGreeterServer
   uc *biz.GreeterUsecase
}
// NewGreeterService new a greeter service.
func NewGreeterService(uc *biz.GreeterUsecase) *GreeterService {
   return &GreeterService{uc: uc}
}
// SayHello implements helloworld.GreeterServer.
func (s *GreeterService) SayHello(ctx context.Context, in *v1.HelloRequest) (*v1.HelloReply, error) {
   g, err := s.uc.CreateGreeter(ctx, &biz.Greeter{Hello: in.Name})
   if err != nil {
      return nil, err
   }
   return &v1.HelloReply{Message: "Hello " + g.Hello}, nil
}

biz 層:定義 repo 接口,注入 data 層。

// GreeterRepo is a Greater repo.
type GreeterRepo interface {
   Save(context.Context, *Greeter) (*Greeter, error)
   Update(context.Context, *Greeter) (*Greeter, error)
   FindByID(context.Context, int64) (*Greeter, error)
   ListByHello(context.Context, string) ([]*Greeter, error)
   ListAll(context.Context) ([]*Greeter, error)
}
// GreeterUsecase is a Greeter usecase.
type GreeterUsecase struct {
   repo GreeterRepo
   log  *log.Helper
}
// NewGreeterUsecase new a Greeter usecase.
func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase {
	return &GreeterUsecase{repo: repo, log: log.NewHelper(logger)}
}
// CreateGreeter creates a Greeter, and returns the new Greeter.
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
	uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
	return uc.repo.Save(ctx, g)
}

data 作為數(shù)據(jù)訪問的實現(xiàn)層,實現(xiàn)了上游接口,注入了數(shù)據(jù)庫實例資源。

type greeterRepo struct {
	data *Data
	log  *log.Helper
}
// NewGreeterRepo .
func NewGreeterRepo(data *Data, logger log.Logger) biz.GreeterRepo {
	return &greeterRepo{
		data: data,
		log:  log.NewHelper(logger),
	}
}
func (r *greeterRepo) Save(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
	return g, nil
}
func (r *greeterRepo) Update(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
	return g, nil
}
func (r *greeterRepo) FindByID(context.Context, int64) (*biz.Greeter, error) {
	return nil, nil
}
func (r *greeterRepo) ListByHello(context.Context, string) ([]*biz.Greeter, error) {
	return nil, nil
}
func (r *greeterRepo) ListAll(context.Context) ([]*biz.Greeter, error) {
	return nil, nil
}

db:注入 data,作為被操作的對象。

type Data struct {
	// TODO wrapped database client
}
// NewData .
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
	cleanup := func() {
		log.NewHelper(logger).Info("closing the data resources")
	}
	return &Data{}, cleanup, nil
}

Golang 優(yōu)雅事務

準備

?? 項目獲?。簭娏医ㄗh克隆倉庫后實機操作。

git clone git@github.com:BaiZe1998/go-learning.git
cd kit/transcation/helloworld

這個目錄基于 go-kratos CLI 工具使用 kratos new helloworld 生成,并在此基礎上修改,實現(xiàn)了事務支持。

運行 demo 需要準備:

  • 本地數(shù)據(jù)庫 dev:root:root@tcp(127.0.0.1:3306)/dev?parseTime=True&loc=Local
  • 建立表:
CREATE TABLE IF NOT EXISTS greater (
    hello VARCHAR(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

ps:Makefile 中提供了使用 goose 進行數(shù)據(jù)庫變更管理的能力(goose 也是一個開源的高 ?? 項目,推薦學習)

up:
	goose mysql "root:root@tcp(localhost:3306)/dev?parseTime=true" up
down:
	goose mysql "root:root@tcp(localhost:3306)/dev?parseTime=true" down
create:
	goose mysql "root:root@tcp(localhost:3306)/dev?parseTime=true" create ${name} sql
  • 啟動服務:go run ./cmd/helloworld/,通過 config.yaml 配置了 HTTP 服務監(jiān)聽 localhost:8000,GRPC 則是 localhost:9000。

  • 發(fā)起一個 get 請求

核心邏輯

helloworld 項目本質是一個打招呼服務,由于 kit/transcation/helloworld 已經(jīng)是魔改后的版本,為了與默認項目做對比,你可以自行生成一個 helloworld 項目,在同級目錄下,對照學習。

在 internal/biz/greeter.go 文件中,是我更改的內容,為了測試事務,我在 biz 層的 CreateGreeter 方法中,調用了 repo 層的 Save 和 Update 兩個方法,且這兩個方法都會成功,但是 Update 方法人為拋出一個異常。

// CreateGreeter creates a Greeter, and returns the new Greeter.
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
   uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
   var (
      greater *Greeter
      err     error
   )
   //err = uc.db.ExecTx(ctx, func(ctx context.Context) error {
   // // 更新所有 hello 為 hello + "updated",且插入新的 hello
   // greater, err = uc.repo.Save(ctx, g)
   // _, err = uc.repo.Update(ctx, g)
   // return err
   //})
   greater, err = uc.repo.Save(ctx, g)
   _, err = uc.repo.Update(ctx, g)
   if err != nil {
      return nil, err
   }
   return greater, nil
}
// Update 人為拋出異常
func (r *greeterRepo) Update(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
	result := r.data.db.DB(ctx).Model(&biz.Greeter{}).Where("hello = ?", g.Hello).Update("hello", g.Hello+"updated")
	if result.RowsAffected == 0 {
		return nil, fmt.Errorf("greeter %s not found", g.Hello)
	}
	return nil, fmt.Errorf("custom error")
	//return g, nil
}

repo 層開啟事務

如果忽略上文注釋中的內容,因為兩個 repo 的數(shù)據(jù)庫操作都是獨立的。

func (r *greeterRepo) Save(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
   result := r.data.db.DB(ctx).Create(g)
   return g, result.Error
}
func (r *greeterRepo) Update(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
   result := r.data.db.DB(ctx).Model(&biz.Greeter{}).Where("hello = ?", g.Hello).Update("hello", g.Hello+"updated")
   if result.RowsAffected == 0 {
      return nil, fmt.Errorf("greeter %s not found", g.Hello)
   }
   return nil, fmt.Errorf("custom error")
   //return g, nil
}

即使最后拋出 Update 的異常,但是 save 和 update 都已經(jīng)成功了,且彼此不強關聯(lián),數(shù)據(jù)庫中會多增加一條數(shù)據(jù)。

image-20240807005400189

biz 層開啟事務

因此為了 repo 層的兩個方法能夠共用一個事務,應該在 biz 層就使用 db 開啟事務,且將這個事務的會話傳遞給 repo 層的方法。

?? 如何傳遞:使用 context 便成了順理成章的方案。

接下來將 internal/biz/greeter.go 文件中注釋的部分釋放,且注釋掉分開使用事務的兩行,此時重新運行項目請求接口,則由于 Update 方法拋出 err,導致事務回滾,未出現(xiàn)新增的 xiaomingupdated 記錄。

// CreateGreeter creates a Greeter, and returns the new Greeter.
func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
   uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
   var (
      greater *Greeter
      err     error
   )
   err = uc.db.ExecTx(ctx, func(ctx context.Context) error {
      // 更新所有 hello 為 hello + "updated",且插入新的 hello
      greater, err = uc.repo.Save(ctx, g)
      _, err = uc.repo.Update(ctx, g)
      return err
   })
   //greater, err = uc.repo.Save(ctx, g)
   //_, err = uc.repo.Update(ctx, g)
   if err != nil {
      return nil, err
   }
   return greater, nil
}

核心實現(xiàn)

由于 biz 層的 Usecase 實例持有 *DBClient,repo 層也持有 *DBClient,且二者在依賴注入的時候,代表同一個數(shù)據(jù)庫連接池實例。

在 pkg/db/db.go 中,為 *DBClient 提供了如下兩個方法: ExecTx() & DB()。

在 biz 層,通過優(yōu)先執(zhí)行 ExecTx() 方法,創(chuàng)建事務,以及將待執(zhí)行的兩個 repo 方法封裝在 fn 參數(shù)中,傳遞給 gorm 實例的 Transaction() 方法待執(zhí)行。

同時在 Transcation 內部,觸發(fā) fn() 函數(shù),也就是聚合的兩個 repo 操作,需要注意的是,此時將攜帶 contextTxKey 事務 tx 的 ctx 作為參數(shù)傳遞給了 fn 函數(shù),因此下游的兩個 repo 可以獲取到 biz 層的事務會話。

type contextTxKey struct{}
// ExecTx gorm Transaction
func (c *DBClient) ExecTx(ctx context.Context, fn func(ctx context.Context) error) error {
   return c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
      ctx = context.WithValue(ctx, contextTxKey{}, tx)
      return fn(ctx)
   })
}
func (c *DBClient) DB(ctx context.Context) *gorm.DB {
   tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
   if ok {
      return tx
   }
   return c.db
}

在 repo 層執(zhí)行數(shù)據(jù)庫操作的時候,嘗試通過 DB() 方法,從 ctx 中獲取到上游傳遞下來的事務會話,如果有則使用,如果沒有,則使用 repo 層自己持有的 *DBClient,進行數(shù)據(jù)訪問操作。

func (r *greeterRepo) Save(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
	result := r.data.db.DB(ctx).Create(g)
	return g, result.Error
}
func (r *greeterRepo) Update(ctx context.Context, g *biz.Greeter) (*biz.Greeter, error) {
	result := r.data.db.DB(ctx).Model(&biz.Greeter{}).Where("hello = ?", g.Hello).Update("hello", g.Hello+"updated")
	if result.RowsAffected == 0 {
		return nil, fmt.Errorf("greeter %s not found", g.Hello)
	}
	return nil, fmt.Errorf("custom error")
	//return g, nil
}

參考文獻

到此這篇關于Golang在整潔架構基礎上實現(xiàn)事務的文章就介紹到這了,更多相關Golang實現(xiàn)事務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • gin?session中間件使用及源碼流程分析

    gin?session中間件使用及源碼流程分析

    這篇文章主要為大家介紹了gin?session中間件使用及源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Go語言中讀取命令參數(shù)的幾種方法總結

    Go語言中讀取命令參數(shù)的幾種方法總結

    部署golang項目時難免要通過命令行來設置一些參數(shù),那么在golang中如何操作命令行參數(shù)呢?那么下面這篇文章就來給大家介紹了關于Go語言中讀取命令參數(shù)的幾種方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起看看吧。
    2017-11-11
  • Go語言連接MySQL數(shù)據(jù)庫執(zhí)行基本的增刪改查

    Go語言連接MySQL數(shù)據(jù)庫執(zhí)行基本的增刪改查

    在后端開發(fā)中,MySQL?是最常用的關系型數(shù)據(jù)庫之一,本文主要為大家詳細介紹了如何使用?Go?連接?MySQL?數(shù)據(jù)庫并執(zhí)行基本的增刪改查吧
    2025-08-08
  • Golang如何將上傳的文件壓縮成zip(小案例)

    Golang如何將上傳的文件壓縮成zip(小案例)

    這篇文章主要介紹了Golang如何將上傳的文件壓縮成zip(小案例),這是一個簡單的golang壓縮文件小案例,可做很多的拓展,這里使用的庫是archive/zip,在gopkg里面搜zip就行,需要的朋友可以參考下
    2024-01-01
  • Go字典使用詳解

    Go字典使用詳解

    今天和大家一起學習Go語言的字典。Go語言的字典又稱為map,一種使用廣泛的數(shù)據(jù)結構。它是擁有key/value對元素的「無序集合」,而且在集合中key必須是唯一的
    2022-11-11
  • 一文搞懂Go語言中defer關鍵字的使用

    一文搞懂Go語言中defer關鍵字的使用

    defer是golang中用的比較多的一個關鍵字,也是go面試題里經(jīng)常出現(xiàn)的問題。今天就來整理一下關于defer的學習使用,希望對需要的朋友有所幫助
    2022-09-09
  • Go語言實戰(zhàn)之實現(xiàn)一個簡單分布式系統(tǒng)

    Go語言實戰(zhàn)之實現(xiàn)一個簡單分布式系統(tǒng)

    如今很多云原生系統(tǒng)、分布式系統(tǒng),例如?Kubernetes,都是用?Go?語言寫的,這是因為?Go?語言天然支持異步編程。本篇文章將介紹如何用?Go?語言編寫一個簡單的分布式系統(tǒng),需要的小伙伴開業(yè)跟隨小編一起學習一下
    2022-10-10
  • Go構建高性能的事件管理器實例詳解

    Go構建高性能的事件管理器實例詳解

    這篇文章主要為大家介紹了Go構建高性能的事件管理器實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Go語言實現(xiàn)定時器的原理及使用詳解

    Go語言實現(xiàn)定時器的原理及使用詳解

    這篇文章主要為大家詳細介紹了Go語言實現(xiàn)定時器的兩種方法:一次性定時器(Timer)和周期性定時器(Ticker),感興趣的小伙伴可以跟隨小編一起學習一下
    2022-12-12
  • go mock server的簡易實現(xiàn)示例

    go mock server的簡易實現(xiàn)示例

    這篇文章主要為大家介紹了go mock server的簡易實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07

最新評論