-
文档提到DAO层的作用是数据库操作收口。我看了示例项目focus项目的dao层实现,发现是在这一层对orm 的大部分方法再简单封装了一层,在service层从用orm链式调用获取数据,改成了用dao来链式调用获取数据,从代码上看实际还是用orm来进行数据存取,那为什么不直接用orm呢?恕我未能参透,不是很清楚这样封装背后的意图是什么?
-
按我的理解,dao层一般都是类似queryByName(name), queryByContentAndCreatedBy(content, order_by)这样的函数,goframe的dao只是对orm的简单封装,那框架推荐的dao的实现是怎么样的?或者说什么样的数据存储函数可以放到dao里面去呢?
[gogf/gf]关于dao分层和orm的疑问
回答
有同样的疑问
代码分层设计参考: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项目有分层不合理的地方,可以提供具体代码,具体问题具体分析更好些;
@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呢?恕我未能参透,不是很清楚这样封装背后的意图是什么? 这个问题我还是没有理解到,希望能指点一二~ 感谢感谢🙂
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还能避免上述大部分问题的修改;
仅是举个例子参考,还有封装,单元测试,代码生成等等因素,简单文字描述有点费力气。。。
有疑问挺好的,其实统一规范,干就是了; 如有疑惑或者更好的建议欢迎探讨
@zcool321 仔细看了你的回复几遍,感觉我的疑问还是存在....还望能多指点一二 我的核心问题是,
- 在DAO层,goframe生成的代码是基于大部分的orm的方法再封装了一层,然后将这一层称为dao,那我去掉这一层对orm方法的封装,直接在service中使用orm,是不是就相当于没了dao层的概念?
- 结合字段名修改的问题,给我的感觉就像,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?
@zcool321 @luengwaiban @hzliangbin 大家好,不好意思来晚了。
-
其实老版本的
dao
中的代码有点冗余(工具自动生成的),将各个orm
的方法封装了一遍,就为了能简单返回对应业务数据表的实体对象。一个是使得代码不太优雅,另一个是投入产出比不高,另一个是使得dao
的扩展性不太好。新版本已经完全简化了,具体可以再使用工具生成代码看看。 -
关于
dao
里面应该放什么东西,仁者见仁,不同的团队在dao
和service
之间的"轻重"衡量不一样。 @luengwaiban 提的情况我在项目中也经常遇到,基本都将数据操作放到dao
中,而service
对dao
只是简单的调用,这特别是在PHP项目中比较常见。但是随着业务的复杂度越来越高,我们往往发现在项目中后期难以将dao
和service
职责清晰界定了,很容易就变成了一坨屎山。因为dao
已经封装了太多的数据操作,后续的业务变更为了图方便也直接在dao
里面进行了修改,dao
中的业务逻辑越来越多,并不是我们当初想得那么单纯。特别是实际中随着团队成员的不断变化、信息的传递缺失、人员技术质量的参差不齐,这样的现象越来越明显。因此后面我们发现如果将dao
的职责严格限定,只提供工具能够自动生成的通用方法,那么业务逻辑基本都沉淀到service
中,这样的项目长久之后不会混乱。这一点我在文档中也有介绍,没有踩过坑的同学挺难理解。 -
不过回过头来,你问你习惯的那种方式封装操作到
dao
中有没问题?这个很难回答,因为不同的项目阶段,不同的成员水平,不同的场景、业务背景下这种衡量和评估会很不一样。不过如果你是做一款长期维护的产品,业务复杂度会不断增长,我的建议是仔细思考下我说得设计模式。
@gqcn @luengwaiban 我理解了你们的想法,我比较认同 @luengwaiban 的想法,在dao层或者说类dao层的方法,提供数据获取的方法,dao层只提供对db的 curd方法,不处理业务逻辑; 业务逻辑有service层调用一个或多个dao方法处理,这样可以在dao层就直接知道有哪些地方调用,同时修改维护也更方便,比如数据获取都是表的某个状态才能输出。这样无论在golang,php,python,java在任何语言,这样的模式都是通用;无论是大型项目,还是中小型项目都是通用,每一层做每层应该做的事;
不过这其实对框架设计者现在也无需改变,依然可以在dao层封装getUserById(), getUserByIds(), getUserByIdsAndType(),CreateUser(),UpdateById() 这样的方法,然后团队的开发同学遵循这样的规范进行开发.