事情是这样的,在有了 orm 之后,在封装一层数据访问层,我觉得是没有大问题的。但是问题是怎么进行封装比较合适呢?
下面是两个 case (伪代码):
case1:
// 新增用户昵称
function addName(user_id, name){
// 获取数据库链接
db = _get_db()
if (not db){
return error
}
add_time = time()
insert_row = {
user_id = user_id,
name = name,
status = this.status_using,
add_time = add_time,
updated_time = add_time,
}
insert_id, err = db.add(table, insert_row)
if (err) {
LOG("添加姓名失败" ,insert_row ,insert_id ,err)
return error
}
return true
}
// service 层调用
res = xxx.addName(12, '拜拜你条尾')
code ...
case2:
function db_option(handlearr){
is_ok = false
if (len(handlearr) <= 0) {
return is_ok
}
db = _get_db()
if (not db){
return is_ok
}
for (index, option in handlearr) {
is_ok = true
if (option.cmd== '__delete') {
res = db.delete(option.table_name, option.data)
if (res <= 0) {
LOG("delete data err where:",json_encode(option.data))
is_ok = false
}
}
if (option.cmd== '__insert'){
res = db.add(option.table_name, option.data)
if (not res) {
LOG("insert data failed:",json_encode(option.data))
is_ok = false
}
}
}
return is_ok
}
// service 层调用
handlearr = [
{cmd = '__insert',table_name = 't_user', data = userinfo},
{cmd = '__delete',table_name = 't_device', data = {id = deviceinfo.id}}
]
res = db_option(handlearr)
code ...
1.想知道怎样的封装才是比较合适的?个人认为第一种就够了,第二种的话好像慢慢会封装成另一个 orm 的感觉,而且隐藏的细节更多了.... 2.dao 层对 orm 再封装的话,怎么样才比较符合认知?
求各位赐教!
1
ChoateYao 2021-05-19 15:49:30 +08:00
拥有 ORM 只需要封装查询,增、删、改不需要封装到数据访问层里面。
毕竟增、删、改这些业务逻辑比较固定且一般都是面向对象操作。但是查会根据不同的条件查询,这里面变化比较多,所以封装到数据访问层里面方便后期迭代优化。 |
2
no1xsyzy 2021-05-19 15:59:26 +08:00
第二种简直是格林斯潘第十定律……
|
3
waibunleung OP @no1xsyzy 没太 get 到这个点
|
4
waibunleung OP @ChoateYao 那删除是直接在 service 层调用?但是我想问的问题不是这个,而是哪种封装更合适一点?
我觉得,像第一种的那样,我明确告诉你就是用了添加用户姓名的,不满足你的直接再封装一个,这样的界限明确。 第二种这样,半通用半不通用的风格就比较难受,你说我要扩展的时候,是直接改你的,还是直接封装个新的?而且 新增__insert 这样的映射,有了 orm 还搞一套命令映射,我有点不理解.... |
5
ChoateYao 2021-05-19 16:55:12 +08:00
@waibunleung 抛弃第二种,第一种的话跟你直接用 ORM 的对象操作也一样,只不过是分散到 services 里面。
比如 function create(name, age) { user = new User() user.setName(name) user.setAge(age) user.save() } function delete(id) { user = UserDAO.findById(id) if (!user) { return error } user.delete() } class UserDAO { function findById(id) { return UserORM.find('id = %d', id); } } |
6
waibunleung OP @ChoateYao 你这例子我感觉跟第一种都是一样的,dao 本来就是一个分层收口的作用,强调一个分层,你现在 dao 类也是基于 orm 封装了一层给外面用,我只是少了 dao 这个类,直接用了 orm 而已。例如你的 delete(id)函数我就变成了:
function delete(id) { user = UserORM.find({id = id}) if (!user) { return error } user.delete() } |
7
no1xsyzy 2021-05-19 18:25:06 +08:00
我是说,你的第二种,基本就是重新实现了一个不完整的 lisp
(db-do (insert 't_user userinfo) (delete 't_device `([id ,id]))) 就目前来看,你的两个重新封装都是多余的。 1. 你的 addName 并没有明确地与 xxx 产生关系,没必要将它放在 xxx 里。 最基本的,应当至少产生硬绑定 user.addSomething(...){ db.add({user_id:user.id, ...}) } 才有必要写在 user 这里。 2. 它其实根本没有降低任何复杂性,甚至干扰了表达力弱的类型系统的静态分析工作。 |
8
KouShuiYu 2021-05-19 18:54:25 +08:00
https://github.com/ckpack/pg-helper
要不借鉴借鉴我之前写的,单表操作还是挺嗨的🐶 |
9
KouShuiYu 2021-05-19 18:59:54 +08:00
|
10
waibunleung OP @no1xsyzy 感觉有点误解了。这个 xxx 代表的是 user_model 之类的数据访问层的意思,方法的目的是为用户增加昵称,那放在 user_model 里面这个问题不大。
数据访问分层没有强调降低复杂性,而是强调分层,职责分离 |
11
no1xsyzy 2021-05-20 00:08:33 +08:00
@waibunleung 我既然是 ORM,那应当已经做到 User(id=12).addName("john") 甚至是 User(id=12).name += ["john"] 这样了,你这样 xxx.addName 反而是改回去了。
职责分离和分层感觉相互正交,一个是横切一个是纵切。 大约还是得从 MVC 架构开始重新看一遍。 |
12
bsg1992 2021-05-20 09:45:37 +08:00
正如 11L 说的那样,你这样的封装 没有啥意义。
ORM 可以做到 dbContext.XXXTable.where(x=>x.age>=18 && x.sex =1).group(x=>x.school).sum(x=>x. score) 这样的查询你如何封装呢?按照目前的做法 ,无非就是在 DAO 层 增加一个与之对应的接口。 如果想做到业务查询能映射到数据库或者其他的数据源或者 ORM,你就得引入规约模式,还得需要吧规约转换成其他 library 写法。还得提供每个不同的 library 提供转换 provider 。 既然引入了规约是不是还得包装一个 unit of work 你做到这里时,你回发现你所做的这一切 ORM 都给你实现好了。 要做到业务查询和 ORM 之类的库解耦,这个绝对考验设计能力,绝对不是你这样简单的封装就解决的问题 |
13
waibunleung OP @bsg1992
"按照目前的做法 ,无非就是在 DAO 层 增加一个与之对应的接口。" 第一种写法不就是在做这样的事情吗? orm 是获取数据的,将获取数据的逻辑抽出来放在 dao 层,至于这个获取数据的逻辑函数,里面是通过 orm 获取,还是通过原始 sql 获取,都是可以的。 并不是说要做 orm 的封装,而是要把数据获取的逻辑抽离到 dao 层,然后才是我真正的问题,选择了这样抽离逻辑,第一种和第二种写法哪种更合适一点? ”dbContext.XXXTable.where(x=>x.age>=18 && x.sex =1).group(x=>x.school).sum(x=>x. score) 这样的查询你如何封装呢?” 按我的理解,就按照查询意图去封装,这个例子就是想获取 sum,那就在所谓的 dao 层弄个 getxxxSum(age, score)这样的方法 |
14
waibunleung OP @no1xsyzy orm 是可以做到 User(id=12).addName("john") ,然而不想在 service 层直接调用(虽然也可以)User(id=12).addName("john") ,所以才将 它变成了 user_model.addName(),这样 service 层都不需要关心他是关系型数据库还是 nosql 添加的,还可在函数里面做一些额外的操作。这是我抽离数据获取逻辑到一个函数的原因。
然后才是我真正的问题,既然我选择了抽离,第一种和第二种哪种合适?如果都不合适,那想请教一下,你们项目里面是怎么处理数据获取的逻辑的呢?方便的话希望能提供个简单的 demo,感谢~ |
15
waibunleung OP @bsg1992 如果说两种都不合适的话,想请教一下,你们项目里面是怎么处理数据获取的逻辑的呢?方便的话希望能提供个简单的 demo,感谢~
|
16
no1xsyzy 2021-05-20 12:40:45 +08:00
@waibunleung User(id=12).addName("john") 也不需要关注这是关系型还是 NoSQL
倒不如说最早就是模仿 NoSQL 的写法操作 RDBMS 。 你要进行额外操作应当是「面向切面编程」的课题,不过即便不是典型的 AOT 框架,也大多包含了方便的包装方法。 错误案例: https://github.com/no1xsyzy/bgmtinygrail/blob/master/src/bgmtinygrail/db/accounts.py 主要是没有一丁点「关系」,纯粹就是把 SQLite 当 kv store,才能这么玩(虽然 CharacterStrategy 那边有 ForeignKey,但可以看到基本没有任何效果) 相对正常的: https://github.com/no1xsyzy/great/blob/master/src/great/hierarchy.py 虽然很裸,但逻辑没有被覆盖,可以很明显地从对象语法中看出关系的存在。 |
17
waibunleung OP @no1xsyzy 正确案例 404 了,不知道是不是仓库没开公共权限的原因,看不到(哭
|
18
waibunleung OP @no1xsyzy 你说的错误案例,这个关系怎么理解呢?另外,感觉你说的错误案例和我的第一个 case 挺像的啊,“把 SQLite 当 kv store,才能这么玩” ,如果没有当成这样,那应该是怎么样玩?我觉得这样玩也没什么问题啊...
还望能提点一二 |
19
no1xsyzy 2021-05-21 09:24:24 +08:00
@waibunleung 确实是没开 public ……
还是拿经典的 article comment 模型,参考如下 1a. Article.query(Article.id=article_id).one().addComment(new Comment(...)) 1b. Article.query(Article.reference_key=refkey).one().addComment(new Comment(...)) 2a. addCommentForArticle(article_id, ...) 2b. article_id = findArticleIdFromRefKey(refkey) ;addCommentForArticle(article_id, ...) 比如你的业务目前发展到写出了 a 项,你需要扩充 b 项的代码,首先就导致了形态的不一致。 当然,你也可以把 2b 这两个部分 compose 起来,但这将导致每出现一个略微变动的业务你的 Model 层封装都要添加新的函数或者给函数扩展签名,会导致 Model 层极速膨胀。 |
20
waibunleung OP @no1xsyzy “当然,你也可以把 2b 这两个部分 compose 起来,但这将导致每出现一个略微变动的业务你的 Model 层封装都要添加新的函数或者给函数扩展签名,会导致 Model 层极速膨胀。”
目前我按照 case1 的写法,是有可能出现 model 层膨胀。但是按照 1a,1b 这种调用,感觉就是 orm 的链式调用,直接在 service 层调用 orm 我觉得不是太好....所以我会偏向于写 2a,2b 这种 |
21
no1xsyzy 2021-05-21 11:05:00 +08:00
Service 层直接用 ORM 有什么不好的呢?
数据分层的目的是避免底层数据结构变动导致多处代码同时修改,你要在 ORM 上套一层,是因为表结构时刻在变吗? 如果数据库表的定义不受控制,那没什么问题,但通常不会有这种情况吧。 或者在 SQL 基础上封装得了( (我从每个地方手写 sql 到半自动组装 sql 到每个行动添加一个 Model 层到 ORM 框架这条路重新走了一遍,我是不太想回去) |