领域模型-资源库 | DDD

共计 7222 个字符,预计需要花费 19 分钟才能阅读完成。

在领域驱动设计(DDD)中,资源库(Repository)是一种模式或组件,用于处理领域对象的持久化和数据访问操作。

资源库的主要目的是将领域对象与持久化机制之间解耦,使得领域层可以独立于底层数据访问实现。资源库隐藏了数据访问的具体细节,提供了一个统一的接口供领域层使用,从而简化了领域层和数据访问层之间的交互。

为什么要引用资源库?

  1. 解耦:资源库将领域层与数据访问层的耦合度降到最低,使得领域模型可以独立于数据存储技术的变化,提高了系统的可维护性和可扩展性。

  2. 抽象数据访问逻辑:资源库将复杂的数据访问逻辑封装在内部,为领域层提供简洁明确的API。领域层只需要关注领域相关的业务逻辑,而无需关心具体的数据存储和操作。

  3. 提供事务管理:资源库可以管理领域对象的生命周期,并且提供事务的支持。这样可以确保领域对象在持久化过程中的一致性和完整性,以及数据操作的原子性。

资源库如何实现数据访问的解耦呢?

资源库通过将领域对象和数据访问层进行隔离,通过一系列的接口和抽象类定义了领域层和数据访问层之间的协议。资源库负责将领域对象转换成持久化的数据表示形式,并将数据存储到持久化机制中。而领域层则通过资源库提供的接口来进行数据的读取和持久化操作,而无需知道具体的数据访问实现细节。

领域模型-资源库 | DDD

资源库可以使用不同的数据访问技术来实现,如关系型数据库、NoSQL数据库、文件系统等。在资源库的内部,可以封装一些通用的数据访问操作,如查询、添加、修改、删除等,以及事务的管理。通过资源库提供的接口,领域层可以通过调用这些方法来进行数据的读取和写入,而无需直接操作底层的数据访问技术。

通过资源库的设计,领域层可以专注于业务逻辑的实现,而与数据访问相关的细节交给专门的资源库来处理,从而实现了数据访问的解耦。

基于DT-Go的实践

用户资源库的实现

// Package repository 资源库
package repository

func init() {
    dt.Prepare(func(initiator dt.Initiator) {
        initiator.BindRepository(func() *User {
            return &User{}
        })
    })
}

var _ dependency.UserRepo = (*User)(nil)

// User ..
type User struct {
    dt.Repository
    EventMgnt domainevent.EventManager
    UniqueID  uniqueid.Sonyflaker // 唯一性ID组件
}

// BeginRequest ..
func (repo *User) BeginRequest(worker dt.Worker) {
    repo.Repository.BeginRequest(worker)
}

// Get ..
func (repo *User) Get(userID int) (userEntity *entity.User, err error) {
    userEntity = &entity.User{}
    sqlStr := "SELECT name, money, password, created, updated FROM hivecore.user WHERE id = ?"
    rows, err := repo.db().Query(sqlStr, userID)
    defer hiveutils.CloseRows(rows)
    if err != nil {
        return
    }
    for rows.Next() {
        userEntity.ID = userID
        err = rows.Scan(&userEntity.Name, &userEntity.Money, &userEntity.Password, &userEntity.Created, &userEntity.Updated)
        if err != nil {
            repo.Worker().Logger().Errorf("Get user error: %v", err)
            return
        }
    }

    // 注入基础Entity
    repo.InjectBaseEntity(userEntity)
    return
}

// Save 保存更新的数据,发布领域事件
func (repo *User) Save(userEntity *entity.User) (err error) {
    changes := userEntity.TakeChanges()
    if changes != "" {
        sqlStr := fmt.Sprintf("UPDATE hivecore.user SET %v WHERE id = ?", changes)
        _, err = repo.txdb().Exec(sqlStr, userEntity.ID)
        if err != nil {
            repo.Worker().Logger().Errorf("Save user error: %v", err)
            return
        }
    }
    return repo.EventMgnt.Save(&repo.Repository, userEntity)
}

// New ..
func (repo *User) New(uservo *vo.UserReq, money int) (entityUser *entity.User, err error) {
    uid, err := repo.UniqueID.NextID()
    if err != nil {
        repo.Worker().Logger().Errorf("new user error: %v", err)
        return
    }
    ct := hiveutils.NowTimestamp()
    user := po.User{
        ID:       uid,
        Name:     uservo.Name,
        Money:    money,
        Password: uservo.Password,
        Created:  ct,
        Updated:  ct,
    }
    sqlStr := "INSERT INTO hivecore.user (id, name, money, password, created, updated) VALUES (?, ?, ?, ?, ?, ?)"
    _, err = repo.db().Exec(sqlStr, uid, user.Name, money, user.Password, ct, ct)
    if err != nil {
        repo.Worker().Logger().Errorf("new user error: %v", err)
        return
    }
    entityUser = &entity.User{User: user}
    repo.InjectBaseEntity(entityUser)
    return
}

func (repo *User) db() *sqlx.DB {
    var db *sqlx.DB
    err := repo.FetchDB(&db)
    if err != nil {
        repo.Worker().Logger().Errorf("repository get db error: %v", err)
        panic(err)
    }
    return db
}

func (repo *User) txdb() *sql.Tx {
    var db *sql.Tx
    err := repo.FetchDB(&db)
    if err != nil {
        repo.Worker().Logger().Errorf("repository get txdb error: %v", err)
        panic(err)
    }
    return db
}

领域服务使用资源库

// Package domain 领域服务
package domain

func init() {
    dt.Prepare(func(initiator dt.Initiator) {
        // 绑定 User Service
        initiator.BindService(func() *User {
            return &User{}
        })
        initiator.InjectController(func(ctx dt.Context) (service *User) {
            // User 注入到控制器
            initiator.GetService(ctx, &service)
            return
        })
    })
}

// User 领域服务
type User struct {
    Worker      dt.Worker                   // 依赖注入请求运行时,无需侵入的传递。
    UserRepo    dependency.UserRepo           // 依赖倒置用户资源库
    Transaction *domainevent.EventTransaction // 依赖注入事务组件
}

// ChangeName 修改用户名
func (user *User) ChangeName(userID int, name string) (err error) {
    userEntity, err := user.UserRepo.Get(userID)
    if err != nil {
        user.Worker.Logger().Errorf("userId:%v, %v", userID, err)
        err = dtErr.New(user.Worker.Bus().Get("language"), hiveErr.ResourceNotFoundErr, fmt.Sprintf("userId:%v not found", userID), nil)
        return
    }
    if userEntity.Name == name {
        return
    }

    if err = userEntity.ChangeName(name); err != nil {
        user.Worker.Logger().Errorf("change name error:%v", err)
        return
    }

    // 使用事务组件保持一致性
    // 1.重命名 2.事件表增加记录
    err = user.Transaction.Execute(func() error {
        return user.UserRepo.Save(userEntity)
    })
    return
}

// ChangePwd 修改密码
func (user *User) ChangePwd(req *vo.UserPwdReq) (err error) {
    userEntity, err := user.UserRepo.Get(req.UserID)
    if err != nil {
        user.Worker.Logger().Errorf("userId:%v, %v", req.UserID, err)
        err = dtErr.New(user.Worker.Bus().Get("language"), hiveErr.ResourceNotFoundErr, fmt.Sprintf("userId:%v not found", req.UserID), nil)
        return
    }
    if userEntity.Password != req.OldPwd {
        err = dtErr.New(user.Worker.Bus().Get("language"), apiErr.PasswordError, "", nil)
        return
    }
    if err = userEntity.ChangePassword(req.NewPwd, req.OldPwd); err != nil {
        user.Worker.Logger().Errorf("change password error:%v", err)
        return
    }
    // 使用事务组件保持一致性
    // 1.修改密码 2.事件表增加记录
    err = user.Transaction.Execute(func() error {
        return user.UserRepo.Save(userEntity)
    })
    return
}

// Get 查询用户
func (user *User) Get(userID int) (result vo.UserInfoRes, e error) {
    userEntity, e := user.UserRepo.Get(userID)
    if e != nil {
        return
    }
    if userEntity.ID == 0 {
        e = dtErr.New(user.Worker.Bus().Get("language"), hiveErr.ResourceNotFoundErr, fmt.Sprintf("id:%v not found", userID), nil)
        return
    }
    result.ID = userEntity.ID
    result.Money = userEntity.Money
    result.Name = userEntity.Name
    return
}

// Register 用户注册
func (user *User) Register(req *vo.UserReq) (result vo.UserInfoRes, err error) {
    userEntity, err := user.UserRepo.New(req, 0)
    if err != nil {
        return
    }
    result.ID = userEntity.ID
    result.Money = userEntity.Money
    result.Name = userEntity.Name
    return
}

隐式的写时复制

通常我们通过资源库读取一个实体后,再对这个实体进行修改。那么这个修改后的持久化是需要知道实体的哪些属性被修改,然后再对应的去持久化被修改的属性。

注意商品实体的TakeChanges,商品被修改某个属性,对应的Repository就持久化相应的修改。这么写有什么好处呢?如果不这么做,那只能在service里调用repository指定更新列,但是这样做的话,Repository的价值就完全被舍弃了!

可以说写时复制是Repository和领域模型的桥梁!

// Package po Persistent Object
package po

// User 用户模型.
type User struct {
    changes  map[string]interface{}
    ID       int    // 用户id
    Name     string // 用户名称
    Money    int    // 金钱
    Password string // 密码
    Created  int64
    Updated  int64
}

// TakeChanges 获取更新.
func (obj *User) TakeChanges() (result string) {
    if obj.changes == nil {
        return ""
    }
    for k, v := range obj.changes {
        if vs, ok := v.(string); ok {
            result += fmt.Sprintf("%v='%v',", k, vs)
        } else {
            result += fmt.Sprintf("%v=%v,", k, v)
        }
    }
    result = strings.TrimRight(result, ",")
    obj.changes = nil
    return result
}

// setChanges 设置更新.
func (obj *User) setChanges(name string, value interface{}) {
    if obj.changes == nil {
        obj.changes = make(map[string]interface{})
    }
    obj.changes[name] = value
    obj.changes["updated"] = utils.NowTimestamp()
}

// SetName ..
func (obj *User) SetName(name string) {
    obj.Name = name
    obj.setChanges("name", name)
}

// SetMoney ..
func (obj *User) SetMoney(money int) {
    obj.Money = money
    obj.setChanges("money", money)
}

// SetPassword ..
func (obj *User) SetPassword(password string) {
    obj.Password = password
    obj.setChanges("password", password)
}

// SetCreated ..
func (obj *User) SetCreated(created int64) {
    obj.Created = created
    obj.setChanges("created", created)
}

// SetUpdated ..
func (obj *User) SetUpdated(updated int64) {
    obj.Updated = updated
    obj.setChanges("updated", updated)
}

// AddMoney ..
func (obj *User) AddMoney(money int) {
    obj.Money += money
    obj.setChanges("money", fmt.Sprintf("money + %v", money))
}
// Package entity 实体
package entity

// User 用户实体
type User struct {
    dt.Entity
    po.User
}

// Identity 唯一
func (u *User) Identity() int {
    return u.User.ID
}

// ChangePassword 修改密码
func (u *User) ChangePassword(newPassword, oldPassword string) error {
    // 更新密码
    u.User.SetPassword(newPassword)

    // 用户实体加入修改密码事件
    u.AddPubEvent(&events.ChangePwd{
        UserID: u.User.ID,
        NewPwd: u.User.Password,
        OldPwd: oldPassword,
    })
    return nil
}

// ChangeName 重命名
func (u *User) ChangeName(name string) error {
    // 更新名字
    u.User.SetName(name)

    // 用户实体加入重命名事件
    u.AddPubEvent(&events.ChangeUserName{
        UserID:   u.User.ID,
        UaseName: name,
    })
    return nil
}
正文完
 
Dustin
版权声明:本站原创文章,由 Dustin 2022-10-23发表,共计7222字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。