Coze源码分析-资源库-删除数据库-后端源码-领域服务/数据访问层
4. 领域服务层
数据库领域服务层架构
数据库领域服务层是Coze Studio中处理数据库业务逻辑的核心层,负责数据库资源的删除、管理和业务规则实现。该层采用领域驱动设计(DDD)模式,将业务逻辑与数据访问分离,确保代码的可维护性和可扩展性。
数据库领域服务接口定义
数据库领域服务接口定义了数据库管理的核心业务能力,包括数据库资源的完整生命周期管理。
type DatabaseService interface {// 删除数据库DeleteDatabase(ctx context.Context, req *DeleteDatabaseRequest) error// 其他数据库管理方法CreateDatabase(ctx context.Context, req *CreateDatabaseRequest) (*CreateDatabaseResponse, error)GetDatabase(ctx context.Context, req *GetDatabaseRequest) (*GetDatabaseResponse, error)UpdateDatabase(ctx context.Context, req *UpdateDatabaseRequest) error
}
核心接口功能:
- 数据库资源管理:创建、获取、更新、删除用户自定义的数据库资源
- 删除操作核心:DeleteDatabase方法是删除数据库的核心业务接口
- 事务管理:确保数据库删除操作的原子性和一致性
- 业务规则封装:封装数据库相关的业务逻辑和验证规则
- 数据完整性保障:确保数据库及其关联数据的完整删除
数据库领域服务实现
文件位置:backend/domain/memory/database/service/database_impl.go
数据库服务实现类包含了所有数据库相关业务逻辑的具体实现,负责处理数据库删除的核心业务逻辑,包括在线库和草稿库的同时删除。
func (d databaseService) DeleteDatabase(ctx context.Context, req *DeleteDatabaseRequest) error {onlineInfo, err := d.onlineDAO.Get(ctx, req.ID)if err != nil {return fmt.Errorf("get online database info failed: %v", err)}draftInfo, err := d.draftDAO.Get(ctx, onlineInfo.GetDraftID())if err != nil {return fmt.Errorf("get draft database info failed: %v", err)}tx := query.Use(d.db).Begin()if tx.Error != nil {return fmt.Errorf("start transaction failed, %v", tx.Error)}defer func() {if r := recover(); r != nil {e := tx.Rollback()if e != nil {logs.CtxErrorf(ctx, "rollback failed, err=%v", e)}err = fmt.Errorf("catch panic: %v\nstack=%s", r, string(debug.Stack()))return}if err != nil {e := tx.Rollback()if e != nil {logs.CtxErrorf(ctx, "rollback failed, err=%v", e)}}}()err = d.draftDAO.DeleteWithTX(ctx, tx, draftInfo.ID)if err != nil {return fmt.Errorf("delete draft database info failed: %v", err)}err = d.onlineDAO.DeleteWithTX(ctx, tx, onlineInfo.ID)if err != nil {return fmt.Errorf("delete online database info failed: %v", err)}err = tx.Commit()if err != nil {return fmt.Errorf("commit transaction failed: %v", err)}// delete draft physical tableif draftInfo.ActualTableName != "" {_, err = d.rdb.DropTable(ctx, &rdb.DropTableRequest{TableName: draftInfo.ActualTableName,})// 错误处理...}// delete online physical tableif onlineInfo.ActualTableName != "" {_, err = d.rdb.DropTable(ctx, &rdb.DropTableRequest{TableName: onlineInfo.ActualTableName,})// 错误处理...}return nil
}
删除操作实现特点:
- 事务保障:使用数据库事务确保删除操作的原子性和一致性
- 完整删除:同时删除在线库和草稿库的元数据和物理表
- 异常处理:完善的panic恢复和事务回滚机制
- 资源清理:删除数据库相关的物理表资源
- 错误包装:详细的错误信息,便于问题定位和调试
DeleteDatabase方法详解:
- 参数:接收DeleteDatabaseRequest结构体,包含数据库ID
- 处理流程:
- 获取在线数据库信息
- 获取对应草稿数据库信息
- 开启事务进行原子操作
- 删除草稿数据库信息
- 删除在线数据库信息
- 提交事务
- 删除物理表资源
- 错误处理:每个步骤都有详细的错误捕获和包装
数据库实体定义
数据库实体定义了数据库资源的核心数据结构,包含了数据库的所有关键属性。
type Database struct {ID int64 // 数据库唯一标识AppID int64 // 所属应用IDSpaceID int64 // 所属空间ID,支持多租户隔离CreatorID int64 // 创建者IDTableName string // 表名FieldList []*FieldItem // 字段列表ActualTableName string // 实际物理表名DraftID *int64 // 草稿IDRwMode BotTableRWMode // 读写模式Status BotTableStatus // 状态CreatedAt int64 // 创建时间戳UpdatedAt int64 // 更新时间戳
}
实体设计特点:
- 基础信息:包含ID、应用ID、空间ID等基本属性
- 表结构定义:通过FieldList定义数据库表结构
- 多租户支持:SpaceID字段支持多租户和空间隔离
- 创建者管理:CreatorID字段支持数据库所有权管理
- 状态管理:Status字段支持数据库的生命周期管理
- 物理映射:ActualTableName字段记录实际物理表名
- 草稿关联:DraftID字段关联对应的草稿版本
- 读写控制:RwMode字段控制数据库的读写权限
- 时间追踪:CreatedAt和UpdatedAt支持时间追踪和审计
5. 数据访问层
5.1 仓储接口定义
数据库仓储接口
数据库仓储接口定义了数据库资源的持久化操作能力,包括数据库的增删改查等核心功能。
type DatabaseRepository interface {// DeleteDatabase 删除数据库DeleteDatabase(ctx context.Context, id int64) error// GetDatabase 获取数据库信息GetDatabase(ctx context.Context, id int64) (*DatabaseInfo, error)// 其他CRUD方法...
}
删除方法特点:
- 事务删除:DeleteDatabase方法支持事务操作,确保数据一致性
- 上下文支持:支持context.Context进行请求链路追踪
- 错误处理:返回error类型,便于上层进行错误处理
- ID定位:通过唯一id精确定位要删除的数据库资源
5.2 数据访问对象(DAO)
数据库删除的具体实现
数据库删除操作涉及在线库和草稿库的同步删除,需要通过事务确保数据一致性:
func (d *databaseRepoImpl) DeleteDatabase(ctx context.Context, id int64) (err error) {onlineInfo, err := d.onlineDAO.Get(ctx, id)if err != nil {return fmt.Errorf("get online database info failed: %v", err)}draftInfo, err := d.draftDAO.Get(ctx, onlineInfo.GetDraftID())if err != nil {return fmt.Errorf("get draft database info failed: %v", err)}tx := query.Use(d.db).Begin()if tx.Error != nil {return fmt.Errorf("start transaction failed, %v", tx.Error)}defer func() {if r := recover(); r != nil {e := tx.Rollback()if e != nil {logs.CtxErrorf(ctx, "rollback failed, err=%v", e)}err = fmt.Errorf("catch panic: %v\nstack=%s", r, string(debug.Stack()))return}if err != nil {e := tx.Rollback()if e != nil {logs.CtxErrorf(ctx, "rollback failed, err=%v", e)}}}()err = d.draftDAO.DeleteWithTX(ctx, tx, draftInfo.ID)if err != nil {return fmt.Errorf("delete draft database info failed: %v", err)}err = d.onlineDAO.DeleteWithTX(ctx, tx, onlineInfo.ID)if err != nil {return fmt.Errorf("delete online database info failed: %v", err)}err = tx.Commit()if err != nil {return fmt.Errorf("commit transaction failed: %v", err)}// 删除物理表资源if draftInfo.ActualTableName != "" {_, err = d.rdb.DropTable(ctx, &rdb.DropTableRequest{TableName: draftInfo.ActualTableName,})// 错误处理...}if onlineInfo.ActualTableName != "" {_, err = d.rdb.DropTable(ctx, &rdb.DropTableRequest{TableName: onlineInfo.ActualTableName,})// 错误处理...}return nil
}
删除操作的关键特点:
- 事务保证:使用数据库事务确保所有删除操作的原子性
- 完整删除:同时删除在线库和草稿库的元数据和物理表
- 异常处理:完善的panic恢复和事务回滚机制
- 资源清理:删除数据库相关的物理表资源
- 错误包装:详细的错误信息,便于问题定位和调试
删除涉及的数据表:
- bot_table_info:数据库元数据表
- bot_table_info_draft:数据库草稿表
- 物理表:实际存储数据的物理表
DatabaseDAO结构体
DatabaseDAO是数据库数据访问的具体实现,负责与数据库进行交互,处理数据库资源的持久化操作。
package dalimport ("context""fmt""gorm.io/gorm""github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
)type DatabaseDAO struct {db *gorm.DBidGen idgen.IDGenerator
}func NewDatabaseDAO(db *gorm.DB, generator idgen.IDGenerator) *DatabaseDAO {return &DatabaseDAO{db: db,idGen: generator,}
}
数据库删除操作实现
删除数据库记录:
func (d *DatabaseDAO) DeleteWithTX(ctx context.Context, tx *gorm.DB, id int64) (err error) {result := tx.WithContext(ctx).Where("id = ?", id).Delete(&DatabaseInfo{})if result.Error != nil {return fmt.Errorf("delete database in transaction failed: %v", result.Error)}if result.RowsAffected == 0 {return fmt.Errorf("database not found with id: %d", id)}return nil
}
获取数据库信息:
func (d *DatabaseDAO) Get(ctx context.Context, id int64) (*DatabaseInfo, error) {var info DatabaseInforesult := d.db.WithContext(ctx).Where("id = ?", id).First(&info)if result.Error != nil {return nil, fmt.Errorf("get database failed: %v", result.Error)}return &info, nil
}
数据转换方法
数据库实体与数据模型转换:
func (d *DatabaseDAO) databaseInfoDO2PO(database *entity.DatabaseInfo) *model.DatabaseInfo {fieldListJSON, _ := json.Marshal(database.FieldList)return &model.DatabaseInfo{ID: database.ID,AppID: database.AppID,SpaceID: database.SpaceID,CreatorID: database.CreatorID,TableName: database.TableName,FieldList: string(fieldListJSON),ActualTableName: database.ActualTableName,DraftID: database.DraftID,RwMode: int32(database.RwMode),Status: int32(database.Status),}
}
数据模型转实体:
type databaseInfoPO model.DatabaseInfofunc (d databaseInfoPO) ToDO() *entity.DatabaseInfo {var fieldList []*entity.FieldItem_ = json.Unmarshal([]byte(d.FieldList), &fieldList)database := &entity.DatabaseInfo{ID: d.ID,AppID: d.AppID,SpaceID: d.SpaceID,CreatorID: d.CreatorID,TableName: d.TableName,FieldList: fieldList,ActualTableName: d.ActualTableName,DraftID: d.DraftID,RwMode: entity.BotTableRWMode(d.RwMode),Status: entity.BotTableStatus(d.Status),CreatedAt: d.CreatedAt,UpdatedAt: d.UpdatedAt,}return database
}
删除操作特点:
- 事务删除:DeleteWithTX方法支持事务操作,确保数据一致性
- 精确定位:通过id精确定位要删除的数据库资源
- 结果验证:验证删除操作是否实际影响了数据行
- 物理删除:执行物理删除操作,从数据库中彻底移除记录
- 资源清理:同时清理物理表资源,避免资源泄漏
删除数据库的完整数据访问流程:
在删除数据库的场景中,数据访问层的操作流程如下:
- 获取在线库信息:查询在线数据库的元数据信息
- 获取草稿库信息:根据在线库关联的DraftID查询草稿库信息
- 事务开始:开启数据库事务,确保删除操作的原子性
- 删除草稿库:在事务中删除草稿库记录
- 删除在线库:在事务中删除在线库记录
- 事务提交:所有删除操作成功后提交事务
- 删除物理表:提交事务后,删除相关的物理表资源
- 错误回滚:任何操作失败时回滚事务,保证数据一致性
这种设计确保了删除操作的安全性、可靠性和数据完整性。
数据访问层删除操作总结:
删除数据库在数据访问层的实现具有以下特点:
- 事务保证:所有元数据删除操作在事务中执行,确保数据一致性
- 完整删除:同时删除在线库、草稿库和物理表资源
- 验证机制:验证删除操作是否成功,确保资源确实被删除
- 错误处理:完善的错误处理和事务回滚机制
- 资源清理:彻底清理相关资源,避免资源泄漏
- 性能优化:通过精确的WHERE条件,确保删除操作的高效执行
5.3 数据库数据模型
DatabaseInfo数据模型
数据库数据模型定义了数据库资源在数据库中的存储结构,使用GORM标签进行ORM映射。
// DatabaseInfo 数据库信息模型
type DatabaseInfo struct {ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:数据库ID" json:"id"`AppID int64 `gorm:"column:app_id;not null;index:idx_appid;comment:应用ID" json:"app_id"`SpaceID int64 `gorm:"column:space_id;not null;index:idx_spaceid;comment:空间ID" json:"space_id"`CreatorID int64 `gorm:"column:creator_id;not null;index:idx_creatorid;comment:创建者ID" json:"creator_id"`TableName string `gorm:"column:table_name;not null;type:varchar(100);comment:表名" json:"table_name"`FieldList string `gorm:"column:field_list;type:text;comment:字段列表" json:"field_list"`ActualTableName string `gorm:"column:actual_table_name;type:varchar(255);comment:实际表名" json:"actual_table_name"`DraftID *int64 `gorm:"column:draft_id;index:idx_draftid;comment:草稿ID" json:"draft_id"`RwMode int32 `gorm:"column:rw_mode;default:0;comment:读写模式" json:"rw_mode"`Status int32 `gorm:"column:status;default:0;comment:状态" json:"status"`CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime;comment:创建时间" json:"created_at"`UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime;comment:更新时间" json:"updated_at"`
}// TableName 指定表名
func (*DatabaseInfo) TableName() string {return "bot_table_info"
}
删除操作中的模型特点:
- 主键定位:ID字段作为主键,是删除操作的核心定位字段,确保精确删除
- 索引优化:为AppID、SpaceID、CreatorID、DraftID字段建立索引,提高查询效率
- 关联字段:DraftID字段关联在线库和草稿库,支持级联删除
- 物理映射:ActualTableName字段记录实际物理表名,支持物理资源清理
- 类型约束:对字符串类型设置长度限制,确保数据完整性
- 默认值设置:为RwMode和Status字段设置默认值
- 时间戳自动管理:使用autoCreateTime和autoUpdateTime自动管理时间戳
- JSON序列化:通过json标签支持JSON序列化,便于API响应和日志记录
5.4 GORM生成的查询接口
GORM框架为数据库数据模型生成了丰富的查询接口,支持各种查询和操作场景。
// 根据ID查询
func (d *DatabaseDAO) GetByID(ctx context.Context, id int64) (*DatabaseInfo, error) {var info DatabaseInforesult := d.db.WithContext(ctx).First(&info, "id = ?", id)if result.Error != nil {return nil, result.Error}return &info, nil
}// 条件查询
func (d *DatabaseDAO) ListByCondition(ctx context.Context, condition map[string]interface{}, page, pageSize int) ([]*DatabaseInfo, error) {var infos []*DatabaseInfoquery := d.db.WithContext(ctx)// 添加查询条件for key, value := range condition {query = query.Where(key, value)}// 分页查询result := query.Offset((page - 1) * pageSize).Limit(pageSize).Find(&infos)if result.Error != nil {return nil, result.Error}return infos, nil
}
#### 查询接口定义
```go
type IWorkflowDraftDo interface {gen.SubQueryDebug() IWorkflowDraftDoWithContext(ctx context.Context) IWorkflowDraftDoWithResult(fc func(tx gen.Dao)) gen.ResultInfoReplaceDB(db *gorm.DB)ReadDB() IWorkflowDraftDoWriteDB() IWorkflowDraftDo// 删除操作相关方法Delete(conds ...gen.Condition) (gen.ResultInfo, error)Where(conds ...gen.Condition) IWorkflowDraftDo......
}
删除操作相关接口特点:
- Delete方法:提供类型安全的删除操作,支持条件删除
- Where条件:支持复杂的删除条件构建,确保精确删除
- 上下文支持:WithContext方法支持请求上下文传递
- 事务支持:支持在事务中执行删除操作,确保数据一致性
- 调试支持:Debug方法便于删除操作的SQL调试和优化
5.5 统一查询入口
文件位置:backend\domain\workflow\internal\dal\query\gen.go
该文件为删除工作流操作提供了统一的查询入口和事务支持,确保删除操作的一致性和可靠性。
核心代码:
// Code generated by gorm.io/gen. DO NOT EDIT.package queryimport ("context""database/sql""gorm.io/gorm""gorm.io/gen""gorm.io/plugin/dbresolver"
)var (Q = new(Query)WorkflowDraft *workflowDraft
)func SetDefault(db *gorm.DB, opts ...gen.DOOption) {*Q = *Use(db, opts...)WorkflowDraft = &Q.WorkflowDraft
}func Use(db *gorm.DB, opts ...gen.DOOption) *Query {return &Query{db: db,PluginDraft: newPluginDraft(db, opts...),ToolDraft: newToolDraft(db, opts...),}
}type Query struct {db *gorm.DBWorkflowDraft workflowDraft
}func (q *Query) Available() bool { return q.db != nil }func (q *Query) clone(db *gorm.DB) *Query {return &Query{db: db,WorkflowDraft: q.WorkflowDraft.clone(db),}
}func (q *Query) ReadDB() *Query {return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
}func (q *Query) WriteDB() *Query {return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
}func (q *Query) ReplaceDB(db *gorm.DB) *Query {return &Query{db: db,WorkflowDraft: q.WorkflowDraft.replaceDB(db),}
}type queryCtx struct {WorkflowDraft IWorkflowDraftDo
}func (q *Query) WithContext(ctx context.Context) *queryCtx {return &queryCtx{WorkflowDraft: q.WorkflowDraft.WithContext(ctx),}
}func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
}func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {tx := q.db.Begin(opts...)return &QueryTx{Query: q.clone(tx), Error: tx.Error}
}type QueryTx struct {*QueryError error
}func (q *QueryTx) Commit() error {return q.db.Commit().Error
}func (q *QueryTx) Rollback() error {return q.db.Rollback().Error
}func (q *QueryTx) SavePoint(name string) error {return q.db.SavePoint(name).Error
}func (q *QueryTx) RollbackTo(name string) error {return q.db.RollbackTo(name).Error
}
删除操作查询入口特点:
- 全局查询对象:提供全局的WorkflowDraft查询对象,便于删除操作的统一管理
- 事务支持:Transaction方法支持在事务中执行删除操作,确保数据一致性
- 读写分离:ReadDB和WriteDB方法支持数据库读写分离,删除操作使用WriteDB
- 上下文传递:WithContext方法支持请求上下文在删除操作中的传递
- 数据库切换:ReplaceDB方法支持动态切换数据库连接,便于多环境部署
- 事务管理:Begin、Commit、Rollback等方法提供完整的事务管理能力
5.6 数据访问层删除操作架构总结
删除工作流在数据访问层的实现体现了现代Go应用的最佳实践:
技术特点:
- 类型安全:使用GORM Gen生成类型安全的查询接口,避免SQL注入和类型错误
- 分层设计:Repository接口抽象数据访问,DAO实现具体的数据库操作
- 错误处理:统一的错误码包装机制,便于上层进行错误分类和处理
- 事务支持:完整的事务支持,确保删除操作的原子性
- 性能优化:精确的WHERE条件和索引利用,确保删除操作的高效执行
安全保障:
- 权限验证:通过OwnerID字段确保只有工作流所有者可以删除自己的工作流
- 存在性检查:删除前验证工作流是否存在,避免无效删除
- 物理删除:彻底从数据库中移除记录,确保数据清理的完整性
- 审计追踪:完整的时间戳记录,支持删除操作的审计和追踪
删除操作流程:
- 接口调用:上层通过Repository接口调用DeleteDraftWorkflow方法
- 事务开启:开启数据库事务确保操作的原子性
- 删除操作:删除工作流数据
- 事务提交:删除操作成功后提交事务
- 错误处理:任何步骤失败都会回滚事务并返回错误
这种设计确保了删除工作流操作的安全性、可靠性和高性能,为上层业务逻辑提供了坚实的数据访问基础。
数据模型与查询文件依赖关系
数据库表结构 (schema.sql)(workflow_draft表)↓ gen_orm_query.go
模型文件 (model/workflow_draft.gen.go) - 生成模型↓
查询文件 (query/workflow_draft.gen.go) - 依赖对应模型↓
统一入口 (query/gen.go) - 依赖所有查询文件
数据访问层架构总结
分层架构:
业务服务层 (Service)↓
仓储接口层 (Repository Interface)↓
数据访问层 (DAO Implementation)↓
GORM查询层 (Generated Query)↓
数据模型层 (Generated Model)↓
数据库层 (MySQL)
删除工作流在数据访问层的完整流程:
- 接口定义:
WorkflowRepository.DeleteDraftWorkflow(ctx, ID)
定义删除操作契约 - DAO实现:
WorkflowDraftDAO.DeleteWithTX(tx, ID)
实现具体删除逻辑 - 事务管理:使用事务确保工作流数据的一致性删除
- 删除操作:执行工作流数据的删除操作
- 错误处理:包装删除异常为统一错误码
- 结果返回:删除成功返回nil,失败返回包装后的错误
设计优势:
- 接口抽象:通过Repository接口实现数据访问的抽象化
- 代码生成:使用GORM Gen自动生成类型安全的查询代码
- 错误处理:统一的错误包装和处理机制
- 事务支持:通过context传递支持数据库事务
- 删除安全:通过ID精确定位,避免误删除操作
- 性能优化:合理的索引设计和查询优化
- 可测试性:清晰的分层结构便于单元测试
- 可维护性:代码生成减少手工编写,降低维护成本
- 单一职责:每个DAO类专注于单一实体的数据访问操作
删除操作的技术特点:
- 物理删除:当前实现为物理删除,直接从数据库中移除记录
- 事务操作:使用数据库事务确保工作流数据的一致性删除
- 索引优化:基于主键ID的删除操作,具有最佳的查询性能
- 错误分类:通过错误码区分不同类型的删除异常
- 审计支持:可通过数据库日志追踪删除操作的执行情况