閒聊軟體設計:軟刪除

更新 發佈閱讀 7 分鐘
圖片來源:ChatGPT 生成

圖片來源:ChatGPT 生成

我相信只要開發並營運系統有一段時間以上,應該都會遇到:「要刪除某一筆資料,但該筆資料因為其他因素要保留作其他用途」,例如:為了稽核等原因,有些資料要保存至少 n 個月。

又或是「要刪除資料庫某個表格的某一筆資料,但參考該筆資料的其他資料卻要保留」,例如:合作的廠商決定解約,因此要刪除帳號,讓該廠商無法繼續使用,但由於最後一期帳單還沒出,該廠商的用量資訊還不能刪除。

這時肯定有人想到,簡單,多加一個欄位,標記該筆資料已刪除,而不是真正的從資料庫刪除,這便是今天的主題:軟刪除。

停用的效果其實很像軟刪除,但停用和軟刪除是可以並存的,就看商業決策上需不需要。

實作軟刪除

實作軟刪除其實蠻容易的,如果搭配 Repository 且團隊對於欄位的名稱有一定的慣例 (convention),甚至可以將軟刪除的邏輯抽到共用的抽象層,各別 Repository 的實作只需在繼承時,宣告要使用硬刪除還是軟刪除即可。

將一筆資料軟刪除是整個故事裡最單純的,剩下的是讀取,要確保所有讀取的程式都要去判斷資料是否已經被軟刪除了。雖然可以比造上面的方式在共用層提供共用的邏輯:

但實務上,特定 Entity 的 Reposiory 時常會加入一些函式取得滿足特定條件的結果,這類函式也記得要檢查確保被軟刪除的資料不被撈出。另外,如果有使用讀取模型 (參閱閒談軟體設計:Read Model ),所有讀取模型的讀取邏輯也都必須加上對軟刪除的判斷。

同生共死?

一般來說,參考另一個資料表,會有個欄位參考另一個資料表的 ID,方便在查詢時快速篩選出需要的資料,SQL 允許當被參考的資料被刪除時,將該欄位設為 NULL (ON DELETE SET NULL),前提是該欄位允許設為 NULL,但設為 NULL 後可能讓資料篩選的機制失效。

使用軟刪除雖然可以避免 CASCADE DELETE 或是 CASCADE SET NULL 的問題,但關聯仍是一個要處理的問題,並沒有因為軟刪除,問題就消失了。

例如,一本菜單 (Menu),上面有很多個品項 (MenuItem),當春季限定菜單被軟刪除時,菜單裡的品項應該全部軟刪除嗎?這裡沒有標準答案,全看商業邏輯怎麼決定,假設商業邏輯上,每本菜單的品項都是獨立的,不共用,那軟刪除菜單時,是可以一併軟刪除品項;但如果是共用的,那就不用軟刪除品項。

raw-image

或是某個品項停售被軟刪除時,菜單在讀取品項時要怎麼處理?這同樣也沒有標準答案,可以一開始就濾掉被軟刪除的品項,也可以讀取,讓前端標示「已停售」。

假設,餐廳透過 POS 機打出一張訂單 (Order),上面有許多消費者點的項目與數量 (OrderItem),若因為消費糾紛,這張訂單作廢 (軟刪除),那該訂單的所有 OrderItem 要不要軟刪除?雖然也可以因商業邏輯而異,但通常會跟著軟刪除。

raw-image

那回到前面說的,如果某個品項停售,那要怎麼處理已經完成 (出完菜且已經付完款) 的訂單?基本上,這類交易資料是不該更動的,不只是停售,甚至連品項名稱或是金額都不該被更動,最好的方式是在訂單建立那一刻,就建立一個快照放到 OrderItem 中。

有人可能注意到,上面的例子使用類別圖而不是 ER 模型圖,主要是類別圖將關聯分成六種,忽略 OOP 特有的 Inheritance 與Implementation,描述物件之間關聯還有:

  • Composition — 用來描述物件同生共死的關係,例如 Order 與 OrderItem
  • Aggregation — 用來描述共用的關係,例如 Menu 和 MenuIem
  • Association — 用來描述較強的依賴
  • Dependency — 用來描述較弱的依賴

雖然類別圖和 ER 模型沒有直接的一對一轉換,但這四種關聯對於分析要怎麼處理軟刪除,個人覺得比 ER 模型更有幫助,通常 Composition 會跟著一起軟刪除,而其他則是商業決策有比較多的彈性。

雖然在實際工作中輸出的結果通常是資料表 Schema,但在思考設計的過程中,我還是比較習慣用類別圖,比較少畫類別圖,單純是讀者不多 (笑)。

開天眼

前面所說的是否要一起軟刪除,其實都是以「一般使用者」的角度出發,如何讓一般使用者處在「正常」不會感到「奇怪」的狀態下使用系統,但所有的系統都不會只有一種使用者,肯定會有管理系統或是負責營運的「超級使用者」,軟刪除某種程度上就是為了這類使用者而存在的。

為什麼?因為軟刪除讓這類使用者可以像是開了天眼一樣,可以看到一般使用者看不到的資料,例如,Order 和 OrderIem 被軟刪除了,消費者看不到這一筆資料,但營運單位仍可以分析有多少訂單被軟刪除了,以及背後的原因,最後用於改善系統。

甚至可以說,通常會需要軟刪除的「種種因素」很多是來自「超級使用者」的需求。例如,使用軟刪除一個因素是「可以支援還原」,畢竟超級使用者也是人,人就可能會犯錯,在營運上可能把某一筆資料誤刪除了,若是硬刪除,要復原是很困難的,更別說如果是 CASCADE DELETE 連鎖硬刪除了許多資料時,那復原更是困難。軟刪除資料都還在,只是被標註刪除,要復原只需要標註移除即可,相對硬刪除來說,復原容易多了。

小節

軟刪除是以提高系統複雜度為代價換取在刪除資料這件事上處理的彈性,但處理起來還是有很多事情要考慮,不是只是單單把某一筆資料標示為已刪除這麼簡單,資料之間的關係該怎麼處理,都是導入軟刪除時需要考慮的,問題通常不會消失,而是換一個方式處理罷了。

留言
avatar-img
Spirit的沙龍
58會員
114內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
Spirit的沙龍的其他內容
2026/01/04
在決定離開 Spring Boot 後,處理資料庫交易管理成了一個挑戰。本文探討如何在不依賴 Spring framework 的情況下,結合 HikariCP、Sql2o、Service Locator 和 ThreadLocal 來實現交易管理,並提供一個基於函式風格的聲明式範例。
Thumbnail
2026/01/04
在決定離開 Spring Boot 後,處理資料庫交易管理成了一個挑戰。本文探討如何在不依賴 Spring framework 的情況下,結合 HikariCP、Sql2o、Service Locator 和 ThreadLocal 來實現交易管理,並提供一個基於函式風格的聲明式範例。
Thumbnail
2025/12/28
透過實際應用場景,闡述如何設計 Read Model 以優化效能,並討論其與領域模型的關係,同時提出設計時需注意的細節,最後留下關於 Read Model 在架構中的位置的討論。
Thumbnail
2025/12/28
透過實際應用場景,闡述如何設計 Read Model 以優化效能,並討論其與領域模型的關係,同時提出設計時需注意的細節,最後留下關於 Read Model 在架構中的位置的討論。
Thumbnail
2025/12/21
分享瞭如何在新系統中應用樂觀鎖,透過 version 欄位簡化併發控制,同時保持高吞吐量。文章也觸及了樂觀鎖的進階應用及注意事項,並總結了兩種鎖機制的適用場景,為開發者提供實用的選擇指南。
Thumbnail
2025/12/21
分享瞭如何在新系統中應用樂觀鎖,透過 version 欄位簡化併發控制,同時保持高吞吐量。文章也觸及了樂觀鎖的進階應用及注意事項,並總結了兩種鎖機制的適用場景,為開發者提供實用的選擇指南。
Thumbnail
看更多