將DDD應用到資料庫設計中 - lazypro
本文將介紹如何將域驅動設計和資料庫組合在一起的另一個示例。接下來,我們將提供一個帶有 MySQL 資料庫的普通街頭gashapon(扭蛋娃娃機)店的真實設計。
使用者故事
正如我們之前所做的那樣,我們從描述使用者故事開始,並通過該故事瞭解我們的需求。
- 會有很多機器,每臺機器都有不同的物品組合。
- 為了讓“慷慨”的客戶一次購買所有物品,我們提供了一次抽取大量物品的選項。
- 當我們用完物品時,我們需要立即補充它們。
- 如果一個人一次畫了很多物品,如果物品用完了,我們會立即補充物品,以便他們繼續畫畫。
這樣的故事其實是每個扭蛋娃娃機店都會發生的場景,描述清楚後,我們就知道如何構建領域模型了。
一般來說,我們需要對扭蛋機和扭蛋實體進行建模。
用例
然後,我們根據使用者故事定義更精確的用例。與故事不同,用例將清楚地描述發生了什麼以及系統應該如何反應。
- 因為這是一家線上扭蛋店,所以可以多人同時抽獎。但就像實體機器一樣,每個人都必須按順序繪製。
- 在加卡的過程中,使用者必須等到所有的加卡都加滿後才能進行抽獎。
- 當A和B各同時抽50,但機器裡只有70時,其中一個會得到批次中的50,而另一個先抽20,然後等到補完再抽30。
有了用例,我們既可以畫流程圖,也可以根據它編寫流程。在此示例中,我選擇將流程編寫Python如下。
def draw(n, machine): gachas = machine.pop(n) if len(gachas) < n: machine.refill() gachas += draw(n - len(gachas), machine) return gachas |
在使用者故事中,我們提到我們將對扭蛋機和扭蛋進行建模。所以machine在這個例子中的程式碼是扭蛋機,它提供了pop和refill方法。另一方面,gachas 是一個列表gacha。
在我們進一步討論之前,必須說明一件非常重要的事情。pop和方法都refill必須是原子的。為了避免競速條件,這兩種方法都必須是原子的,不能被搶佔,同時會有多個使用者同時繪製。
資料庫建模
我們已經有了machine和gacha,這兩個物件對於熟悉物件導向程式設計的開發者來說是非常簡單的,但是它們應該如何與資料庫整合呢?
正如在《企業應用程式架構模式》一書中提到的,有三個選項可以描述資料庫上的域邏輯。
- 事務指令碼
- 表模組
- 領域模型
這本書分別解釋了這三種選擇的優缺點,根據我的經驗,我更喜歡使用Table Module。原因是,資料庫是一個獨立的元件,而對於應用程式來說,資料庫實際上是一個Singleton。為了能夠控制對這個Singleton的併發訪問,必須有一個單一的、統一的和公共的介面。
使用Transaction Script,對資料庫的訪問分佈在整個原始碼中,當應用程式變大時幾乎無法管理。另一方面,Domain Model過於複雜,因為它為表的每一行建立一個特定的例項,這在實現原子操作方面非常複雜。所以我選擇了折衷表模組作為與資料庫互動的公共介面。
以上述machine為例。
由於我們已經完成了域物件的構建,讓我們定義表的模式GachaTable。
在表格中,我們可以看到有兩臺機器,一臺以侏羅紀公園Jurassic Park為主題,另一臺以冰河世紀Ice Age為主題,兩者都有兩個單獨的扭蛋。侏羅紀公園的機器看起來像三個,但實際上已經繪製了一個。
Gacha的領域模型直截了當,gacha_id或者更詳細,可能是主題加專案,比如:侏羅紀公園和霸王龍。
一個更有趣的話題是machine. machine需要定義幾個屬性。首先,它應該可以在建構函式中指定一個 id,其次,有兩個原子方法,pop和refill. 我們將在以下部分重點介紹這兩種方法。
原子性的POP
實現一個原子性彈出哇哇POP並不難:先按序號排序,然後取出第一n行,最後設定is_drawn為true. 我們以侏羅紀公園機器為例。
START TRANSACTION; SELECT * FROM GachaTable WHERE machine_id = 1 AND is_drawn = false ORDER BY gacha_seq LIMIT n FOR UPDATE; UPDATE GachaTable SET is_drawn = true WHERE gacha_seq IN ${resultSeqs}; COMMIT; |
為了避免丟失更新,MySQL 提供了三種方法。在這個例子中,要實現原子更新,最簡單的方法是新增FOR UPDATE到末尾SELECT以搶佔這些行。
更新完成後,SELECT可以將 的結果包裝到Gacha例項中並返回。這樣,呼叫者將能夠獲得抽出的扭蛋並知道抽出了多少個。
原子性Refill
另一種原子方法是refill. 在填充過程中不被打斷很簡單。COMMIT因為只有在條件下,其餘客戶端才會讀取 MySQL 事務Repeatable Read。
START TRANSACTION; for gacha in newGachaPackage(): INSERT INTO GachaTable VALUES ${gacha}; COMMIT;
這就是全部?不,不是。
當兩個使用者都畫n,但沒有足夠的n扭蛋來draw時,就會出現這個問題。
上面的順序圖顯示,當A和B同時抽籤時,A會抽到r個gachas,而B不會,正如我們預期的那樣。然而,A和B會一起refill,導致同一批嘎查被refill兩次。
通常情況下,這不會造成任何重大問題。因為我們按序列號排列流行,我們可以保證第二批只有在第一批被抽乾後才會被抽出。但是如果我們想改變專案,那麼新的專案就會比我們預期的晚放出來。
另一方面,兩個人同時refill,那麼冗餘度就變成了兩倍,如果系統同時有非常多的使用者,那麼冗餘度可能會變成幾倍,佔用大量的資源。
如何解決這個問題?在《解決MySQL中的幻象讀取》一文中提到,我們可以將衝突具體化。換句話說,我們新增一個外部同步機制來調解所有併發的使用者。
在此示例中,我們可以新增一個新表MachineTable.
此表還允許原始machine_id的GachaTable具有附加的外來鍵引用目標。當我們這樣做時refill,我們必須先鎖定這臺機器,然後才能對其進行更新。
START TRANSACTION; SELECT * FROM MachineTable WHERE machine_id = 1 FOR UPDATE; SELECT COUNT(*) FROM GachaTable WHERE machine_id = 1 AND is_drawn = false; if !cnt: for gacha in newGachaPackage(): INSERT INTO GachaTable VALUES ${gacha}; COMMIT; |
首先,我們獲得獨佔鎖,然後重新確認GachaTable是否需要重新refill,最後,我們實際將資料插入其中。如果沒有重新確認,那麼仍有可能重複refill。
這裡有一些擴充套件的討論。
- 為什麼我們需要一個額外的MachineTable?我們不能鎖定原來的GachaTable嗎?由於幻象讀取,MySQL的可重複讀取不能避免在新資料情況下的幻象讀取帶來的寫偏移。
- 鎖定MachaTable時,從GachaTable獲取計數時不需要鎖定GachaTable嗎?實際上,這是沒有必要的。因為它會進入補給過程,一定是因為Gacha已經被抽走了,大家都在等待補給,所以不用擔心彈出的問題。
結論
在這篇文章中,我們通過一個真實的例子來解釋在將領域驅動設計與資料庫設計相結合時需要考慮的問題。
最終的結果將包含兩個物件,Gacha和Machine,而資料庫也將包含兩個表,GachaTable和MachineTable。在Machine中的所有方法在本質上都是原子性的。
正如我們在之前的設計步驟中所描述的,我們需要首先定義正確的使用者故事和用例,然後開始建模,最後是實現。與普通的應用程式實現不同,資料庫是作為一個大的Singleton存在的,所以我們需要將資料庫的設計也更好地整合到我們的領域設計中。
為了儘量減少資料庫對整個設計的影響,正確建立領域模型是至關重要的。當然,在這篇文章中,我們採用了表模組的方法來進行領域設計,這有它的優點和缺點。
優點是通過使用Machine領域模型,我們能夠模擬出扭蛋娃娃機Machine的真實外觀,併為所有使用者提供一個共同的介面來同步處理。通過將娃娃機行為封裝到Machine中,未來的娃娃機擴充套件可以很容易地進行。GachaTable和MachineTable的所有操作也將由一個單一物件控制。
缺點是Machine裡面實際上包含了娃娃機機器和gacha表,對於嚴格的物件導向的宗教來說,這太粗糙了。當更多的人蔘與到專案中時,每個人對物件和資料表的理解開始出現分歧,導致設計崩潰。對於一個大型組織來說,表模組有它的範圍,更好的跨部門協作依賴於完整的文件和設計審查,這會影響每個人的工作效率。
相關文章
- 如何將斐波那契數列應用到排版設計中
- 【資料庫設計】資料庫的設計資料庫
- 設計師必看!如何將色彩正確運用到遊戲中?遊戲
- 設計模式應用場景之Model設計中可以用到的設計模式設計模式
- Go Web 程式設計--應用資料庫GoWeb程式設計資料庫
- 也談應用之“基石”——資料庫設計資料庫
- 資料庫設計中的敏捷方法 (轉)資料庫敏捷
- 資料庫設計資料庫
- 常用到的Linux程式設計庫Linux程式設計
- OKR如何應用到產品設計? - RedditOKR
- 領域驅動設計DDD應用心得
- 資料庫設計中的14個常用技巧資料庫
- Go Web 程式設計入門--應用資料庫GoWeb程式設計資料庫
- 資料庫設計技巧資料庫
- 資料庫表設計資料庫
- 資料庫原理-設計資料庫
- 資料庫設計(1)資料庫
- KMC資料庫設計資料庫
- mysql導資料庫用到的語句MySql資料庫
- DDD如何介入資料庫思維繫統?資料庫
- 反DDD模式之關係型資料庫模式資料庫
- 資料庫實驗八 資料庫程式設計資料庫程式設計
- 資料庫實驗五:資料庫程式設計資料庫程式設計
- pandas 將函式應用到列(qbit)函式
- 資料庫設計中使用設計模式資料庫設計模式
- 將MYSQL資料顯示在QT的tablewidget中/將QT中的資料儲存到MYSQL資料庫中MySqlQT資料庫
- 資料庫在資料分析中如何應用資料庫
- 探秘資料庫中的平行計算技術應用資料庫
- 資料庫設計---即資料庫架構設計的幾個步驟資料庫架構
- 從百度應用到設計師的位置
- 資料庫設計之思考資料庫
- 資料庫設計總結資料庫
- IM 的資料庫設計資料庫
- PowerDesigner設計資料庫資料庫
- 資料庫設計規範資料庫
- 資料庫設計的流程資料庫
- 資料庫設計例項資料庫
- ERP 資料庫設計資料庫