技术解析

Repository 层的代码如何封装才比较合适?
0
2021-06-01 10:22:39
idczone

有两个 case,分别是不同人封装的,下面是伪代码:

case1:

// 根据用户昵称获取用户信息
function findUserByName(name){
    // 获取数据库链接
    db = _get_db()
    if (not db){
    	return error
    }

	// orm 查询
    user, err = db.where('name', name).get(table_name)
    if (err) {
        LOG("查找姓名失败", err)
        return error
    }

    return user
}

// 根据用户 ID 获取用户信息
function findUserById(userid){
    // 获取数据库链接
    db = _get_db()
    if (not db){
    	return error
    }

	美国服务器// orm 查询
    user, err = db.where('id', userid).get(table_name)
    if (err) {
        LOG("查找用户 id 失败", err)
        return error
    }

    return user
}

// service 层调用
user1 = userRepo.findUserByName('拜拜你条尾')
user2 = userRepo.findUserById(12)
code ...

case2:

// 简单封装了查询函数?
function query(where_array, limit){
    // 获取数据库链接
    db = _get_db()
    if (not db){
    	return error
    }
	
    // orm 查询
    user, err = db.where(where_array).limit(limit).get(table_name)
    if (err) {
        LOG("查找用户失败", err)
        return error
    }

    return user
}

// service 层调用
user1 = userRepo.query({name = '拜拜你条尾'}, 1)
user2 = userRepo.query({id = 12}, 1)
code ...

case2 这种封装有点迷的感觉,不能 cover 大部分的情况(where in/ or 之类),那这样的封装的意义何在?倒不如像第一种一样,不同的查询条件就封装一个方法,见名知意,不会给人带来疑惑?

不知道各位怎么看...

另外像请教下大家在这一层是怎么封装代码的?


太惨了....是没有人明白我在说什么吗....

仓储层应该是组合数据吧,sql 语句你应该放到 model 层吧

这个应该都是生成的吧,自动生成 get 的方法和 query 方法。query 方法就是传入参数自动解析拼接成查询条件,这个就看封装能力了,一般的查询 like,eq,in 等还有排序条件等简单封装一下就够了吧。其余的情况就在生成代码的基础上自己写单独的方法实现

解析查询条件,简单点如果实体类和数据库字段对应规则可以确定的话,传入实体类范型,遍历字段和值,就可以完成 eq 条件查询了。如果不好对应就传入 map,key 是字段+查询情况( like,eq,ne,gt,lt 等) value 就是值。这样就完成了查询条件的拼接了

可以参考下 mybatis 生成的 example 和 criteria 。我感觉这个思路还是很强大的。就是太复杂了。我们用不到那么复杂后期就换 mybatis plus 了

参考下 jpa 吧,方便得一批

这不是 java 的那一套....
应该说是语言无关的 php,go python 之类的都可以这么写

@gongym

case2 不是自动生成的,是人写的

case2 更灵活一些 我的理解 目的就是为了减少代码量

case2 改个名呗 query_with_limits 设置为私有,然后再写俩个 wrapper 一个 findUserByName 一个 findUserById 。这样两个方法的实现比较好维护。

工资按代码量选 1 反之 2//doge

让我选,那应该是 case1 吧,case2 的想法其实是可以的,但写法有问题
case2 这写法泄露了数据结构。`user1 = userRepo.query({name = '拜拜你条尾'}, 1)` 中的 `name` 是具体 BD 的字段名 ,repository 目的之一不就是隔离具体的数据存储过程吗。这样写的话,意味着具体数据的结构溢出到了其他的领域层(比如调用 repo 的是 Service 层)
case2 改改的话 `query(id int, name string, limit int)` 自动根据 id 和 name 哪个是空值自动创建 sql,或者动态语言的话直接 定义`query(user_info)` ,根据 user_info 的的类型( int 或 string )来创建对应的 sql (虽说感觉后者的方法可能今后不太好维护)

我理解的是,repository 层就不要向上层暴露细节了(比如 where 条件)。
的就挺好的

用 EF, 注入 context 。啊? java ?算了算了,手写吧。

灵活只是相对的,case2 的做法只是能让人方便添加 where and 的条件,但是要兼容 where in 或者 where or 的时候要怎么办呢?而且取了个叫 query()的名字,看上去就比较兼容多种查询情况的样子....实际上又不是能做大部分的工作。
如果我有一个 where in 的查询,那我是不是要重新封装一个函数?那这个时候这个函数叫什么好?

动态语言

Repository 感觉应该隐藏掉数据的存储方式吧,比如数据会存储在不同的位置,不同类型数据库,甚至是在文件里,对外只提供获取数据的接口。也有可能需要获取的数据并不对应一个数据表,可以在 Repository 做一些处理,所以 findUserByName 这种表意且隐藏数据存储方式的感觉会更好一些。userRepo.query 这种方式感觉就是要操作数据库了

1 是 repository 2 是 database helper
做成 1 调用 2 都行

有 orm 的链式调用的,如果说要做成 1 再调用 2,那为什么不直接用 orm db.where(‘name’, name).get()
而是
function getUserByName(name){
return self.query({‘name’ = name})
}

有 orm 的链式调用的,如果说要做成 1 再调用 2,那为什么不直接用 orm db.where(‘name’, name).get()
而是
function getUserByName(name){
return self.query({‘name’ = name})
}

可以参考 CQRS 的操作。
由于查询不存在副作用,因此可以不用拘泥于 domain 和 repository,直接从 domain 的上一层调用 orm 查询就可以了,domain 只提供一个实体的数据结构(当然你也可以另外弄 DTO,直接去掉对 domain 的依赖,怎么方便怎么来)
于是 repository 就只需要一个 `getEntityById()` 的方法,由上层(调用方,或者 application service )先通过复杂查询搞到 Id,在执行写命令的时候提供这个 Id 就可以。

简单,case 1 调用 case 2.
可以考虑把 case 2 作为高级接口开放出去。

是可以,不是必须或推荐
我说 2 是 helper 其实是说 2 不可能把自己叫 repo,理由其实和其他人一样的,封装层次的问题
至于 1 调 2 合理的可能性,比如 ORM 不够甜增加一些甜度,比如 where 是焦点问题(取决于项目情况可能有注入、扫表性能等等等各种痛点),通过收口( repo 不写 where 了)来解决某个问题
你也说了是跨语言聊,所以其实也说不了不同语言的常见 ORM 问题有啥,所以其实说不了 2 的存在合不合理,但 2 当作 repo 层的可能性是不存在的

高级接口?有 orm 了这种高级接口就显得,你想用又不想用的样子....

大概了解了

但是我又有另一个疑问是,多条件搜索的时候,case1 这种写法应该怎么写?比如用户管理后台,可以根据用户 id,姓名,状态,类型等进行搜索那就要写个 getUserList(id, name, status, type, page, page_size){ code... } 这样的函数?

但是我又有另一个疑问是,多条件搜索的时候,case1 这种写法应该怎么写?比如用户管理后台,可以根据用户 id,姓名,状态,类型等进行搜索那就要写个 getUserList(id, name, status, type, page, page_size){ code... } 这样的函数?



@simonlu9

其实是再想问下动态查询的时候,repository 层的代码要怎么写比较好


搜索有一点儿复杂的时候,可以定义个搜索条件的 struct 或类,根据需要的搜索的内容填上信息。这个 struct/类是和具体表结构无关的。或者写个简单的 query builder,这个 qury builder 依旧是和具体数据结构无关,然后把 builder 交给 repository 进行具体的搜索
如果是非常复杂的搜索的话,应该使用 CQRS

构建 query builder 是在 service 层构建吧?那构建完了我在 service 层直接 execute 这个 builder 不就可以了吗,还要扔到 repository 层?如果扔到 repository 层,那 repository 层只是简单执行并返回结果就好了,感觉有点怪怪的....
我感觉是不是应该是
```
// UserController 层
function List(req request) {
id = req.id
status = req.status
name = req.name
type = req.type
page = req.page
pageSize = req.page_size

res = userService.getUserList(id, status, name, type, page, pageSize)
return res
}
// UserService 层
function getUserList(id, status, name, type, page, pageSize) {
res = userRepository.getUserList(id, status, name, type, page, pageSize)
return res
}

// repository 层
function getUserList(id, status, name, type, page, pageSize) {
db = getDb() // orm 对象
if (id > 0){
db.where('id', id)
}
if (status > 0){
db.where('status', status)
}
if (name != ''){
db.like('name', '%' + name + '%')
}
.......
return db.orderBy(id, 'desc).limit(page, page_size).get()
}
```

你给 builder 定义个接口,然后 builder 的实现放到 infrastructure 里,上层调用 builder 的接口也没问题啊。我上面说把 builder 扔给 repository 是针对定义个简单搜索条件的 struct/类来说的,看做 struct/类的简单升级版。如果真想写个复杂点的 query builder,直接在 infrastructure 实现 query builder 会更好。如果还要复杂了,那考虑使用 CQRS

数据地带为您的网站提供全球顶级IDC资源
在线咨询
专属客服