領域驅動設計 -DDD- 之實踐

  1. 簡介

領域驅動設計是一個應對複雜應用系統的設計方法,它通過一系列從粗到細粒度的邏輯邊界劃分,從而創建系列的高內聚的領域模型,並使用與領域模型一致性的代碼實現。最終,高複雜度的應用系統被劃分爲一個個小的低複雜度服務 / 功能 / 任務。後續文章不按照常見的戰略設計 + 戰術設計實現,只按照自己的理解來展開。

  1. 基本概念

領域驅動設計核心是利用業務概念創建領域模型對象最終完成系統設計,而不是數據存儲出發設計系統。業務概念的來源主要是用戶故事中各種業務術語。下述概念的粒度由粗到細,例如一個上下文邊界中包含一個及以上的應用服務。

2.1 上下文邊界

在有界上下文中,領域對象纔會具有確定的語義,保證沒有二義性。劃分好有界上下文,就可以知道領域對象應該放在哪個上下文中實現。實際上,微服務的拆分和設計是基於有界上下文,一個上下文對應一個微服務,但是防止過度拆分帶來的維護成本激增,往往會將多個上下文邊界作爲一個服務管理。例如,在電商領域,一個 “商品” 在不同的上下文中可能有不同的含義和屬性。在銷售上下文中,它可能包括價格、促銷信息等屬性;而在庫存管理的上下文中,它可能包含庫存數量、存儲位置等屬性。通過定義這兩個不同的上下文邊界,我們可以清晰地區分 “銷售商品” 和“庫存商品”,避免因概念混淆而導致的業務邏輯錯誤。

2.1.1 應用服務

應用服務通常以業務用例爲粒度,每個服務對應一個獨立的業務用例。應用服務主要負責對服務內部的領域服務編排。 假設我們有一個電子商務平臺,該平臺有一個功能是 “用戶提交訂單”。這個業務用例可以由一個名爲OrderSubmissionService的應用服務來實現。這個服務會處理用戶提交訂單的整個流程,包括驗證用戶輸入的數據、創建訂單、計算訂單總額、檢查庫存、記錄交易日誌等。在這個過程中,OrderSubmissionService會調用領域層中的多個聚合根和實體來完成這些任務,例如調用Order聚合根的placeOrder方法,以及Product實體的decrementStock方法。

2.1.1.1 聚合

聚合由一個聚合根、多個實體、多個領域服務、多個領域事件以及一個倉儲實現,這些對象在業務上高度關聯,並且作爲一個整體被統一管理,具有強一致性。聚合內實現高內聚的業務邏輯,如果特別複雜,則它的代碼可以獨立拆分爲微服務。

聚合根

聚合根也是一個實體,但是封裝了所在聚合內的所有領域對象的管理,並維護聚合內的強一致性。 聚合軟件包的根目錄,可以根據實際項目的聚合名稱命名,比如權限聚合。在聚合內定義聚合根、實體和值對象以及領域服務之間的關係和邊界。

領域事件

領域內產生的事件。可以關注用戶故事中,“當..., 則要...." 這種描述。

領域服務

負責編排聚合內實體和值對象,組合出一段業務邏輯,一個聚合可以只有一個領域服務類,如果比較複雜再考慮拆分。

倉儲

一個聚合對應一個倉儲,負責聚合的查詢和持久化。

實體

實體是最底層的領域對象之一,主要特徵是:

值對象

值對象也是最底層的領域對象之一,主要特徵是:

  1. 工程實現(or 分層架構)

服務內部代碼的組織使用分層架構來組織,主要分爲用戶接入層、應用層、領域層、基礎設施層來作爲上述領域對象的載體。

3.1 用戶接入層

負責將用戶輸入轉換爲領域對象,並調用應用服務產生結果,將結果轉換爲展現數據。

dto

存放 web 接口 / soa 接口的入參和出參

assembler

存放 dto 與領域對象的轉換邏輯

facade

存放應用服務的編排代碼

3.2 應用層

event

負責事件的發佈和訂閱

publish

subscribe

service

負責存放應用服務的業務,使用 xxxCommand\xxxQuery\xxxEvent 明確意圖。

external

負責存放外部服務的接口

3.3 領域層

service

存放領域服務的邏輯,調用

entity

存放實體

valueObject

存放值對象

event

存放領域事件

倉儲

存放倉儲接口

3.4 基礎設施層

負責存放 rpc 調用、mp 配置以及事件訂閱 / 發佈接口的實現、倉儲接口的數據庫的實現以及緩存的實現等。

4 實踐

背景

用例 1:在一個設計工具中,家裝設計師可以打開方案,在方案中選中想要生成檯面的櫃子,然後選擇需要生成的檯面材質樣式,然後點擊一鍵生成檯面。同時,檯面材質和前後擋水樣式之間的約束關係有單獨的配置。生成的檯面塊要能夠恰好覆蓋櫃子表面。檯面塊和牆和櫃子重疊的部分會生成後擋水,其餘邊生成前擋水。如果用戶要求前擋水要內含,則櫃子表面的輪廓要包含前擋水;如果是外擴,則前擋水可以在櫃子外部。多個檯面如果相鄰等高,則需要合併一個檯面。檯面塊是平面板件模型,而前後擋水則是掃掠模型。櫃子和牆形成的閉合縫隙,如果面積小於 100 平米釐米則需要擴展臺面,補上縫隙。
用例 2: 生成好的檯面可以根據用戶的要求切割成多個檯面塊和多段前擋水和後擋水。
用例 3: 用戶選中方案中的櫃子,進入腳線生成環境,選擇腳線輪廓和材質樣式以及腳線高度,可以使用設計工具一鍵生成腳線,腳線是掃掠模型。同時,首尾相接的多段腳線可以合併爲一段。

任務

行動 && 結果

基礎設施層部分代碼展示:

@Repository
public class DrawerClearanceConfigRepoImpl implements DrawerClearanceConfigRepo {
    @Resource
    private DrawerClearanceConfigMapper drawerClearanceConfigMapper;
    @Resource
    private DrawerClearanceConfigRelationMapper drawerClearanceConfigRelationMapper;
    /**
     * 通過抽屜ID列表查找配置。
     *
     * @param drawerId 抽屜的唯一標識ID
     * @return 一個映射,將抽屜ID映射到它們的配置
     */
    @Override
    @Cacheable(value = "drawerClearanceConfigs"key = "#rootAccountId + '-' + #drawerId")
    public DrawerClearanceConfig findConfigsByDrawerId(Long drawerId, long rootAccountId) {
        final List<DrawerClearanceConfigRelationPO> drawerClearanceConfigRelationPOList
                = drawerClearanceConfigRelationMapper.selectByDrawerBgIds(Collections.singletonList(drawerId), rootAccountId);
        if (CollectionUtils.isEmpty(drawerClearanceConfigRelationPOList)) {
            return null;
        }

        DrawerClearanceConfigRelationPO drawerClearanceConfigRelationPO = drawerClearanceConfigRelationPOList.get(0);
        DrawerClearanceConfigPO drawerClearanceConfigPO = drawerClearanceConfigMapper.selectById(drawerClearanceConfigRelationPO.getConfigId(), rootAccountId);
        return DrawerClearanceConfigConverter.toDrawerClearanceConfig(drawerClearanceConfigPO);
    }
    /**
     * 通過配置ID刪除一個配置。
     *
     * @param configId 配置的唯一標識ID
     */
    @Override
    @Transactional(transactionManager = "dcsModelTransactionManager"rollbackFor = {Exception.class}propagation = Propagation.REQUIRED)
    public void removeConfigById(long configId, long rootAccountId) {
        LOGGER.message("removeConfigById").with("configId", configId).with("rootAccountId", rootAccountId).info();
        drawerClearanceConfigMapper.deleteById(configId, rootAccountId);
        drawerClearanceConfigRelationMapper.deleteByConfigId(configId, rootAccountId);
    }
}

5 一些 BP

原文:https://juejin.cn/post/7404739357083697188

作者:AI 改變世界嗎

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/zjjXW7awneDppIaQ2DcC1w