You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

遵循整洁架构/DDD原则,如何在服务层正确管理事务?

在Clean Architecture+DDD下服务层管控事务的Go实现方案

针对你提出的问题,核心解决思路是用上下文传递事务+抽象事务接口,既保持仓储接口的纯净性(不依赖底层数据库),又能在服务层完全管控事务生命周期。以下是具体实现:

1. 领域层:抽象事务与仓储接口

先在领域层定义和数据库无关的事务抽象,确保仓储接口不引入任何数据库依赖:

// 领域层:事务抽象接口,仅暴露必要操作
type Transaction interface {
	Commit() error
	Rollback() error
}

// 领域层:Item仓储接口,仅依赖上下文和领域对象
type ItemRepository interface {
	Add(ctx context.Context, item *domain.Item) (uint, error)
}

2. 基础设施层:适配数据库事务

在基础设施层实现领域层的事务接口(以GORM为例),并让仓储实现从上下文读取事务:

// 基础设施层:GORM事务实现,适配领域层Transaction接口
type GormTransaction struct {
	tx *gorm.DB
}

func (gt *GormTransaction) Commit() error {
	return gt.tx.Commit().Error
}

func (gt *GormTransaction) Rollback() error {
	return gt.tx.Rollback().Error
}

// 基础设施层:Item仓储的GORM实现
type GormItemRepository struct {
	db *gorm.DB
}

func (r *GormItemRepository) Add(ctx context.Context, item *domain.Item) (uint, error) {
	// 从上下文获取事务实例,不存在则用普通连接
	tx, hasTx := ctx.Value("tx").(*gorm.DB)
	if !hasTx {
		tx = r.db
	}

	if err := tx.Create(item).Error; err != nil {
		return 0, err
	}
	return item.ID, nil
}

3. 应用服务层:管控事务生命周期

服务层负责开启事务、传递上下文、执行业务逻辑,在需要时触发回滚:

// 应用服务层:业务逻辑与事务管控
type ItemService struct {
	itemRepo  ItemRepository
	emailSvc  EmailService // 假设的邮件服务接口
	dbConn    *gorm.DB     // 依赖注入的数据库连接
}

func (s *ItemService) CreateItemWithNotification(ctx context.Context, item *domain.Item) error {
	// 开启事务
	tx := s.dbConn.Begin()
	if tx.Error != nil {
		return tx.Error
	}
	// 将事务存入上下文
	txCtx := context.WithValue(ctx, "tx", tx)

	// 执行仓储操作
	_, err := s.itemRepo.Add(txCtx, item)
	if err != nil {
		tx.Rollback()
		return err
	}

	// 执行邮件发送
	err = s.emailSvc.SendItemCreatedAlert(item)
	if err != nil {
		// 邮件失败,回滚事务
		tx.Rollback()
		return fmt.Errorf("send notification failed: %w", err)
	}

	// 提交事务
	return tx.Commit().Error
}

关键优势说明

  • 符合Clean Architecture+DDD原则:领域层仓储接口完全不依赖数据库细节,依赖方向始终是内层(领域)指向外层(基础设施)。
  • 服务层完全掌控事务:可以在任意业务节点(比如邮件发送失败)触发回滚,满足业务需求。
  • 无侵入式事务传递:通过Go原生的context.Context传递事务,不需要修改仓储接口签名。

优化建议

为避免上下文key硬编码,可以在基础设施层定义包级常量:

const contextKeyTx = "transaction"

后续使用context.WithValue(ctx, contextKeyTx, tx)和ctx.Value(contextKeyTx)来操作。

内容的提问来源于stack exchange,提问作者Christian Gama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 07:35:05