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

Go服务层事务回滚:entGO仓库用户创建与Token生成的事务处理

基于entGO实现用户创建+Token生成的事务回滚方案

针对你遇到的问题,推荐给仓库层方法增加事务参数(接收*ent.Tx),由服务层控制事务的启动、提交和回滚,这样既能保证原子性,又符合分层设计的职责划分,比事后删除用户或自定义仓库事务函数更可靠。

核心思路

你的痛点在于:原流程是先创建用户(已持久化到数据库),再生成Token,失败后只能删除用户,这会带来数据不一致风险。正确的做法是把用户创建放在未提交的事务里,只有当Token生成成功后再提交事务,失败则直接回滚,从根源避免脏数据。

具体实现步骤

1. 修改仓库层的Create方法,支持事务参数

让Create方法可以接收*ent.Tx参数,内部根据是否传入事务选择对应的客户端(事务客户端或普通客户端),这样同一个方法既支持事务场景,也支持非事务场景:

// mariaUserRepository is data/repository implementation
// of service layer UserRepository
type mariaUserRepository struct {
    Client *ent.Client
}

// NewUserRepository is a factory for initializing User Repositories
func NewUserRepository(client *ent.Client) models.UserRepository {
    return &mariaUserRepository{
        Client: client,
    }
}

// Create 支持传入事务参数,实现事务内/非事务内的用户创建
func (r *mariaUserRepository) Create(ctx context.Context, u *ent.User, tx *ent.Tx) (*ent.User, error) {
    // 确定使用的数据库客户端:有事务则用事务客户端,否则用仓库默认客户端
    client := r.Client
    if tx != nil {
        client = tx.Client()
    }

    // 检查用户是否已存在
    check, err := client.User.
        Query().
        Where(user.Email(u.Email)).Exist(ctx)
    if err != nil {
        utils.Logger.Info("Error when checking user",
            zap.String("email", u.Email),
            zap.Error(err),
        )
        return nil, handle_errors.NewInternal()
    }
    if check {
        err := handle_errors.NewConflict("email", u.Email)
        utils.Logger.Info("Error when Register User",
            zap.String("email", u.Email),
            zap.Error(err),
        )
        return nil, err
    }

    // 创建用户
    createdUser, err := client.User.
        Create().
        SetFirstName(u.FirstName).
        SetLastName(u.LastName).
        SetEmail(u.Email).
        SetGender(u.Gender).
        SetUserType(u.UserType).
        SetPasswordHash(u.PasswordHash).
        Save(ctx)
    if err != nil {
        utils.Logger.Info("Error when create user",
            zap.Any("User", u),
            zap.Error(err),
        )
        return nil, handle_errors.NewInternal()
    }
    return createdUser, nil
}

2. 服务层控制事务生命周期

在服务层的Signup方法中启动事务,先在事务内创建用户,再生成Token:

  • Token生成成功:提交事务,用户正式持久化
  • Token生成失败:回滚事务,用户不会被保存
func (s *userService) Signup(ctx context.Context, u *ent.User) (*ent.User, error) {
    // 启动ent事务
    tx, err := s.userRepo.Client.Tx(ctx)
    if err != nil {
        utils.Logger.Info("Failed to start transaction", zap.Error(err))
        return nil, handle_errors.NewInternal()
    }

    // 处理panic场景,确保事务回滚
    defer func() {
        if p := recover(); p != nil {
            if rollbackErr := tx.Rollback(); rollbackErr != nil {
                utils.Logger.Info("Failed to rollback transaction on panic", zap.Error(rollbackErr))
            }
            panic(p)
        }
    }()

    // 事务内创建用户(此时用户未真正写入数据库)
    createdUser, err := s.userRepo.Create(ctx, u, tx)
    if err != nil {
        // 创建失败,回滚事务
        if rollbackErr := tx.Rollback(); rollbackErr != nil {
            utils.Logger.Info("Failed to rollback transaction after create user error", zap.Error(rollbackErr))
        }
        return nil, err
    }

    // 生成Token
    _, err = s.tokenService.NewPairFromUser(ctx, createdUser, "")
    if err != nil {
        // Token生成失败,回滚事务,用户不会被保存
        if rollbackErr := tx.Rollback(); rollbackErr != nil {
            utils.Logger.Info("Failed to rollback transaction after token generation error", zap.Error(rollbackErr))
        }
        utils.Logger.Info("Failed to create tokens for user",
            zap.String("userID", createdUser.ID.String()),
            zap.Error(err),
        )
        return nil, err
    }

    // 所有步骤成功,提交事务
    if err := tx.Commit(); err != nil {
        utils.Logger.Info("Failed to commit transaction", zap.Error(err))
        return nil, handle_errors.NewInternal()
    }

    return createdUser, nil
}

为什么不选自定义事务函数?

如果在仓库层封装事务逻辑(比如写一个CreateWithTransaction函数),会把事务边界和数据操作绑定,导致服务层无法将Token生成这类业务逻辑纳入事务判断。仓库层只负责数据访问,事务边界应该由服务层根据业务逻辑决定,这样更灵活,也符合单一职责原则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:36:23