閒談軟體設計: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
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
起源是當時 Facebook 有篇文章討論不少人分不清楚上述二者的差別,當時寫了首部曲《閒談軟體設計:API Naming Style》,接著是《閒談軟體設計:內部函式庫》,但始終沒談到 library 和 framework 的差別,主要是沒有好的例子,這次這例子還蠻不錯的。
我自己偏好用 Repository 搭配 decorator 來管理 cache,而不是在 controller 層或是到處都有快取的邏輯,如果程式都是透過 Repository 更新資料,Repository 就會是一個不錯的地方更新快取,邏輯也就不會散亂在各處了。
提到後端工程師,似乎就只是開發 API,但一個複雜的系統其實不太可能只透過 API 就能完成,例如一個簡單的功能,註冊會員,其實是由好幾個不同類型的工作互相配合,您才能收到開通信,才確保資料庫不會有一堆未開通帳號等。所以今天就來聊聊一個系統有幾種不同執行方式的工作。
最近隨著 FP 的流行,immutability 一直被提倡,物件有狀態,會被修改好像是一種惡,但真是如此?immutability 很好,但所謂的狀態就是會隨著操作變動,差別只在於變動發生在哪裡?
針對這議題,從 devOps 的角度看,團隊應抱有持續不斷地改進的精神,努力降低上版的風險,最後,哪一天上版就僅僅是風險控管的問題了。風險控管除了考量到損失,當然還要考慮到團隊要怎麼 on-call,on-call 的資源夠不夠應付上版後的突發狀況,才能做出適當的決策。
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
起源是當時 Facebook 有篇文章討論不少人分不清楚上述二者的差別,當時寫了首部曲《閒談軟體設計:API Naming Style》,接著是《閒談軟體設計:內部函式庫》,但始終沒談到 library 和 framework 的差別,主要是沒有好的例子,這次這例子還蠻不錯的。
我自己偏好用 Repository 搭配 decorator 來管理 cache,而不是在 controller 層或是到處都有快取的邏輯,如果程式都是透過 Repository 更新資料,Repository 就會是一個不錯的地方更新快取,邏輯也就不會散亂在各處了。
提到後端工程師,似乎就只是開發 API,但一個複雜的系統其實不太可能只透過 API 就能完成,例如一個簡單的功能,註冊會員,其實是由好幾個不同類型的工作互相配合,您才能收到開通信,才確保資料庫不會有一堆未開通帳號等。所以今天就來聊聊一個系統有幾種不同執行方式的工作。
最近隨著 FP 的流行,immutability 一直被提倡,物件有狀態,會被修改好像是一種惡,但真是如此?immutability 很好,但所謂的狀態就是會隨著操作變動,差別只在於變動發生在哪裡?
針對這議題,從 devOps 的角度看,團隊應抱有持續不斷地改進的精神,努力降低上版的風險,最後,哪一天上版就僅僅是風險控管的問題了。風險控管除了考量到損失,當然還要考慮到團隊要怎麼 on-call,on-call 的資源夠不夠應付上版後的突發狀況,才能做出適當的決策。
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
動態規劃Dynamic Programming其實是 一種泛用的演算法思考方式與演算法建構框架。 動態規劃並不拘束於只能解課本上特定的的範例題。 只要我們能找出DP狀態定義、DP遞迴結構、初始條件(終止條件),就能適用動態規劃來解題,以數學的形式表達,並且在紙筆上或者電腦上、計算機上計算
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
程式設計中不可或缺的一部分 介面是使用者與程式互動的媒介,因此介面的設計會影響使用者的體驗和感受。一個清晰明白、易懂的介面,可以讓使用者輕鬆地使用程式,並獲得良好的使用體驗。 需要與程式設計師密切溝通 設計師需要了解程式的功能和需求,並根據使用者的習慣和需求進行設計。設計師和程式設計師之間的溝
Thumbnail
系統的分析與規劃 在談到程式設計時,首要的是進行系統的分析與規劃。程式設計的起點通常是系統分析與規劃,這涉及到如何分析和設計系統的大原則和方向。為了達到預期效果,重要的是擁有對產業的清晰邏輯認識和深入了解。 進行深入了解 若要進行系統分析,必須對企業的設計和程式設計的對象進行深入了解,以充分理
Thumbnail
替產業做設計 有人要我談程式設計,那我就稍微談一下。我從事的大都是產業的工作,所以我們也從如何替產業做設計來談起。基本上,每個產業都會有自己的作業流程,大同小異。但是基礎來做都是一樣的,都會有客戶、物料、產品、供應商、員工等資料。不同的是,由於企業型態的不同,他們每個人有不同的作業流程。這個作業流
1.5 Date Techonlogy DT時代的特徵是體驗,體驗就是感受。顧客要的不是服務,顧客要的是體驗。 2.4 在大數據時代,企業必須運用DT技術從資料收集至中探索巨大的價值,因為它能夠讓企業比顧客更懂顧客。DT技術運用方式 一、自動預測趨勢和行為 二、關聯性分析 三、分群
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
動態規劃Dynamic Programming其實是 一種泛用的演算法思考方式與演算法建構框架。 動態規劃並不拘束於只能解課本上特定的的範例題。 只要我們能找出DP狀態定義、DP遞迴結構、初始條件(終止條件),就能適用動態規劃來解題,以數學的形式表達,並且在紙筆上或者電腦上、計算機上計算
Thumbnail
軟體系統的發展歷程大多相似,首重解決基本需求、提供操作介面,進而提升安全性、擴充功能、優化操作。
Thumbnail
程式設計中不可或缺的一部分 介面是使用者與程式互動的媒介,因此介面的設計會影響使用者的體驗和感受。一個清晰明白、易懂的介面,可以讓使用者輕鬆地使用程式,並獲得良好的使用體驗。 需要與程式設計師密切溝通 設計師需要了解程式的功能和需求,並根據使用者的習慣和需求進行設計。設計師和程式設計師之間的溝
Thumbnail
系統的分析與規劃 在談到程式設計時,首要的是進行系統的分析與規劃。程式設計的起點通常是系統分析與規劃,這涉及到如何分析和設計系統的大原則和方向。為了達到預期效果,重要的是擁有對產業的清晰邏輯認識和深入了解。 進行深入了解 若要進行系統分析,必須對企業的設計和程式設計的對象進行深入了解,以充分理
Thumbnail
替產業做設計 有人要我談程式設計,那我就稍微談一下。我從事的大都是產業的工作,所以我們也從如何替產業做設計來談起。基本上,每個產業都會有自己的作業流程,大同小異。但是基礎來做都是一樣的,都會有客戶、物料、產品、供應商、員工等資料。不同的是,由於企業型態的不同,他們每個人有不同的作業流程。這個作業流
1.5 Date Techonlogy DT時代的特徵是體驗,體驗就是感受。顧客要的不是服務,顧客要的是體驗。 2.4 在大數據時代,企業必須運用DT技術從資料收集至中探索巨大的價值,因為它能夠讓企業比顧客更懂顧客。DT技術運用方式 一、自動預測趨勢和行為 二、關聯性分析 三、分群