1. 程式人生 > >解構領域驅動設計(三):領域驅動設計

解構領域驅動設計(三):領域驅動設計

ddd 引擎 .get states 成員變量 float 類的屬性 table custom

在上一部分,分層架構的目的是為了將業務規則剝離出來在單獨的領域層中進行實現。再回顧一下領域驅動設計的分層中應用層代碼的實現。

@Override
public void pay(int orderId, float amount) {
    DesignerOrder order = designerOrderRepository.selectByKey(orderId);   // 領域對象的加載
    if (order == null) {
        AppException.throwAppException(AppExceptionMessage.DESIGNER_ORDER_NOT_EXIST_CODE, AppExceptionMessage.DESIGNER_ORDER_NOT_EXIST, orderId);
    }
    
    order.pay(amount);    
// 領域對象業務規則實現 designerOrderRepository.update(order); // 領域對象狀態持久化 }

所有的業務規則都抽象到領域對象,比如“order.pay(amount)”抽象了付款的業務規則。領域對象由狀態(對象的字段、屬性)和操作(對象的方法)構成,領域對象的操作用於實現業務規則,業務規則執行完成後更改領域對象的狀態。領域對象的持久化交給了基礎設施層,這裏,Repository目的是持久化領域對象狀態。

領域驅動設計,即領域模型驅動程序設計,它的核心是保證系統的實現與實際的業務規則一致,完整實現了領域模型。它包含了兩個部分:領域模型、領域模型的編程實現。

在軟件設計和實現過程中要充分利用領域模型,設計過程中,領域模型作為與業務專家的溝通語言;實現過程中,領域模型作為與開發人員溝通的語言。領域模型在軟件生命周期過程作為通用語言。

1 領域模型

領域建模(這裏不重點介紹如何建模)方法論產出領域模型。我們可以使用UML建模,使用最簡單、最容易理解的名詞-形容詞-動詞法對領域知識進行建模,使用該模型作為與業務、技術團隊溝通的通用語言。

在名詞-形容詞-動詞法建模方法中,領域知識中的名詞一般對應模型、形容詞對應模型屬性、動詞對應模型方法。模型之間的關系有:組合、聚合、關聯、依賴,四者關系由強到弱。

依賴(Dependency)關系是類與類之間的聯接。依賴關系表示一個類依賴於另一個類的定義。一般而言,依賴關系在Java語言中體現為局域變量、方法的形參,或者對靜態方法的調用。

關聯(Association)關系是類與類之間的聯接,它使一個類知道另一個類的屬性和方法。關聯可以是雙向的,也可以是單向的。在Java語言中,關聯關系一般使用成員變量來實現。

聚合(Aggregation) 關系是關聯關系的一種,是強的關聯關系。聚合是整體和個體之間的關系。例如,汽車類與引擎類、輪胎類,以及其它的零件類之間的關系便整體和個體的關系。與關聯關系一樣,聚合關系也是通過實例變量實現的。但是關聯關系所涉及的兩個類是處在同一層次上的,而在聚合關系中,兩個類是處在不平等層次上的,一個代表整體,另一個代表部分。

組合(Composition) 關系是關聯關系的一種,是比聚合關系強的關系。它要求普通的聚合關系中代表整體的對象負責代表部分對象的生命周期,組合關系是不能共享的。代表整體的對象需要負責保持部分對象和存活,在一些情況下將負責代表部分的對象湮滅掉。代表整體的對象可以將代表部分的對象傳遞給另一個對象,由後者負責此對象的生命周期。換言之,代表部分的對象在每一個時刻只能與一個對象發生組合關系,由後者排他地負責生命周期。部分和整體的生命周期一樣。

簡而言之,組合關系表示部分與整體關系,部分不能單獨存在;聚合關系表示稍弱的部分與整體關系,部分可以單獨存在;關聯關系是一個模型和另一個模型的聯接,比如一個訂單有一個顧客而一個顧客有多個訂單;依賴是最弱的關系,表示一個模型的實現使用到另一個模型的功能。

舉個例子,我們與業務專家溝通,梳理了如下業務知識,然後我們使用名詞-形容詞-動詞法來進行建模。

=====================
領域知識:裝修設計預約平臺
1 客戶通過系統預約設計師進行裝修設計,客戶只能預約一個設計師訂單,不能預約多個同時進行設計。
2 預約後,設計師上門進行量房,根據面積進行報價和預估設計時間。設計師訂單按照4個節點預估交付時間,在不同節點交付不同成果,這四個節點分別為平面圖、效果圖、施工
圖、交底,四個節點的付款比率分別為10%、40%、40%、10%。
3 客戶接受報價方案後,進行付款,設計師開始設計;如果拒絕,則設計師可以進行再次報價和預估設計時間。
4 客戶在付款之前,都可以進行終止。
5 客戶付款後,正式進入設計階段。設計師按階段推進設計並按階段更新進度。在每一個階段,設計師完成任務後,客戶進行階段成果確認,客戶確定後所有階段後,訂單自動完成。
6 客戶可以對完成的訂單進行評價。
7 客戶對已付款但未完成的訂單可以提出退款申請,退款計算方法依據當前設計進度,如果當前進度已經達到設計師請求施工圖設計確認進度或超過該進度,則不允許退款。如果允許退款,退款金額最多為(總額 - 已完成的各階段付款之和),最少為未完成交付節點的待付款總額。
8 申請通過的退款訂單不再允許更新進度。
=====================

在這裏我們可以梳理出來的名詞有:客戶、設計師訂單、設計師、訂單交付進度與交付節點、退款訂單。
和設計師訂單有關的動詞有:量房、報價、接受(拒絕)報價、取消、付款、確認進度、退款、評價等。
設計師訂單有關的屬性有:訂單金額、支付金額、面積、取消原因、評價、狀態等。

因此,我們通過使用名詞-形容詞-動詞法構建的模型圖如下所示。

技術分享圖片

這裏,模型有:客戶Customer,設計師Designer,設計師訂單DesignerOrder,退款單RefundOrder,設計進度DesigningProgressReport,設計進度節點DesigningProgressNode。模型中組合關系為:設計進度DesigningProgressReport,設計進度節點DesigningProgressNode;其它模型之間的關系為關聯關系。

這個模型就作為軟件開發和維護過程的通用語言。接下來,我們將介紹如何來實現領域模型。

2 領域模型實現

在上一節,我們介紹了通過領域建模來構建了領域模型。接下來我們要介紹如何實現模型驅動程序設計,即我們如何通過代碼來實現領域模型對應的業務邏輯。領域模型的實現代碼在領域層,它完整實現了領域模型的內部結構和模型之間的關系。

領域模型的實現代碼由以下幾個部分構成:
• 領域模型關系的實現:組合、聚合、關聯、依賴。
• 領域模型的實現:實體和值對象。
• 跨領域模型的業務規則的實現:領域服務。

2.1 領域模型關系的實現

聚合、組合、關聯關系在實現上的表現基本上是一個類(或者類的標識)作為另一個類的屬性;而依賴關系則是一個類作為另一個類在方法的實現上的參數、變量,為另一個類提供功能實現。

下面我們簡單看一下如何通過編碼來實現類關聯關系,比如在模型上客戶和設計師訂單是關聯關系,一個客戶可以有多個設計師訂單,但是每一個設計師訂單只能有一個客戶和一個設計師並且最多只有一個退款訂單。

(1)聚合、組合、關聯
表現在一個類持有另一個類的引用,引用可以是實例的引用或者標識的引用,具體實現為屬性。這種關系是雙向關系,為了簡化編碼,可能只需要一方持有另一方的引用即可,這依賴於具體要實現的業務邏輯。如下代碼實現了DesignerOrder對設計師、進度報告的關系。

public class DesignerOrder implements Entity<DesignerOrder> {
    private int id;
    private int designerId;

    private DesigningProgressReport progressReport;
    ……

    public Designer getDesigner() {
        return designerRepository.getDesignerById(this.designerId);
    }

    public DesigningProgressReport getProgressReport() {
        return this.progressReport; 
    }

    ……
}

(2)依賴
依賴表現在一個類的實現使用到另一個類的功能,依賴的類可能作為方法的參數、方法局部變量或者靜態引用等。如下代碼體現了對DesignerOrderWorkflowService的功能依賴。

public class DesignerOrder implements Entity<DesignerOrder> {
    public void pay(float amount) {
        Assert.isTrue(amount > 0, "The amount must be bigger than 0.");

        if (!DesignerOrderWorkflowService.canChangeState(state, DesignerOrderState.PAID)) {
            BusinessException.throwException(DomainExceptionMessage.PAYMENT_NOT_IN_READY_STATE_CODE, DomainExceptionMessage.PAYMENT_NOT_IN_READY_STATE, this.id, this.state);
        }

        if (Math.abs(amount - this.expectedAmount) > 0.01) {
            BusinessException.throwException(DomainExceptionMessage.PAYMENT_NOT_MATCHED_CODE, DomainExceptionMessage.PAYMENT_NOT_MATCHED, this.id, this.expectedAmount, amount);
        }

        this.state = DesignerOrderWorkflowService.changeState(this.id, state, DesignerOrderState.PAID);
        this.actualPaidAmount = amount;

        // 付款完成後,自動啟動進度跟蹤
        this.progressReport.startup();
    }
}

2.2 領域模型的實現

領域模型在實現上表現為兩類:(1)實體(Entity):這個領域模型有特定的標識,但是其內部狀態會隨著一序列的事件(對應業務規則的執行)發生變化,我們把這類模型的實現稱為實體;(2)值對象(Value Object):這個領域模型由屬性來定義,實例創建後不會發生變更,變更也意味著重新創建一個實例,我們把這類模型的實現稱為值對象。

(1)實體
在裝修設計預約平臺的領域模型裏面,我們很容易可以發現設計師訂單就是一個實體,在創建後,每一個設計師訂單有一個唯一的訂單號,後續有量房、報價、付款、退款等系列動作的發生,從而訂單的內部狀態(字段值)會發生變化,但是都代表的是同一個訂單。每一個實體的實現都有一個標識。如下所示,這裏的id字段表示了訂單的唯一標識,並實現了Entity接口,Entity接口sameIdentityAs方法,判斷實體的Id是否相同。

實體的屬性和操作,對應著模型的狀態和狀態的變更,他們與模型的定義使一致的。

@Data
@EqualsAndHashCode(of = {"id"})
public class DesignerOrder implements Entity<DesignerOrder> {
    private int id;
    private DesignerOrderState state;
    private int customerId;
    private int designerId;
    private float area;

    private float expectedAmount;
    private int estimatedDays;
    private DesigningProgressReport progressReport;

    private String abortCause;

    private float actualPaidAmount;

    private int feedbackStar;
    private String feedbackDescription;

    private Date createdTime;
    private Date updatedTime;

    @Override
    public boolean sameIdentityAs(DesignerOrder other) {
        return this.equals(other);
    }

    public void measure(float area) {
        Assert.isTrue(area > 0, "The area must be bigger than 0.");

        this.state = DesignerOrderWorkflowService.changeState(this.id, state, DesignerOrderState.MEASURED);
        this.area = area;
    }

    public void quote(float amount, int[] estimatedDaysList) {
        Assert.isTrue(amount > 0, "The price must be bigger than 0.");
        this.assertEstimatedDaysList(estimatedDaysList);

        this.state = DesignerOrderWorkflowService.changeState(this.id, state, DesignerOrderState.QUOTED);
        this.expectedAmount = amount;
        this.progressReport = DesigningProgressReportFactory.newReport(this, estimatedDaysList);
        this.estimatedDays = this.progressReport.getEstimatedCompletionDays();
    }

    private void assertEstimatedDaysList(int[] estimatedDaysList) {
        if (null == estimatedDaysList || estimatedDaysList.length != 4) {
            throw new IllegalArgumentException("The size of estimatedDaysList must be 4.");
        }

        for (int days : estimatedDaysList) {
            if (days <= 0) {
                throw new IllegalArgumentException("Each element of estimatedDaysList must be bigger than 0.");
            }
        }
    }

    public void pay(float amount) {
        Assert.isTrue(amount > 0, "The amount must be bigger than 0.");

        if (!DesignerOrderWorkflowService.canChangeState(state, DesignerOrderState.PAID)) {
            BusinessException.throwException(DomainExceptionMessage.PAYMENT_NOT_IN_READY_STATE_CODE, DomainExceptionMessage.PAYMENT_NOT_IN_READY_STATE, this.id, this.state);
        }

        if (Math.abs(amount - this.expectedAmount) > 0.01) {
            BusinessException.throwException(DomainExceptionMessage.PAYMENT_NOT_MATCHED_CODE, DomainExceptionMessage.PAYMENT_NOT_MATCHED, this.id, this.expectedAmount, amount);
        }

        this.state = DesignerOrderWorkflowService.changeState(this.id, state, DesignerOrderState.PAID);
        this.actualPaidAmount = amount;

        // 付款完成後,自動啟動進度跟蹤
        this.progressReport.startup();
    }

    public RefundOrder refund(String cause) {
        this.assertCanRefund();

        this.state = DesignerOrderWorkflowService.changeState(this.id, state, DesignerOrderState.REFUND);

        return RefundOrderFactory.newRefundOrder(this, cause);
    }
}

DDD對於實體有一段重要描述:當一個對象由其標識而不是屬性區分時,那麽在模型中應該主要通過標識來確定該對象的定義。使類定義變得簡單,並集中關註生命周期的連續性和標識。定義一種區分每個對象的方式,這種方式應該與其形式和歷史無關。要格外註意那些需要通過屬性來匹配對象的需求。在定義標識操作時,要確保這種操作作為每個對象生成唯一的結果,這可以通過附加一個保證唯一性的符號來實現。這種定義標識的方法可能來自外部,也可能是由系統創建的任意標識符,但它在模型中必須是唯一的標識。模型必須定義出“符合什麽條件才算是相同的事務”。

(2)值對象
在貨物運輸系統中,當我們為一個貨物的運輸執行一條路線之後,那麽這條路線不能發生變更,我們傾向於把路由線路看做一個值對象。如下圖所示。對於值對象,通過屬性值即可標識。

public class RouteSpecification extends AbstractSpecification<Itinerary> implements ValueObject<RouteSpecification> {

  private Location origin;
  private Location destination;
  private Date arrivalDeadline;

  public RouteSpecification(final Location origin, final Location destination, final Date arrivalDeadline) {
    Validate.notNull(origin, "Origin is required");
    Validate.notNull(destination, "Destination is required");
    Validate.notNull(arrivalDeadline, "Arrival deadline is required");
    Validate.isTrue(!origin.sameIdentityAs(destination), "Origin and destination can‘t be the same: " + origin);

    this.origin = origin;
    this.destination = destination;
    this.arrivalDeadline = (Date) arrivalDeadline.clone();
  }

  public Location origin() {
    return origin;
  }

  public Location destination() {
    return destination;
  }

  public Date arrivalDeadline() {
    return new Date(arrivalDeadline.getTime());
  }

  @Override
  public boolean isSatisfiedBy(final Itinerary itinerary) {
    return itinerary != null &&
           origin().sameIdentityAs(itinerary.initialDepartureLocation()) &&
           destination().sameIdentityAs(itinerary.finalArrivalLocation()) &&
           arrivalDeadline().after(itinerary.finalArrivalDate());
  }

  @Override
  public boolean sameValueAs(final RouteSpecification other) {
    return other != null && new EqualsBuilder().
      append(this.origin, other.origin).
      append(this.destination, other.destination).
      append(this.arrivalDeadline, other.arrivalDeadline).
      isEquals();
  }

  @Override
  public boolean equals(final Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    final RouteSpecification that = (RouteSpecification) o;

    return sameValueAs(that);
  }

  @Override
  public int hashCode() {
    return new HashCodeBuilder().
      append(this.origin).
      append(this.destination).
      append(this.arrivalDeadline).
      toHashCode();
  }
}

值對象(Value Object)所包含的屬性應該行程一個概念整體。當我們只關心一個模型元素的屬性時,應該把它歸類為Value Object。我們應該使這個模型元素能夠表示出其屬性的意義,並為它提供相關功能。Value Object應該是不可變的。不要為它分配粉盒標識,而且不要把它設計成像Entity那麽復雜。

2.3 跨領域模型的業務規則的實現

我們使用領域服務來封裝不屬於領域模型或者領域模型公共的業務規則。領域服務的方法一般是靜態的,並且不會更改內部狀態。在裝修設計預約平臺裏面,我們使用狀態機工作流服務實現訂單狀態流轉,它可以在設計師訂單和退款單中共用。在《領域驅動設計》裏面有一個示例,展示了轉賬服務的實現,轉賬動作實現的是從一個賬戶到另一個賬戶的資金流轉,因此將轉賬設計到領域服務TransferService裏面。關於服務的描述是:當領域中的某個重要的過程或轉換操作不屬於實體或值對象的自然職責時,應該在模型中添加一個作為獨立接口的操作,並將其聲明為Service。定義接口時要使用模型語言,並確保操作名稱是領域模型的術語。此外,應該將Service定義為無狀態的。

以下是服務示例。

public class DesignerOrderWorkflowService {
    private DesignerOrderWorkflowService() { }

    private static Map<DesignerOrderState, DesignerOrderState[]> states = new HashMap<>();
    static {
        states.put(DesignerOrderState.NEW, new DesignerOrderState[]{ DesignerOrderState.MEASURED, DesignerOrderState.ABORTED });
        states.put(DesignerOrderState.MEASURED, new DesignerOrderState[]{ DesignerOrderState.QUOTED, DesignerOrderState.ABORTED });
        states.put(DesignerOrderState.QUOTED, new DesignerOrderState[]{ DesignerOrderState.ACCEPT_QUOTE, DesignerOrderState.REJECT_QUOTE, DesignerOrderState.ABORTED });
        states.put(DesignerOrderState.REJECT_QUOTE, new DesignerOrderState[]{ DesignerOrderState.QUOTED, DesignerOrderState.ABORTED });
        states.put(DesignerOrderState.ACCEPT_QUOTE, new DesignerOrderState[]{ DesignerOrderState.PAID, DesignerOrderState.ABORTED });
        states.put(DesignerOrderState.PAID, new DesignerOrderState[]{ DesignerOrderState.REFUND, DesignerOrderState.COMPLETION });
        states.put(DesignerOrderState.COMPLETION, new DesignerOrderState[]{ DesignerOrderState.FEEDBACK });

        states.put(DesignerOrderState.ABORTED, new DesignerOrderState[]{ DesignerOrderState.FEEDBACK });
        states.put(DesignerOrderState.REFUND, new DesignerOrderState[]{ DesignerOrderState.FEEDBACK });
        states.put(DesignerOrderState.FEEDBACK, new DesignerOrderState[]{ DesignerOrderState.FEEDBACK }); // 允許多次評價
    }

    public static boolean canChangeState(DesignerOrderState state, DesignerOrderState nextState) {
        Assert.notNull(state, "The state can not be null.");
        Assert.notNull(nextState, "The nextState can not be null.");

        DesignerOrderState[] nextStates = states.get(state);
        for (DesignerOrderState possibleNextState : nextStates) {
            if (possibleNextState.equals(nextState)) {
                return true;
            }
        }

        return false;
    }

    public static boolean canAbort(DesignerOrder order) {
        return canChangeState(order.getState(), DesignerOrderState.ABORTED);
    }

    public static DesignerOrderState changeState(long orderId, DesignerOrderState state, DesignerOrderState nextState) {
        if (!canChangeState(state, nextState)) {
            BusinessException.throwException(DomainExceptionMessage.STATE_CHANGE_ILLEGAL_CODE, DomainExceptionMessage.STATE_CHANGE_ILLEGAL, orderId, state, nextState);
        }

        return nextState;
    }

    public static boolean isCompleted(DesignerOrder order) {
        return order.getState() == DesignerOrderState.ABORTED ||
                order.getState() == DesignerOrderState.REFUND ||
                order.getState() == DesignerOrderState.COMPLETION ||
                order.getState() == DesignerOrderState.FEEDBACK;
    }
}

3 領域模型生命周期管理

領域模型的創建會包含業務規則,我們應該將這些業務規則封裝起來,使創建過程對應用層透明,這裏引入Factory來實現創建。此外,對於實體,發生一系列事件後,其內部狀態發生了變更,這些狀態變更需要持久化,以使得應用程序能夠恢復實體狀態。對於值對象,我們可能也需要持久化相應的屬性。這裏,我們引入Repository來實現持久化管理。對於一些關聯很緊密的對象,比如采購訂單和商品,他們需要共同的滿足一個規則(比如采購訂單裏面的商品的總額不能超過采購訂單的限額),如果多個用戶同時變更采購訂單或者其包含的商品,就需要引入很復雜的鎖。為了使關聯緊密的對象在整個生命周期都保持一致性,我們引入了聚合Aggregate,通過它來實現一致性。

技術分享圖片

3.1 緊密關聯的領域對象的一致性維護—Aggregate

首先,我們先看一下為什麽要引入Aggregate。這裏以采購訂單為例子,采購員創建采購訂單時需要指定限額,然後增加采購項目,因此可能存在兩個采購員對同一個創建的采購訂單進行操作,來更改訂單。

技術分享圖片

如下所示,對於采購訂單0012946,當前的商品金額為700,限額為1000。采購員A可能更改商品項1的數量為5,其總額為900,滿足限額;采購員B可能更改商品項2的數量為3,其總額也為900,滿足限額。

技術分享圖片

當采購員A、B同時提交更新後,采購訂單的總額為1100,超過了1000元限額,破壞了業務規則。

在傳統的方法,當我們采用以下方式更新采購訂單商品,就會出現剛才破壞業務規則的情況發生。

PurchaseOrder purchaseOrder = purchaseOrderBiz.getByKey(“0012946”);

List<PurchaseOrderItem> purchaseOrderItems = purchaseOrderItemBiz.getByOrderId(“0012946”);
changePurchaseOrderItems(purchaseOrderItems);
if (new PurchaseOrderApprovedLimitSpecify(purchaseOrderItems, purchaseOrder).isSatisfied()) {
    purchaseOrderItemBiz.updateBatch(purchaseOrderItems);
}

為了避免發生采購訂單限額的業務規則被破壞,對采購訂單項的變更,需要對采購訂單加排它鎖。

在DDD裏面,引入了聚合(Aggregate)來解決這個問題。Aggregate時一組相關對象的集合,作為數據修改的單元,在整個生命周期中滿足固定的業務規則。每個Aggregate都有一個根(root)和一個邊界(boundary)。邊界定義了Aggregate的內部都有什麽,根則是Aggregate中所包含的一個特定Entity。在Aggregate中,根是唯一允許外部對象保持對它的引用的元素,而邊界內部的對象則可以互相引用。基於聚合,我們來實現一致的采購訂單業務規則如下。

(1)應用層通過以下方式來更新聚合根裏面的內容,這裏必須滿足一致性規則:對聚合內部實體的狀態變更,只能通過聚合根來實現,通過聚合根來維持業務一致性。

PurchaseOrder order = purchaseOrderRepository.load(id);
order.addItem(…)/removeItem(…)/updateItem(…); // 註意:這裏是重點,對聚合根內部的變更,只能通過聚合根,不能通過獲取內部對象進行操作
purchaseOrderRepository.save(order);

(2)聚合根對內部實體的狀態變更如下。

public class PurchaseOrder {
    private PurchaseOrderItemRepository orderItemRepository;
    private List<PurchaseOrderItem> orderItems;
    // ……
    public void addItem(int itemId, int count) {
        PurchaseOrderItem orderItem = PurchaseOrderItemFactory.create(this, itemId, count);
        orderItems.add(orderItem);
        if (!new PurchaseOrderApprovedLimitSpecification(this).isSatisfied()) {
            BusinessException.throwException(…);
            return;
        }
        
        orderItemRepository.save(orderItem);
        this.updateTimestamp();
    }
    // ……
}

聚合根定義的規則如下:
• 根Entity具有全局標識,它最終負責檢查固定規則。
• 根Entity具有全局標識。邊界內的Entity具有本地標識,這些標識只有在Aggregate內部才是唯一的。
• Aggregate外部的對象不能引用除根Entity之外的任何內部對象。根Entity可以把對內部Entity的引用傳遞給它們,但這些對象只能臨時使用這些引用,而不能保持引用。根可以把一個Value Object的副班傳遞給另一個對象,而不必關心它發生什麽變化,因為它只是一個Value,不再與Aggregate有任何關聯。
• 作為上一條規則的推論,只有Aggregate的根才能直接通過數據庫查詢獲取。所有其他對象必須通過關聯的遍歷才能找到。
• Aggregate內部的對象可以保持對其他Aggregate根的引用。
• 刪除操作必須一次刪除Aggregate之內的所有對象。
• 當提交對Aggregate彬姐內部的任何對象的修改時,整個Aggregate中的所有固定規則都必須被滿足。

3.2 領域模型的創建—Factory

當創建一個對象或創建整個Aggregate時,如果創建工作很負責,或者暴露了過多的內部結構,則可以使用Factory進行封裝。領域模型的創建也可能隱含了業務規則,Factory可以向應用層屏蔽業務規則。以下是一個設計師訂單的Factory類。

public class DesignerOrderFactory {
    private DesignerOrderFactory() {}

    public static DesignerOrder createOrder(int customerId, int designerId) {
        DesignerOrder designerOrder = new DesignerOrder();
        designerOrder.setCustomerId(customerId);
        designerOrder.setDesignerId(designerId);
        designerOrder.setState(DesignerOrderState.NEW);

        return designerOrder;
    }
}

結論:應該將創建復雜對象的實例和聚合的職責轉移給一個單獨的對象,這個對象本身在領域模型中可能沒有職責,但它仍是領域設計的一部分。提供一個封裝所有復雜裝配操作的接口,而且這個接口應該不需要上層引用要被實例化的對象的具體類。在創建Aggregate時,要把它作為一個整體,並確保它滿足固定規則。

3.3 領域模型的持久化—Repository

Repository的目的是實現領域對象的持久化,用於領域對象關聯查詢、重建、添加和刪除。我們只為那些確實需要直接訪問的Aggregate提供Repository,將所有對象的存儲和訪問操作交給Repository。如下是一個實例。

@Repository
public class DesignerOrderRepositoryImpl implements DesignerOrderRepository {
    private static final String DESIGNER_ORDER_TABLE = "designer_order";

    @Autowired
    private DesignerOrderMapper designerOrderMapper;

    @Override
    public void create(DesignerOrder order) {
        if (designerOrderMapper.create(order) == 0) {
            TableException.throwTableException(DESIGNER_ORDER_TABLE, TableOperation.CREATE);
        }
    }

    @Override
    public DesignerOrder selectByKey(int id) {
        DesignerOrder order = designerOrderMapper.selectByKey(id);
        buildConnection(order);
        return order;
    }

    @Override
    public DesignerOrder selectOneBySpecification(DesignerOrder example) {
        DesignerOrder designerOrder = designerOrderMapper.selectOneBySpecification(example);
        buildConnection(designerOrder);
        return designerOrder;
    }

    @Override
    public List<DesignerOrder> selectBySpecification(DesignerOrder example) {
        List<DesignerOrder> designerOrders = designerOrderMapper.selectBySpecification(example);
        buildConnection(designerOrders);
        return designerOrders;
    }

    @Override
    public void update(DesignerOrder order) {
        if (designerOrderMapper.update(order) == 0) {
            TableException.throwTableException(DESIGNER_ORDER_TABLE, TableOperation.UPDATE);
        }
    }
}

4 結論

領域驅動設計的模式如下所示。

技術分享圖片

綜上,領域層的實現由聚合構成,每一個聚合通常包含了聚合根和領域模型實現、Service、工廠、Repository、領域異常等。

技術分享圖片

最終裝修設計預約平臺的領域模型如下所示。

技術分享圖片

解構領域驅動設計(三):領域驅動設計