[gogf/gf]关于dao分层和orm的疑问

2024-07-09 273 views
3
  1. 文档提到DAO层的作用是数据库操作收口。我看了示例项目focus项目的dao层实现,发现是在这一层对orm 的大部分方法再简单封装了一层,在service层从用orm链式调用获取数据,改成了用dao来链式调用获取数据,从代码上看实际还是用orm来进行数据存取,那为什么不直接用orm呢?恕我未能参透,不是很清楚这样封装背后的意图是什么?

  2. 按我的理解,dao层一般都是类似queryByName(name), queryByContentAndCreatedBy(content, order_by)这样的函数,goframe的dao只是对orm的简单封装,那框架推荐的dao的实现是怎么样的?或者说什么样的数据存储函数可以放到dao里面去呢?

回答

1

有同样的疑问

2

代码分层设计参考:https://goframe.org/pages/viewpage.action?pageId=3672442

dao层其实有行业标准规范,就是把数据库操作放到这一层,便于项目规范;比如你没有这一层,所有的sql,数据库操作都要前置到service层,这样service既有业务逻辑,又有数据库交互逻辑,容易维护混乱;

focus项目: 1、dao层下有internal,这个封装是由gf gen生成,不建议修改;dao层逻辑应该通知放到dao目录下文件; 2、由于dao层生成的代码已经能具备大部分业务查询逻辑,所以直接在service调用,当然有些数据库复杂逻辑或者需要复用放到dao层才更合理; 3、如果觉得focus项目有分层不合理的地方,可以提供具体代码,具体问题具体分析更好些;

9

@zcool321 你好,代码分层设计其实已经看过,但是有点没理解其中dao部分的设计。 如我前面问题第一点所言,focus项目,在dao层下的internal中,gf gen生成的代码类似于:

// Filter marks filtering the fields which does not exist in the fields of the operated table.
func (d *CategoryDao) Filter() *CategoryDao {
    return &CategoryDao{M: d.M.Filter()}
}

// Where sets the condition statement for the model. The parameter <where> can be type of
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
// multiple conditions will be joined into where statement using "AND".
// Eg:
// Where("uid=10000")
// Where("uid", 10000)
// Where("money>? AND name like ?", 99999, "vip_%")
// Where("uid", 1).Where("name", "john")
// Where("status IN (?)", g.Slice{1,2,3})
// Where("age IN(?,?)", 18, 50)
// Where(User{ Id : 1, UserName : "john"})
func (d *CategoryDao) Where(where interface{}, args ...interface{}) *CategoryDao {
    return &CategoryDao{M: d.M.Where(where, args...)}
}

// WherePri does the same logic as M.Where except that if the parameter <where>
// is a single condition like int/string/float/slice, it treats the condition as the primary
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
// WherePri function treats the condition as "id=123", but M.Where treats the condition
// as string "123".
func (d *CategoryDao) WherePri(where interface{}, args ...interface{}) *CategoryDao {
    return &CategoryDao{M: d.M.WherePri(where, args...)}
}

// And adds "AND" condition to the where statement.
func (d *CategoryDao) And(where interface{}, args ...interface{}) *CategoryDao {
    return &CategoryDao{M: d.M.And(where, args...)}
}

然后在service层,对dao的调用类似于

userRecord, err := dao.User.Fields(getDetailRes.User).WherePri(contentEntity.UserId).M.One()
    if err != nil {
        return nil, err
    }

这样的dao链式调用看起来就类似直接链式调用orm

userRecord, err := db.Table("user u").Fields("u.*,ud.site").Where("u.uid", contentEntity.UserId).One()
    if err != nil {
        return nil, err
    }

我的疑惑是,为什么dao层的封装是针对每个orm的方法再简单封装了一层,然后在service层对dao进行链式调用。而不是在dao层搞一个 类似于下面这样函数呢?

func (d *dao) getUserInfo(uid int) *userRecord{
    userRecord, err := db.Table("user u").Fields("u.*,ud.site").Where("u.uid", contentEntity.UserId).One()
    if err != nil {
        return nil, err
    }
    retrun userRecord
}

所以才会问,在dao这一层对orm 的大部分方法再简单封装了一层,在service层从用orm链式调用获取数据,改成了用dao来链式调用获取数据,从代码上看实际还是用orm来进行数据存取,那为什么不直接用orm呢?恕我未能参透,不是很清楚这样封装背后的意图是什么? 这个问题我还是没有理解到,希望能指点一二~ 感谢感谢🙂

7

1、https://goframe.org/pages/viewpage.action?pageId=3672442 官方介绍 《数据访问层 - DAL》章节 和 《如何清晰界定和管理service和dao的分层职责》 章节 可以参考

2、其实 软件开发 三层架构 就是如此;java大多数应用项目结构都是如此;

userRecord, err := db.Table("user u").Fields("u.*,ud.site").Where("u.uid", contentEntity.UserId).One()
    if err != nil {
        return nil, err
    }

像这样写,也不会有问题; 但是比如你表名改了, u.*里面字段增多减少了,ud.site字段名改了; 如果有多个service使用,在各个service去改很麻烦的,都用dao层统一修改维护,并且dao还能避免上述大部分问题的修改;

仅是举个例子参考,还有封装,单元测试,代码生成等等因素,简单文字描述有点费力气。。。

有疑问挺好的,其实统一规范,干就是了; 如有疑惑或者更好的建议欢迎探讨

7

@zcool321 仔细看了你的回复几遍,感觉我的疑问还是存在....还望能多指点一二 我的核心问题是,

  1. 在DAO层,goframe生成的代码是基于大部分的orm的方法再封装了一层,然后将这一层称为dao,那我去掉这一层对orm方法的封装,直接在service中使用orm,是不是就相当于没了dao层的概念?
  2. 结合字段名修改的问题,给我的感觉就像,goframe 的dao层 = 字段名定义 + orm方法封装。我所以为的dao层封装,是类似于
    
    func (d *dao) getUserInfo(uid int) *userRecord{
    userRecord, err := db.Table("user u").Fields("u.*,ud.site").Where("u.uid", contentEntity.UserId).One()
    if err != nil {
        return nil, err
    }
    retrun userRecord
    }

func (d dao) getBatchUser(uids int[]) userRecord{ userRecords, err := db.Table("user u").Fields("u.*,ud.site").WhereIn("u.uid", uids).All() if err != nil { return nil, err } retrun userRecords }


这样的,有特定入参限制,见名知意的封装。不明白goframe基于每个orm方法再封装一层的含义?似乎跟我所认知的dao不太一样?也不知道是不是我观念没跟上...

另外,参考laravel,我觉得goframe的orm就对应laravel的查询构造器,自动生成的DAO层代码,就对应laravel 的eloquent?
5

@zcool321 @luengwaiban @hzliangbin 大家好,不好意思来晚了。

  1. 其实老版本的dao中的代码有点冗余(工具自动生成的),将各个orm的方法封装了一遍,就为了能简单返回对应业务数据表的实体对象。一个是使得代码不太优雅,另一个是投入产出比不高,另一个是使得dao的扩展性不太好。新版本已经完全简化了,具体可以再使用工具生成代码看看。

  2. 关于dao里面应该放什么东西,仁者见仁,不同的团队在daoservice之间的"轻重"衡量不一样。 @luengwaiban 提的情况我在项目中也经常遇到,基本都将数据操作放到dao中,而servicedao只是简单的调用,这特别是在PHP项目中比较常见。但是随着业务的复杂度越来越高,我们往往发现在项目中后期难以将daoservice职责清晰界定了,很容易就变成了一坨屎山。因为dao已经封装了太多的数据操作,后续的业务变更为了图方便也直接在dao里面进行了修改,dao中的业务逻辑越来越多,并不是我们当初想得那么单纯。特别是实际中随着团队成员的不断变化、信息的传递缺失、人员技术质量的参差不齐,这样的现象越来越明显。因此后面我们发现如果将dao的职责严格限定,只提供工具能够自动生成的通用方法,那么业务逻辑基本都沉淀到service中,这样的项目长久之后不会混乱。这一点我在文档中也有介绍,没有踩过坑的同学挺难理解。 image

  3. 不过回过头来,你问你习惯的那种方式封装操作到dao中有没问题?这个很难回答,因为不同的项目阶段,不同的成员水平,不同的场景、业务背景下这种衡量和评估会很不一样。不过如果你是做一款长期维护的产品,业务复杂度会不断增长,我的建议是仔细思考下我说得设计模式。

1

@gqcn @luengwaiban 我理解了你们的想法,我比较认同 @luengwaiban 的想法,在dao层或者说类dao层的方法,提供数据获取的方法,dao层只提供对db的 curd方法,不处理业务逻辑; 业务逻辑有service层调用一个或多个dao方法处理,这样可以在dao层就直接知道有哪些地方调用,同时修改维护也更方便,比如数据获取都是表的某个状态才能输出。这样无论在golang,php,python,java在任何语言,这样的模式都是通用;无论是大型项目,还是中小型项目都是通用,每一层做每层应该做的事;

不过这其实对框架设计者现在也无需改变,依然可以在dao层封装getUserById(), getUserByIds(), getUserByIdsAndType(),CreateUser(),UpdateById() 这样的方法,然后团队的开发同学遵循这样的规范进行开发.