V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
waibunleung
V2EX  ›  程序员

Repository 层的代码如何封装才比较合适?

  •  2
     
  •   waibunleung · 2021-05-28 12:09:22 +08:00 · 3070 次点击
    这是一个创建于 1035 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有两个 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 之类),那这样的封装的意义何在?倒不如像第一种一样,不同的查询条件就封装一个方法,见名知意,不会给人带来疑惑?

    不知道各位怎么看...

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

    32 条回复    2024-03-28 14:33:03 +08:00
    waibunleung
        1
    waibunleung  
    OP
       2021-05-28 14:22:15 +08:00
    太惨了....是没有人明白我在说什么吗....
    juzzle
        2
    juzzle  
       2021-05-28 14:35:11 +08:00
    仓储层应该是组合数据吧,sql 语句你应该放到 model 层吧
    gongym
        3
    gongym  
       2021-05-28 14:46:38 +08:00
    这个应该都是生成的吧,自动生成 get 的方法和 query 方法。query 方法就是传入参数自动解析拼接成查询条件,这个就看封装能力了,一般的查询 like,eq,in 等还有排序条件等简单封装一下就够了吧。其余的情况就在生成代码的基础上自己写单独的方法实现
    gongym
        4
    gongym  
       2021-05-28 14:55:54 +08:00
    解析查询条件,简单点如果实体类和数据库字段对应规则可以确定的话,传入实体类范型,遍历字段和值,就可以完成 eq 条件查询了。如果不好对应就传入 map,key 是字段+查询情况( like,eq,ne,gt,lt 等) value 就是值。这样就完成了查询条件的拼接了
    gongym
        5
    gongym  
       2021-05-28 14:57:39 +08:00
    可以参考下 mybatis 生成的 example 和 criteria 。我感觉这个思路还是很强大的。就是太复杂了。我们用不到那么复杂后期就换 mybatis plus 了
    simonlu9
        6
    simonlu9  
       2021-05-28 14:59:06 +08:00   ❤️ 1
    参考下 jpa 吧,方便得一批
    waibunleung
        7
    waibunleung  
    OP
       2021-05-28 15:36:37 +08:00
    这不是 java 的那一套....
    应该说是语言无关的 php,go python 之类的都可以这么写
    @simonlu9
    @gongym
    waibunleung
        8
    waibunleung  
    OP
       2021-05-28 15:37:00 +08:00
    @gongym case2 不是自动生成的,是人写的
    ksc010
        9
    ksc010  
       2021-05-28 15:58:13 +08:00
    case2 更灵活一些 我的理解 目的就是为了减少代码量
    InvincibleDream
        10
    InvincibleDream  
       2021-05-28 16:06:45 +08:00
    case2 改个名呗 query_with_limits 设置为私有,然后再写俩个 wrapper 一个 findUserByName 一个 findUserById 。这样两个方法的实现比较好维护。
    meshell
        11
    meshell  
       2021-05-28 16:08:38 +08:00
    工资按代码量选 1 反之 2//doge
    BeautifulSoap
        12
    BeautifulSoap  
       2021-05-28 16:09:12 +08:00
    让我选,那应该是 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 (虽说感觉后者的方法可能今后不太好维护)
    devld
        13
    devld  
       2021-05-28 16:11:08 +08:00 via Android
    我理解的是,repository 层就不要向上层暴露细节了(比如 where 条件)。
    #10 的就挺好的
    thtznet
        14
    thtznet  
       2021-05-28 16:24:48 +08:00
    用 EF, 注入 context 。啊? java ?算了算了,手写吧。
    waibunleung
        15
    waibunleung  
    OP
       2021-05-28 16:24:59 +08:00
    @ksc010 灵活只是相对的,case2 的做法只是能让人方便添加 where and 的条件,但是要兼容 where in 或者 where or 的时候要怎么办呢?而且取了个叫 query()的名字,看上去就比较兼容多种查询情况的样子....实际上又不是能做大部分的工作。
    如果我有一个 where in 的查询,那我是不是要重新封装一个函数?那这个时候这个函数叫什么好?
    waibunleung
        16
    waibunleung  
    OP
       2021-05-28 16:25:14 +08:00
    @thtznet 动态语言
    xiaoyiqingz
        17
    xiaoyiqingz  
       2021-05-28 16:40:42 +08:00
    Repository 感觉应该隐藏掉数据的存储方式吧,比如数据会存储在不同的位置,不同类型数据库,甚至是在文件里,对外只提供获取数据的接口。也有可能需要获取的数据并不对应一个数据表,可以在 Repository 做一些处理,所以 findUserByName 这种表意且隐藏数据存储方式的感觉会更好一些。userRepo.query 这种方式感觉就是要操作数据库了
    mcfog
        18
    mcfog  
       2021-05-28 16:47:45 +08:00 via Android   ❤️ 1
    1 是 repository 2 是 database helper
    做成 1 调用 2 都行
    waibunleung
        19
    waibunleung  
    OP
       2021-05-28 17:26:42 +08:00
    @mcfog 有 orm 的链式调用的,如果说要做成 1 再调用 2,那为什么不直接用 orm db.where(‘name’, name).get()

    而是
    function getUserByName(name){
    return self.query({‘name’ = name})
    }
    waibunleung
        20
    waibunleung  
    OP
       2021-05-28 17:26:55 +08:00
    @devld 有 orm 的链式调用的,如果说要做成 1 再调用 2,那为什么不直接用 orm db.where(‘name’, name).get()

    而是
    function getUserByName(name){
    return self.query({‘name’ = name})
    }
    ikesnowy
        21
    ikesnowy  
       2021-05-28 18:32:49 +08:00
    可以参考 CQRS 的操作。

    由于查询不存在副作用,因此可以不用拘泥于 domain 和 repository,直接从 domain 的上一层调用 orm 查询就可以了,domain 只提供一个实体的数据结构(当然你也可以另外弄 DTO,直接去掉对 domain 的依赖,怎么方便怎么来)

    于是 repository 就只需要一个 `getEntityById()` 的方法,由上层(调用方,或者 application service )先通过复杂查询搞到 Id,在执行写命令的时候提供这个 Id 就可以。
    forgottencoast
        22
    forgottencoast  
       2021-05-28 23:09:28 +08:00
    简单,case 1 调用 case 2.
    可以考虑把 case 2 作为高级接口开放出去。
    mcfog
        23
    mcfog  
       2021-05-29 01:08:25 +08:00 via Android
    @waibunleung 是可以,不是必须或推荐
    我说 2 是 helper 其实是说 2 不可能把自己叫 repo,理由其实和其他人一样的,封装层次的问题

    至于 1 调 2 合理的可能性,比如 ORM 不够甜增加一些甜度,比如 where 是焦点问题(取决于项目情况可能有注入、扫表性能等等等各种痛点),通过收口( repo 不写 where 了)来解决某个问题

    你也说了是跨语言聊,所以其实也说不了不同语言的常见 ORM 问题有啥,所以其实说不了 2 的存在合不合理,但 2 当作 repo 层的可能性是不存在的
    waibunleung
        24
    waibunleung  
    OP
       2021-05-29 15:02:33 +08:00
    @forgottencoast 高级接口?有 orm 了这种高级接口就显得,你想用又不想用的样子....
    waibunleung
        25
    waibunleung  
    OP
       2021-05-29 15:05:53 +08:00
    @mcfog 大概了解了
    waibunleung
        26
    waibunleung  
    OP
       2021-05-29 23:00:30 +08:00
    @mcfog 但是我又有另一个疑问是,多条件搜索的时候,case1 这种写法应该怎么写?比如用户管理后台,可以根据用户 id,姓名,状态,类型等进行搜索那就要写个 getUserList(id, name, status, type, page, page_size){ code... } 这样的函数?
    waibunleung
        27
    waibunleung  
    OP
       2021-05-29 23:02:09 +08:00
    但是我又有另一个疑问是,多条件搜索的时候,case1 这种写法应该怎么写?比如用户管理后台,可以根据用户 id,姓名,状态,类型等进行搜索那就要写个 getUserList(id, name, status, type, page, page_size){ code... } 这样的函数?
    @devld
    @BeautifulSoap
    @xiaoyiqingz
    @simonlu9
    waibunleung
        28
    waibunleung  
    OP
       2021-05-29 23:15:52 +08:00
    其实是再想问下动态查询的时候,repository 层的代码要怎么写比较好
    BeautifulSoap
        29
    BeautifulSoap  
       2021-05-29 23:20:07 +08:00
    @waibunleung

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

    如果是非常复杂的搜索的话,应该使用 CQRS
    waibunleung
        30
    waibunleung  
    OP
       2021-05-30 02:40:41 +08:00
    @BeautifulSoap 构建 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()
    }

    ```
    BeautifulSoap
        31
    BeautifulSoap  
       2021-05-30 11:58:00 +08:00
    @waibunleung 你给 builder 定义个接口,然后 builder 的实现放到 infrastructure 里,上层调用 builder 的接口也没问题啊。我上面说把 builder 扔给 repository 是针对定义个简单搜索条件的 struct/类来说的,看做 struct/类的简单升级版。如果真想写个复杂点的 query builder,直接在 infrastructure 实现 query builder 会更好。如果还要复杂了,那考虑使用 CQRS
    chaleaochexist
        32
    chaleaochexist  
       2 小时 48 分钟前
    大佬我遇到了和你一样的问题, 大佬最近有什么新体会吗?
    欢迎分享.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5334 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 09:21 · PVG 17:21 · LAX 02:21 · JFK 05:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.