閒談軟體設計:Database Driven Design?

更新於 發佈於 閱讀時間約 8 分鐘


今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,試著回想一下,平常在開發一個新功能時,是不是有以下症狀:

  • 總是先從 database schema 開始設計,連 use case 都還沒討論清楚,就已經先有 schema 了
  • 討論 use case 時,都是針對 database schema 的 CRUD,連 API 看起來都像是某個 table 的 CRUD
  • 除了 service 物件,物件都只有資料,沒有 getter/setter 以外的邏輯 (貧血模型)
  • 不太考慮 boundary context,把不同服務所需要的資訊都放在一個大表中,於是形成一個超大物件。


如果有以上症狀,那有很高的機率是 Database Driven Design,這邊要先聲明,這種設計方式不見得是不好的,如果是簡單的應用程式,只需 CRUD 卻硬要套複雜的 Clean Architecture 或是 Domain Driven Design 有時反而沒有必要,使用某些現成的框架,快速地完成 CRUD 的開發是可以接受的。

在討論 Database Driven Design 可能遇到的問題前,先閒聊一下個人過去的學習經驗吧!大二算是正式接觸物件導向設計,在那之前都是自學,從各種不同的管道東拼西湊關於物件的概念,可能是學校課程太過偏於語言怎麼實作物件導向的特性,於是那門課上完後,除封裝、繼承與多型,大多記得的是語言特性 (C++),像是 class、pointer、overloading、pure virtual function,實際上怎麼用這些東西設計出真正的物件導向系統,倒是沒什麼概念。

大三學資料庫系統,從正規化開始,什麼第一正規化、第二正規化到第三正規化,ER 模型,一對多、多對多、reference key 等等,最後專題用 PHP 寫個組裝電腦用的購物車 (跟原價屋有點像,但會幫忙過濾掉不相容的規格,例如選 Intel 的 CPU 會過濾掉 AMD 晶片組的主機板之類的),那時就真的是 Database Driven Design 了,物件導向完全沒用上,但還是弄出個可以動的系統。

碩一的物件導向設計課,學 design pattern,整個學期分七次作業慢慢寫出一個 class diagram 編輯器,加上作業要求用上特定的 design pattern,慢慢開始對物件導向有感覺了。接著修 OOAD,從 use case 開始分析模型,用在自己的論文上,開發 visual language 的編輯器,算是比較熟悉物件導向設計了。這兩個 editor 用的是自定義的檔案結構,沒有用到資料庫,因此完全不會有被 schema 綁架的問題。

好,回到正題,會寫這篇主要是進到業界後,開發系統時一定會碰上資料庫,只要碰上資料庫,總是覺得哪裡怪怪的,物件之間的關聯是為了 ORM 寫的、核心的模型卻依賴 ORM 框架,DAO 或是 repository 充斥著各種邏輯,又或者出現上述的幾個症狀。

首先,OO 模型和 ER 模型是不一樣的,OO 有繼承、實作、composite、aggregate 等不同關係,不是每一個都能一對一的對應到 ER 模型,ER 模型為了處理多對多的關係,會有 relation table,但 OO 不需要,ER 需要 reference key,或是因應 multi-tenancy 設計所加的 columns,但這些資料作為物件的 field 卻不一定有用。例如,一個多商家使用的電商系統,在訂單資料表上一定會有像是 merchant_id 的 column,但訂單物件卻不一定要有 merchantId 的 filed。


Figure 1 - ER Model

Figure 1 - ER Model


Figure 2 - Class Diagram

Figure 2 - Class Diagram


從 Figure 1 和 Figure 2 可以看到兩者的差異,事實上,Merchant 物件需不需要有一個 orders 的陣列,代表某個商家的所有訂單,或是 Order 有個 merchant 指向所屬的商家?若是使用 ORM 框架,框架會建議你要加,但這是因為 ORM 框架需要而不是你的 Domain Model 需要。

因此,從 database schema 出發,往往會影響到 OO 的模型設計,但有趣的是,自己的經驗是,先從 OO 模型出發,反而不太會影響到 database schema 的設計。

再來,個人覺得最大的問題是,都沒有再討論行為,都只討論狀態,OO 的重點是把狀態封裝,透過有限的行為操作物件,OO 的模型怎麼可能會沒有行為?在閒談軟體設計:State 與語言中,在建立 SlotMachine 介面時,都是以自動販賣機能提供什麼行為開始,內部的狀態反倒是其次。這邊提供兩種不同的實作範例,第一種用狀態的角度出發進行設計 (解說用,某些內容省略,不能編譯):


第二種則是從可以有什麼操作的角度出發進行設計:


不知道大家比較喜歡那個版本?我覺得寫程式沒有標準答案,所以僅提供幾個觀點讓大家思考:

  • 當要呼叫 API 接受訂單時,是 PUT /orders/{merchantId}/{orderId}/state 比較直覺?還是 PUT /orders/{merchantId}/{orderId}/accepted 比較直覺?
  • UpdateOrderStateRequest 也把取消訂單需要的內容也放進去時,說明文件好寫嗎?
  • 能不能接單的邏輯,到底該歸 OrderManager 還是 Order
  • 比較喜歡 switch...case 的直接了當,還是清楚語意的 method name?
  • 當要修改接受訂單的邏輯時,哪個版本比較容易知道修改何處?
  • 修改完後,那個版本需要比較多的回歸測試?取消訂單要測試嗎?
  • Order 還是沒有邏輯的貧血模型嗎?
  • 物件一定需要提供 setter 嗎?

基於上述的觀點,我個人喜歡第二個版本。當然,這是隱含著使用 OO 作為設計的基礎假設,若是使用 functional programming 或是 Data-oriented programming,可能會有完全不同的想法。貧血模型不見得不好,就看團隊的喜好,會變成貧血模型,也不完全一定是因為 Database Driven Design,但自己的觀察是 Database Driven Design 確實比較容易變成貧血模型。

關於 boundary context 的症狀,單從 single responsibility 的角度來看 (參閱閒談軟體設計:Single Responsibility),一個物件承擔多種服務的設定是合適的嗎?

一個電商平台會提供多種服務,下單 (order)、支付 (payment)、物流 (logistics),不同的服務都有各自的設定,那大家會比較喜歡 Figure 3 中的 SuperMerchant 還是 SimpleMerchant 呢?


Figure 3 - SuperMerchant vs. SimpleMerchant

Figure 3 - SuperMerchant vs. SimpleMerchant


這一樣沒有標準答案,同樣提供幾個觀點去思考:

  • 假設設定的屬性之間有關連性,有邏輯需要驗證關聯性,例如若開啟信用卡,就必須開啟至少一種卡別,那邏輯放在 SuperMerchant 或是個別的物件中,哪個比較直覺?
  • 在實作 OrderService 時,取得一個 SuperMerchant 或是 OrderSetting,哪個比較直覺?當然也有可能 SimpleMerchantOrderSetting 都需要的情況。
  • 微服務流行了好一陣子,假設下單 (order)、支付 (payment)、物流 (logistics) 都拆分成微服務,在資料庫分離的情況下,哪個比較容易?

雖然說讓 Database Driven Design 背這個鍋其實有點尷尬,只是跟貧血模型的症狀很像,自己的觀察,Database Driven Design 好像特別容易會變成這樣。

好啦,來總結一下,如果正在嘗試用 DDD 開發應用程式,也許可以思考一下,是哪一種 DDD?有以上幾個症狀嗎?

留言
avatar-img
留言分享你的想法!
avatar-img
Spirit的沙龍
55會員
107內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
Spirit的沙龍的其他內容
2024/03/23
這篇文章探討了在軟體開發中的技術債可能來自哪些原因,以及如何自動化偵測與修復技術債。作者透過分享不同情境下的技術債選擇,提供了對於技術債的思考與建議,針對開發人員在需要做出無奈的技術決策時,提供了一些建議。此外,還提供了一些在做出技術決策時的方法,如保留抽象層和避免vendor lock-in。
Thumbnail
2024/03/23
這篇文章探討了在軟體開發中的技術債可能來自哪些原因,以及如何自動化偵測與修復技術債。作者透過分享不同情境下的技術債選擇,提供了對於技術債的思考與建議,針對開發人員在需要做出無奈的技術決策時,提供了一些建議。此外,還提供了一些在做出技術決策時的方法,如保留抽象層和避免vendor lock-in。
Thumbnail
2024/03/02
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
2024/03/02
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
2024/02/24
起源是當時 Facebook 有篇文章討論不少人分不清楚上述二者的差別,當時寫了首部曲《閒談軟體設計:API Naming Style》,接著是《閒談軟體設計:內部函式庫》,但始終沒談到 library 和 framework 的差別,主要是沒有好的例子,這次這例子還蠻不錯的。
Thumbnail
2024/02/24
起源是當時 Facebook 有篇文章討論不少人分不清楚上述二者的差別,當時寫了首部曲《閒談軟體設計:API Naming Style》,接著是《閒談軟體設計:內部函式庫》,但始終沒談到 library 和 framework 的差別,主要是沒有好的例子,這次這例子還蠻不錯的。
Thumbnail
看更多
你可能也想看
Thumbnail
動態規劃Dynamic Programming其實是 一種泛用的演算法思考方式與演算法建構框架。 動態規劃並不拘束於只能解課本上特定的的範例題。 只要我們能找出DP狀態定義、DP遞迴結構、初始條件(終止條件),就能適用動態規劃來解題,以數學的形式表達,並且在紙筆上或者電腦上、計算機上計算
Thumbnail
動態規劃Dynamic Programming其實是 一種泛用的演算法思考方式與演算法建構框架。 動態規劃並不拘束於只能解課本上特定的的範例題。 只要我們能找出DP狀態定義、DP遞迴結構、初始條件(終止條件),就能適用動態規劃來解題,以數學的形式表達,並且在紙筆上或者電腦上、計算機上計算
Thumbnail
本書大多數的內容都以 OO 的概念出發,詳列了許多設計的臭味道,也有大量的例子。個人雖然不會這樣寫程式,但仍是覺得受益良多,至少在 code review 時能更清楚知道該怎麼描述問題。不過,即便不是用 OO 的概念,有些章節還是可以帶來一些想法,用 OO 概念寫程式的人更不該錯過這本好書。
Thumbnail
本書大多數的內容都以 OO 的概念出發,詳列了許多設計的臭味道,也有大量的例子。個人雖然不會這樣寫程式,但仍是覺得受益良多,至少在 code review 時能更清楚知道該怎麼描述問題。不過,即便不是用 OO 的概念,有些章節還是可以帶來一些想法,用 OO 概念寫程式的人更不該錯過這本好書。
Thumbnail
本書介紹了戰略設計、管理領域複雜度、實際應用領域驅動設計等主題。透過對核心子領域、支持子領域、限界上下文等概念的探討,提供了領域驅動設計的相關知識。這篇文章中還涉及了微服務、事件驅動架構和資料網格等相關主題,提供了設計系統和應用領域驅動設計的指導。
Thumbnail
本書介紹了戰略設計、管理領域複雜度、實際應用領域驅動設計等主題。透過對核心子領域、支持子領域、限界上下文等概念的探討,提供了領域驅動設計的相關知識。這篇文章中還涉及了微服務、事件驅動架構和資料網格等相關主題,提供了設計系統和應用領域驅動設計的指導。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
這本書從 docker 的角度出發,介紹很多可重複使用的 pattern,除了翻譯某些地方有點怪之外,算是很有趣的一本書,後面很多的 pattern 可以想成是 sidecar 的進階使用方式,在不改變應用程式的情況下,增加不同的功能,相當實用。
Thumbnail
這本書從 docker 的角度出發,介紹很多可重複使用的 pattern,除了翻譯某些地方有點怪之外,算是很有趣的一本書,後面很多的 pattern 可以想成是 sidecar 的進階使用方式,在不改變應用程式的情況下,增加不同的功能,相當實用。
Thumbnail
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
程式設計中不可或缺的一部分 介面是使用者與程式互動的媒介,因此介面的設計會影響使用者的體驗和感受。一個清晰明白、易懂的介面,可以讓使用者輕鬆地使用程式,並獲得良好的使用體驗。 需要與程式設計師密切溝通 設計師需要了解程式的功能和需求,並根據使用者的習慣和需求進行設計。設計師和程式設計師之間的溝
Thumbnail
程式設計中不可或缺的一部分 介面是使用者與程式互動的媒介,因此介面的設計會影響使用者的體驗和感受。一個清晰明白、易懂的介面,可以讓使用者輕鬆地使用程式,並獲得良好的使用體驗。 需要與程式設計師密切溝通 設計師需要了解程式的功能和需求,並根據使用者的習慣和需求進行設計。設計師和程式設計師之間的溝
Thumbnail
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News