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

接口设计合理性讨论

  •  
  •   sseven · 2023-09-12 10:54:53 +08:00 · 2944 次点击
    这是一个创建于 442 天前的主题,其中的信息可能已经有所发展或是发生改变。

    编辑保存的接口入参如下

    {
      id: 1,
      name: '',
      age: 1,
      childTable: [{ id: 1, cardNo: '', type: 1, del: 0 }],
    }
    

    关于 childTable 中的数据如何传?

    方式一

    • 无 id ,表示新增
    • 有 id ,表示编辑
    • 不传的表示删除了
      • 即,获取详情 childTable 返回了三条,如果删了一条,删的那条不用传了

    方式二

    • 无 id ,表示新增
    • 有 id ,表示编辑
    • 传 del=1,表示删除
      • 即,获取详情 childTable 返的数据中,如有有被删的,删除那条打 del 标记

    不同点在于删除的处理

    • 方式一需要后台查库比较哪些数据被删了
    • 方式二需要前台做逻辑,标记哪些数据被删了

    需要传 del 吗?哪种设计更合理?

    25 条回复    2023-09-13 10:34:51 +08:00
    geelaw
        1
    geelaw  
       2023-09-12 10:57:27 +08:00 via iPhone
    当然是删除传入 del=1 合理,否则只要有两个客户端并发保存数据就很容易发生踩踏事件。

    两个客户端可能只是开了另一个窗口,或者离开窗口很久之后忘记,重新打开,并在一年之后回到原来的窗口。
    luomao
        2
    luomao  
       2023-09-12 11:03:31 +08:00
    我觉得都不咋合理,如果非要选一个,建议选方式二。
    方案一有个非常严重的问题,用户 A 新增 id 1 2 3 ,用户 B 新增了 4 ,A 先提交,B 在提交,那么你这代码不就变成了仅保留 4 ,删除 1 2 3 。
    sseven
        3
    sseven  
    OP
       2023-09-12 11:04:03 +08:00
    @geelaw 懂,不传 del,可能删别人新增的数据,传 del,精准到此次编辑的数据
    opengps
        4
    opengps  
       2023-09-12 11:04:33 +08:00
    方式一“不传的表示删除了”有重大缺陷
    justfindu
        5
    justfindu  
       2023-09-12 11:05:11 +08:00
    我们选的方式二 给标记, 你要选一, 那就是 del 单条操作
    yazinnnn
        6
    yazinnnn  
       2023-09-12 11:07:24 +08:00
    除非你的数据以客户端为准, 服务端的接口只是同步数据的目的, 否则哪个都不合理
    NessajCN
        7
    NessajCN  
       2023-09-12 11:26:47 +08:00   ❤️ 4
    拆成三个接口,/edit /new /delete
    wyx119911
        8
    wyx119911  
       2023-09-12 11:37:19 +08:00
    直接传三个 childTable 数组多清晰
    Pythoner666666
        9
    Pythoner666666  
       2023-09-12 12:46:22 +08:00
    如果 childTable 不超过 10 条直接方式一 ,因为 childTable 我会直接存成一个 string ,每次直接覆盖。如果条数很多那么久方式二
    IvanLi127
        10
    IvanLi127  
       2023-09-12 13:20:33 +08:00 via Android
    如果不做多人同时编辑的话,我选一。直接批量删除再批量添加,比都不用比。这种要么拆接口,要么直接方案一,方案二的话为啥不拆接口,或者做成 put 数组和 delete 数组?
    leetcode2020
        11
    leetcode2020  
       2023-09-12 13:32:57 +08:00
    综合方式一、二和其它老哥回复,可得:
    1. 新增条目:条目没有`id`字段(或`id`为 null 或特殊标记)。
    2. 编辑条目:条目有`id`字段,并且`del`字段未标记或为 0 。
    3. 删除条目:不从列表中移除,而是保留`id`字段,并将`del`字段标记为 1 。

    优点:
    1. 数据一致性:通过保持所有条目(包括已删除的)在列表中,并明确标记它们的状态,我们可以减少因数据丢失或时间差产生的数据一致性问题。
    2. 简化后端逻辑:后端可以直接根据`id`和`del`字段来确定应该执行哪种操作(新增、编辑或删除),而无需与数据库中的现有数据进行比较。
    3. 前端逻辑清晰:前端只需要在用户执行删除操作时更新`del`字段,而不是从列表中移除条目,使得前端逻辑更为简单和清晰。

    缺点:
    1. 数据传输量:由于已删除的条目仍然保留在列表中,会稍微增加数据传输量。

    示例:
    {
    "id": 1,
    "name": "",
    "age": 1,
    "childTable": [
    { "id": null, "cardNo": "", "type": 1, "del": 0 }, // 新增
    { "id": 1, "cardNo": "", "type": 1, "del": 0 }, // 编辑
    { "id": 2, "cardNo": "", "type": 1, "del": 1 } // 删除
    ]
    }
    eatgrass
        12
    eatgrass  
       2023-09-12 13:47:34 +08:00
    从前到后一套接口,又想优雅,说实话很难

    一个接口直接做整个聚合根( Aggregate Root )资源的更新,REST 的做法一般会继续往下拆解,即内嵌资源的增删改查 继续暴露接口

    两套接口的做法增加 BFF 层,这套接口直接为前端服务,不考虑后端领域模型,中间层组装请求调用后端的 Rest 接口
    kujio
        13
    kujio  
       2023-09-12 14:13:06 +08:00
    /**
    * 比较两个列表的差异
    *
    * @param newList 新的列表
    * @param oldList 旧的列表
    * @param keyGetter 主键获取器
    * @return 返回新列表对比旧列表变化的元素(新增的、修改的、删除的)
    */
    public static <T> SeparateRst<T> separate(List<T> newList, List<T> oldList, SeparateKeyGetter<T> keyGetter) {}
    ychost
        14
    ychost  
       2023-09-12 15:53:45 +08:00
    建议删除添加一个接口
    leonshaw
        15
    leonshaw  
       2023-09-12 16:02:09 +08:00
    不一定,看业务逻辑。举个简单的情况:如果 childTable 是有序的,怎么解决冲突呢?
    imokkkk
        16
    imokkkk  
       2023-09-12 16:33:42 +08:00
    按我们的习惯 根据有无 id 区分是新增 还是 修改还可以接受,删除最好是单独出来一个接口
    dreamKing
        17
    dreamKing  
       2023-09-12 16:34:54 +08:00
    如果是我的话,应该回这样吧
    {
    id: 1,
    name: '',
    age: 1,
    childTable: [{ id: 1, cardNo: '', type: 1}],

    }
    dreamKing
        18
    dreamKing  
       2023-09-12 16:36:40 +08:00
    @dreamKing 不小心发出去了 完整的是
    {
    id: 1,
    name: '',
    age: 1,
    childTable: [{ id: 1, cardNo: '', type: 1}],
    delChildId[{id:1},{id:2} ]
    }
    Edward4074
        19
    Edward4074  
       2023-09-12 16:44:57 +08:00
    我现在的方案,原来的编辑接口用的方案 1 ,单独开个 patch 接口用于增量更新,可以同时处理增删改

    {
    id: 1,
    action: "ADD/EDIT/DEL"
    data: {
    name: '',
    age: 1,
    childTable: [{
    id: 1,
    action: EDIT,
    data: {
    cardNo: '',
    type: 1
    }
    }, {
    action: ADD,
    data: {
    cardNo: '',
    type: 1
    }
    }, {
    id: 1,
    action: DEL
    }
    ]
    }
    }
    Edward4074
        20
    Edward4074  
       2023-09-12 16:46:24 +08:00
    @Edward4074 这 json 格式没法看……自己格式化下吧 /doge
    debuggerx
        21
    debuggerx  
       2023-09-12 16:50:03 +08:00
    我的话宁愿用 POST/PUT/DELETE 方法来区分……
    wpzz
        22
    wpzz  
       2023-09-12 17:35:57 +08:00
    拆成三个接口,能监控到用户不同的行为。

    而且后端一个 controller 三个不同 service 不合理。
    yinmin
        23
    yinmin  
       2023-09-12 23:31:52 +08:00 via iPhone
    写 3 个接口,通过 url 区分,都用 post method ,别 restful 过不了等保
    luermao
        24
    luermao  
       2023-09-13 09:03:38 +08:00
    方式二
    wei2629
        25
    wei2629  
       2023-09-13 10:34:51 +08:00
    有的情况是无法拆分。不是一个操作就保存,而是多个操作后一次性保存。 可能考虑读取数据然后比对。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3596 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 10:51 · PVG 18:51 · LAX 02:51 · JFK 05:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.