浅谈前后端 id 对接逻辑

453 阅读3分钟

背景

某天突然在线上环境发现了一个 id 重复的问题,具体如下。

img

由于框以 ID 作为区分标识,重复框 ID 会引起多个选中,从而引起标注时的不便。于是我就开始了 debug 之旅。

原因

首先我们定位到源码,从 frameId 定义角度进行排查, 发现 frameId 主要在标注框时进行定义,其逻辑如下:

img

其中关键的 setFeatureFrameId函数具体如下:

  setFeatureFrameId(feature: Feature, frameId: number) {
    let id = frameId
    if (!id) {
      this.maxFrameId++
      id = this.maxFrameId
    }
    feature.set('frameId', id)
  }

主要是根据 maxFrameId作为当页的唯一值标识进行赋值,这一逻辑乍一看没啥问题,但是细细思索,却是个很糟糕的设计。

关于 ID 前后端对接

一般而言,ID 都是从后端获取的,通过后端数据库的自增以确保唯一值。但常常的情况是用户操作新增时会先在前端进行数据的新增,而后再发送到后端,那么在这个“前端已新增但后端未新增”时,怎么去唯一标识新增数据呢?

根据个人经验及业内相关实践,主要比较好的方式有以下两种:

  1. 等待后端生成 ID

img

如图,ID 生成完全交由后端实现,优点是前端实现简单并准确,但代价是延迟 & 一定程度上的用户体验影响。

  1. 前端生成临时 ID 后在后端同步

img

这一步通过前端使用 UUID,nano ID等生成唯一值 ID,以保证独一标识,先行更新界面数据而不是等待后端的 ID 更新。这一方法优点很明显是缩短了数据更新的时间,优化体验,但代价是后端可能要稍微做一下兼容。

这两种方法都立基于让后端签发 ID的思想。但原代码中关于 id 更新的逻辑却不是这两种中的任何一种,它是完全由前端签发的:

  1. 完全前端签发 ID

img

如图,纯前端签发时,后端需新增一个字段进行存储,这种方法虽然和完全后端签发不同,但如果能确保 ID 的唯一性也不失为一种方法。

问题定位

但问题就出现 frameId 并不是一个唯一值。

如前代码所示,frameId通过 maxFrameId作为当页的唯一值标识进行赋值。根据业务逻辑,不同页的 frameId 可以相同,同页的frameId不同即可,预计原代码是根据此逻辑进行设计的 ID 赋值逻辑。

但由于可以存在跨页标记框,那么 frameId为了适应这一需求需要去对跨页的赋值打补丁。但如果当初选择后端签发 ID 或 唯一值 ID,那都不会出现这个 bug。

解决方案

由于代码耦合比较多,不好对 frameId 赋值逻辑进行重构,只能在跨页的函数出加上唯一值maxFrameId的兼容:

 const maxFrameId = features.length
        ? _.max(_.map(features, feature => feature.values_.frameId))
        : 0
 this.maxFrameId = _.max([maxFrameId, this.maxFrameId])

当前最佳的方案私以为是直接将 maxFrameId的赋值逻辑用唯一值替代,以避免后续的更改再出问题,成本也不大。

小结

id 签发这块逻辑最好还是让后端做,在前端做非常容易让逻辑变得复杂化。