共计 7222 个字符,预计需要花费 19 分钟才能阅读完成。
在领域驱动设计(DDD)中,资源库(Repository)是一种模式或组件,用于处理领域对象的持久化和数据访问操作。
资源库的主要目的是将领域对象与持久化机制之间解耦,使得领域层可以独立于底层数据访问实现。资源库隐藏了数据访问的具体细节,提供了一个统一的接口供领域层使用,从而简化了领域层和数据访问层之间的交互。
为什么要引用资源库?
-
解耦:资源库将领域层与数据访问层的耦合度降到最低,使得领域模型可以独立于数据存储技术的变化,提高了系统的可维护性和可扩展性。
-
抽象数据访问逻辑:资源库将复杂的数据访问逻辑封装在内部,为领域层提供简洁明确的API。领域层只需要关注领域相关的业务逻辑,而无需关心具体的数据存储和操作。
-
提供事务管理:资源库可以管理领域对象的生命周期,并且提供事务的支持。这样可以确保领域对象在持久化过程中的一致性和完整性,以及数据操作的原子性。
资源库如何实现数据访问的解耦呢?
资源库通过将领域对象和数据访问层进行隔离,通过一系列的接口和抽象类定义了领域层和数据访问层之间的协议。资源库负责将领域对象转换成持久化的数据表示形式,并将数据存储到持久化机制中。而领域层则通过资源库提供的接口来进行数据的读取和持久化操作,而无需知道具体的数据访问实现细节。
资源库可以使用不同的数据访问技术来实现,如关系型数据库、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
}