多版本業務模型設計
最近業務上用到比較多的多版本場景。這裏總結一下多版本業務模型設計的思路。
多版本需求梳理
先梳理一下多版本的一般訴求:
-
同一個數據經過多次編輯後,會產生多個版本,其中歷史版本不能刪除掉,因爲可能有上下游在使用;
-
多版本通常用於配置中,最新一個版本的配置通常可以多次修改、測試,確定後再發布;
-
已經發布的歷史版本不能隨便修改,因爲有數據在使用;
-
在消費側,一般默認是使用最新已發佈的版本;
-
多版本可能會有發佈審批、與上一個版本的 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 條數據?要解決這個辦法有兩種思路:
-
狀態機添加一個 Online_history 的狀態,在寫入的時候維護這種狀態;
-
表增加一個 is_last_online_version,在寫入的時候維護這個字段;
我個人比較喜歡用第一種方案,少維護一個字段,僅僅多維護一個枚舉就行了。
其它注意事項
上下游
我們在上下游的接口交互中,除了要根據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