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

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

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

複製代碼

@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,通過它來實現一致性。

 

總結一句話:創建階段——Factory用於封裝實現領域對象創建的業務規則;創建、修改——Aggregate用於封裝緊密關聯領域對象在生命週期內的數據一致性;存儲——Repository用於封裝領域對象持久化的邏輯。

 

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、領域異常等。

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

 

源碼:https://github.com/lorry2018/ddd-simple-designer-order

 

關於iOpenWorksSDK下載和疑問,訪問:https://www.cnblogs.com/baihmpgy/p/11818026.html

本文基於Creative Commons Attribution 2.5 China Mainland License發佈,歡迎轉載,演繹或用於商業目的,但是必須保留本文的署名道法自然(包含鏈接)。如您有任何疑問或者授權方面的協商,請給我留言。

 

好文要頂 關注我 收藏該文  

道法自然
關注 - 78
粉絲 - 1975

推薦博客

+加關注

13

« 上一篇: 解構領域驅動設計(二):分層架構
» 下一篇: iOpenWorskSDK下載和答疑貼