解構領域驅動設計(一):爲何DDD可以解決軟件複雜性

1 爲何我要研究領域驅動設計數據庫

1.1 設計方法各樣且代碼沒法反映設計緩存

我大概從2017年10月份開始研究DDD,當時在一家物流信息化的公司任職架構師,研究DDD的初衷在於爲團隊尋找一種軟件設計的方法論。做爲架構師,常常參與設計評審,包括:需求評審、設計評審、代碼評審。在評審過程當中,有一點感覺很是深,就是評審過程很是痛苦且幾乎沒有效率和成果。讓我痛苦的地方有:架構

  • 每個系統分析師都是基於本身的方式來進行設計功能,有的用類圖、有的基於流程圖,有的詳細、有的粗放,更麻煩的是,你們對業務背景的理解程度徹底不一樣,認知不一樣,溝通低效,很難找出設計的不合理性。
  • 評審代碼時,我幾乎很難將其與設計對應起來,看設計我已經夠痛苦了,還要被這些代碼再虐待一遍,實在痛苦至極,這樣的代碼評審也就變成了代碼規範性、代碼設計優雅度的評審,很難找出代碼業務邏輯的問題。讓代碼正確的反應設計,是當時評審過程當中碰到的一個更大的問題。

1.2 代碼質量很難有效提高app

在承擔架構師以前,個人另外一個職責是技術管理,作的工做是與軟件質量相關的。當時加入一個大概2000萬規模的項目,有大約100開發人員參與,開發週期大概1年。加入該團隊在開發的過程當中,發現了兩個問題:編碼

  • 每個BA(能夠理解爲PD)設計的產品界面操做習慣都不同,全部的開發人員作出來的界面的操做也徹底不一樣。可是,這是一個面向物流行業的信息化軟件,操做習慣的一致性很重要。
  • 代碼很是混亂,沒有任何的規範可言,看代碼簡直想吐。

基於第一個問題,我定義了統一的界面規範,這個界面規範經過和公司的PMO合做將其融入到工程過程當中,做爲開發人員必須遵循的規範。第二個問題,我則花費了不少的時間來嘗試解決(大概有2年時間都與代碼質量作鬥爭),最終與尋找統一的設計方法異曲同工。spa

如何讓咱們的代碼變得更加乾淨,我在執行的過程當中,按照如下步驟一步一步的執行。設計

  • 定義了統一的代碼規範,基於界面規範的基礎上,統必定義了模板工程,這些模板工程都有很好的代碼基因。
  • 定義了代碼規範的培訓教程,包括基本的書寫規範、《代碼整潔之道》、《重構技巧》。
  • 定義了代碼規範、代碼評審制度,寫入PMO定義的過程工做,做爲開發人員遵循的制度。
  • 經過代碼評審提高質量太慢,爲了大規模快速推廣,引入了SonarQube,定義了軟件代碼質量的度量方法,軟件的代碼質量分數由:圈複雜度、重複率、代碼規模問題、SonarQube掃描的問題數四個維度來衡量。在度量方法之上,定義了代碼質量管理制度,每週掃描軟件得到詳細的代碼質量報告,發送給相應的產品負責人,將代碼質量管理制度也融入PMO的工程過程裏面,全公司進行推廣,由產品負責人負責本部分的代碼質量提高。

基於以上的代碼質量管理方法,我認爲已是作的至關不錯,可是很是遺憾的是,當我抽樣評審產品的代碼時,我依然感到無比沮喪,軟件的代碼仍是太複雜、太難看懂了,與《代碼整潔之道》的要求相差太遠了,我耗費了1年多的工做幾乎毫無成果可言。所以,我在深深思考,在編碼層面,定義了規範、作了優雅編碼培訓、定義了編寫優秀代碼的相關制度,就爲了讓開發人員把代碼寫好,使代碼看起來更加清晰,軟件更加容易維護,爲何仍是沒法實現?代碼規範

2 軟件複雜性的根源code

貧血模型是軟件複雜性的根源。貧血模型本質是面向數據的設計,面向過程的編碼。基於貧血模型的分層架構,一般分爲UI層、業務邏輯層、數據訪問層、貧血模型層,貧血模型與數據模型一致。對象

業務規則是軟件最核心的代碼,一般只佔整個軟件很小的一部分。在基於貧血模型的架構中,業務規則的實現,一般混雜在上層UI展示邏輯、數據庫訪問、緩存等各類邏輯中,分散在各個層和關聯對象。經過閱讀業務邏輯層的代碼來還原真實的業務規則很困難,很難從代碼反映其業務規則設計,而且隨着軟件需求變動,業務規則更加難以還原,軟件複雜度將不可控。

如下是一段業務邏輯層的實現代碼。

public OrderDto signOrder(Order order) {
    Assert.notNull(order, "OrderDto can not be null.");
    OrderDto result = new OrderDto();
    result.setIsOperationSuccess(true);
    if (null == order.getId()) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("id不能爲空。");
        return result;
    }
    OrderCondition orderCondition = new OrderCondition();
    orderCondition.setId(order.getId());
    order = orderMapper.selectOne(orderCondition);
    if (null == order) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("該訂單不存在。");
        return result;
    }
    if (order.getOrderStatus() != Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_WAIT_RECEIVE.getCode())) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("訂單號:{" + order.getOrderNo() + "}不是待收貨狀態,不能進行簽收。");
        return result;
    }
    // 該訂單下的全部商品的實收數(發貨數量)必須都大於0
    boolean validDeliveryCount = true;
    Double orderTotalAmount = 0d;
    List<OrderGoodsDto> orderGoodsList = orderGoodsBiz.selectOrderGoodsByOrderId(order.getId());
    List<OrderGoods> orderGoodsListForUpdate = new ArrayList<>();
    if (EmptyUtil.isNotEmpty(orderGoodsList)) {
        for (OrderGoodsDto orderGoods : orderGoodsList) {
            if (null == orderGoods.getDeliveredNum() || orderGoods.getDeliveredNum() <= 0) {
                validDeliveryCount = false;
            } else {
                // 根據商品發貨數量從新計算訂單總金額......
                Double price = (null == orderGoods.getDiscountPrice() ? orderGoods.getOriginalPrice() : orderGoods.getDiscountPrice());
                Integer goodsNum = (null == orderGoods.getDeliveredNum() ? 0 : orderGoods.getDeliveredNum());
                orderTotalAmount += price * goodsNum;
                // 更新orderGoods的收貨數量
                orderGoods.setReceivedNum(goodsNum);
                OrderGoods orderGoodsForUpdate = new OrderGoods();
                BeanUtils.copyProperties(orderGoods, orderGoodsForUpdate);
                orderGoodsListForUpdate.add(orderGoodsForUpdate);
            }
        }
    }
    if (!validDeliveryCount) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("訂單號:" + order.getOrderNo() + ",訂單下全部商品都已發貨纔可進行簽收操做,請確認。");
        return result;
    }
    order.setOrderStatus(Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_SIGN.getCode()));
    order.setOrderTotalAmount(orderTotalAmount);
    order.setPaymentAmount(orderTotalAmount);
    order.setUnpaidAmount(orderTotalAmount);
    update(order);
    orderGoodsBiz.batchUpdate(orderGoodsListForUpdate);
    List<Order> orders = new ArrayList<Order>();
    orders.add(order);
    saveRouteMessage(orders);
    return result;
}

 

相似這樣的代碼很是常見,經過閱讀這段業務邏輯代碼,能夠發現它處理了如下的任務:
(1)返回結果的處理。
(2)數據庫訪問。
(3)關聯對象的數據庫訪問。
(4)業務規則。

業務規則代碼與數據庫訪問、關聯對象數據庫訪問、結果處理等其它邏輯在一塊兒實現,經過代碼還原業務規則會愈來愈複雜且隨着時間推移,代碼邏輯會愈來愈偏離設計。做爲軟件系統最核心的部分——業務規則,若是咱們僅僅將其從其它任務中剝離,咱們的代碼將演化以下。注:如下代碼僅演示剝離出來業務邏輯,並不是DDD推薦方式,下篇介紹。 

public void signOrder(Order order) {
    assertCanBeSigned(order);

    Double orderTotalAmount = 0d;

    List<OrderGoods> orderGoodsList = order.getOrderGoods();
    for (OrderGoods orderGoods : orderGoodsList) {
        Double price = (null == orderGoods.getDiscountPrice() ? orderGoods.getOriginalPrice() : orderGoods.getDiscountPrice());
        Integer goodsNum = (null == orderGoods.getDeliveredNum() ? 0 : orderGoods.getDeliveredNum());
        orderTotalAmount += price * goodsNum;

        orderGoods.setReceivedNum(goodsNum);
    }

    order.setOrderStatus(Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_SIGN.getCode()));
    order.setOrderTotalAmount(orderTotalAmount);
    order.setPaymentAmount(orderTotalAmount);
    order.setUnpaidAmount(orderTotalAmount);
}

public void assertCanBeSigned(Order order) {
    Assert.notNull(order, "OrderDto can not be null.");

    if (order.getOrderStatus() != Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_WAIT_RECEIVE.getCode())) {
        throw new BusinessException("訂單號:{" + order.getOrderNo() + "}不是待收貨狀態,不能進行簽收。");
    }

    List<OrderGoods> orderGoodsList = order.getOrderGoods();
    if (!EmptyUtil.isNotEmpty(orderGoodsList)) {
        throw new BusinessException("訂單號:" + order.getOrderNo() + ",訂單沒有包含商品,是一個空的訂單,沒法簽收。");
    }

    for (OrderGoods orderGoods : orderGoodsList) {
        // 該訂單下的全部商品的實收數(發貨數量)必須都大於0
        if (null == orderGoods.getDeliveredNum() || orderGoods.getDeliveredNum() <= 0) {
            throw new BusinessException("訂單號:" + order.getOrderNo() + ",訂單下全部商品都已發貨纔可進行簽收操做,請確認。");
        }
    }
}

 

這段代碼反映的業務規則是訂單簽收規則。

(1)若是訂單不是待發貨狀態,不能簽收;
(2)校驗訂單下全部商品的發貨數量都要大於0;
(3)計算訂單總金額,並設置收貨數量爲發貨數量;
(4)設置簽收狀態、總金額、支付金額和未付金額。

你能夠發現這段單純實現業務規則的代碼,會更加的簡單、清晰,也會使軟件更加的容易維護。在DDD的方法論裏面,業務規則是在領域層來實現的,領域層的代碼僅僅是業務規則,這時候,其分層架構的分層邏輯和基於貧血模型的分層邏輯也會不同了。

經過以上代碼的對比咱們發現:

  • 剝離業務規則無關的代碼,將更加清晰簡單,容易和業務規則保持一致。
  • 貧血模型會致使業務邏輯層混雜了太多代碼和邏輯,難以還原業務規則,保證代碼與設計一致性,是複雜性根源。

3 DDD如何解決軟件複雜性

DDD解決軟件複雜性的方法核心爲兩點:

  • 經過領域模型爲業務知識建模,領域模型做爲業務、技術團隊溝通的統一語言。
  • 確保軟件實現與領域模型保持一致。

軟件實現與領域模型保持一致是本書的核心思想,DDD構建了一套完整的方法論來支持領域模型驅動程序設計。這套方法論簡述以下。

  • 分層架構:業務規則的代碼只佔軟件不多的代碼倒是最核心的部分代碼,將其分離出來做爲獨立的領域層,使領域層的實現與領域模型保持一致,領域層的業務對象再也不是貧血模型。
  • 領域驅動設計:領域驅動設計,即領域模型驅動程序設計。這裏給出瞭如何經過代碼表達領域模型的編碼模式。這些模式包括:關聯、實體、值對象、服務、聚合根、Repository、Factory。它們構建了將領域模型表達成代碼的方法論,保證了代碼和設計一致。
  • 戰略設計:複雜領域模型的實現方法論。

我將在下一篇文章中詳細解釋DDD的核心思想,讓你明白它是如何解決複雜性的。

相關文章
相關標籤/搜索