多版本業務模型設計

最近業務上用到比較多的多版本場景。這裏總結一下多版本業務模型設計的思路。

多版本需求梳理

先梳理一下多版本的一般訴求:

  1. 同一個數據經過多次編輯後,會產生多個版本,其中歷史版本不能刪除掉,因爲可能有上下游在使用;

  2. 多版本通常用於配置中,最新一個版本的配置通常可以多次修改、測試,確定後再發布;

  3. 已經發布的歷史版本不能隨便修改,因爲有數據在使用;

  4. 在消費側,一般默認是使用最新已發佈的版本;

  5. 多版本可能會有發佈審批、與上一個版本的 diff 等需求場景;

多版本狀態機設計

一個多版本的業務模型,通常會有以下的狀態機。其中 “廢棄” 不是必須的,回滾操作也不是必須的(回滾操作會給代碼和表設計帶來很大的複雜性),發佈中間可能會有發佈中、審批中等狀態。

草稿可以在原版本編輯,但已發佈的數據再編輯,就會生成一個新版本的草稿。

有時候也會有下線操作,這個時候所有版本的狀態就會被改爲 “已下線”。

多版本表設計

對於多版本而言,你需要有一個唯一標識這個業務數據的字段,可以叫id或者code

同時,需要一個字段來標識版本,這個版本建議是一個遞增的數字,叫version。有些業務期望版本是業務輸入的,或者有一個版本說明的概念,那可以新增一個字段叫version_desc

我們可以把唯一標識和版本拼接起來,作爲這個數據在這個版本的唯一鍵,可以叫code_version。通常是拼接成一個字符串,中間用某種特定的分隔符來區分,比如#。那code_version可能就長這樣:A12334#3。這裏就要求code裏面不能有分隔符#,不然代碼邏輯處理起來就比較麻煩。

這裏說一下這個拼接字段的必要性,因爲上下游往往會存 code + version。那上下游在列表查詢等場景來查詢數據的時候,如果沒有這個字段,只能循環一個個查,不能用where批量查詢。

另外一個必要的字段就是status來標識當前版本的狀態。

還有一個非必須的字段is_last_version,用來標識當前這條數據是不是它的最新版本,無論是草稿態還是已發佈還是已廢棄,它都會變成 true。這裏在待會兒下文的查詢要點中會解釋它的用處。如果不用這個字段也能查,但是需要group by order,整體查詢語句麻煩,效率低。在寫的時候多維護一下這個數據,會讓查詢的時候方便很多。

其它的都是審計字段了。最終的建表語句可能長這樣:

CREATE TABLE `t_xxx`  
(  
    `id`              bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',  
    `code`            varchar(255)    NOT NULL DEFAULT '' COMMENT 'code',  
    `version`         int             NOT NULL DEFAULT 0 COMMENT '版本',  
    `version_desc`    varchar(255)    NOT NULL DEFAULT '' COMMENT '版本說明',  
    `code_version`    varchar(255)    NOT NULL DEFAULT '' COMMENT 'code和版本',  
    `is_last_version` tinyint         NOT NULL DEFAULT '0',  
    `status`          varchar(255)    NOT NULL DEFAULT '' COMMENT '狀態',  
    # 以下是業務字段...  
    `name`            varchar(255)    NOT NULL DEFAULT '' COMMENT '名稱',  
  
    # 以下是審計字段...  
    `create_time`     timestamp       NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',  
    `create_by`       varchar(10)     NOT NULL DEFAULT '' COMMENT '創建人',  
    `modify_time`     timestamp       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',  
    `modify_by`       varchar(10)     NOT NULL DEFAULT '' COMMENT '修改人',  
    `deleted_at`      bigint                   DEFAULT '0' COMMENT '刪除時間秒時間戳',  
    PRIMARY KEY (`id`),  
    UNIQUE KEY `uk_code_version_deleted_at` (`code``version``deleted_at`)  
) ENGINE = InnoDB  
  AUTO_INCREMENT = 5  
  DEFAULT CHARSET = utf8mb4  
  COLLATE = utf8mb4_0900_ai_ci COMMENT ='xxx表'

生產端和消費端的查詢要點

生產端就是配置數據的地方,消費端就是使用的地方。生產端和消費端有一些區別,生產端往往要看最新的版本,包括草稿等狀態,還要能修改。而消費端一般只用最新已發佈的版本。

生產端

生產端的查詢,可以按照is_last_version爲 true 來過濾,這樣就只查每一個 code 的最新版本的數據。

同時,每個 code 也應該返回一個version列表,是這個數據code_version的集合,以便用戶查看和跳轉歷史版本。

生產端的寫入,需要維護好狀態、版本、is_last_version等字段。在編輯的時候,要判斷當前的狀態是草稿態還是已發佈,如果是已發佈,是要創建一條新的記錄(當然這個在前端判斷也是可以的,但後端要做好校驗,防止頁面沒刷新等場景造成髒數據)。

消費端

消費端的查詢,需要查詢最新已發佈版本,一般是通過狀態來過濾,比如status = Online

但這裏根據狀態過濾有一個問題:歷史已發佈的版本怎麼辦?如果一個 code 發佈了 3 個版本,那豈不是會查出來 3 條數據?要解決這個辦法有兩種思路:

我個人比較喜歡用第一種方案,少維護一個字段,僅僅多維護一個枚舉就行了。

其它注意事項

上下游

我們在上下游的接口交互中,除了要根據code查最新已發佈版本這種消費端場景外,通常用code_version來交互。這樣在 DB 中可以直接命中一條數據,查詢起來也方便。

這個數據和其他數據的關係,也通常使用code_version來存,因爲不同版本關聯的數據可能不同。

diff

diff 往往是利用領域模型 json 化後來 diff。這裏的 diff 能力可以做成一個通用的服務,傳入 old json 和 new json,返回哪些是新增的,哪些是刪除的,哪些是變更的。內部的邏輯一般是利用 json_path 和遞歸的方法來做。

diff 的難點是做成配置化,配置哪些屬性參與 diff,哪些屬性 ignore diff。diff 出來之後,可能枚舉等需要 key 轉換成 label,外部有一個轉換函數,或者前端去轉。

另一塊需要注意的就是數組的順序。有些字段雖然到 json 是數組,但業務上本身是順序無關的,這種數據的比對會更麻煩一些。

回滾

回滾其實很麻煩,包括已發佈的回滾到上一個版本、發佈中的回滾到草稿態。主要是前者很麻煩,尤其是有上下游使用了這個版本的數據,一般是不允許輕易回滾的。

如果有這類場景,多半是沒有上下游,比如服務發佈、應用發佈等。這種回滾,當前版本的數據一般也不會刪除,而是設置成一個特殊的狀態。下次編輯上一個版本的時候,生成的 version 也不是 + 1, 而是 + 2 甚至是 + n,還得查一遍庫,比較麻煩。

所以如果不是有特殊的需求,可以不做已發佈的回滾,它會帶來很多複雜性。

關於作者

我是 Yasin,一個愛寫博客的技術人

微信公衆號:編了個程 (blgcheng)

個人網站:https://yasinshaw.com

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/garPDFss_5FE88qpQPvPfA