解构领域驱动设计(三):领域驱动设计
在上一部分,分層架構(gòu)的目的是為了將業(yè)務(wù)規(guī)則剝離出來在單獨的領(lǐng)域?qū)又羞M行實現(xiàn)。再回顧一下領(lǐng)域驅(qū)動設(shè)計的分層中應(yīng)用層代碼的實現(xiàn)。
@Override public void pay(int orderId, float amount) {DesignerOrder order = designerOrderRepository.selectByKey(orderId); // 領(lǐng)域?qū)ο蟮募虞dif (order == null) {AppException.throwAppException(AppExceptionMessage.DESIGNER_ORDER_NOT_EXIST_CODE, AppExceptionMessage.DESIGNER_ORDER_NOT_EXIST, orderId);}order.pay(amount); // 領(lǐng)域?qū)ο髽I(yè)務(wù)規(guī)則實現(xiàn)designerOrderRepository.update(order); // 領(lǐng)域?qū)ο鬆顟B(tài)持久化 }?
所有的業(yè)務(wù)規(guī)則都抽象到領(lǐng)域?qū)ο?#xff0c;比如“order.pay(amount)”抽象了付款的業(yè)務(wù)規(guī)則。領(lǐng)域?qū)ο笥蔂顟B(tài)(對象的字段、屬性)和操作(對象的方法)構(gòu)成,領(lǐng)域?qū)ο蟮牟僮饔糜趯崿F(xiàn)業(yè)務(wù)規(guī)則,業(yè)務(wù)規(guī)則執(zhí)行完成后更改領(lǐng)域?qū)ο蟮臓顟B(tài)。領(lǐng)域?qū)ο蟮某志没唤o了基礎(chǔ)設(shè)施層,這里,Repository目的是持久化領(lǐng)域?qū)ο鬆顟B(tài)。
領(lǐng)域驅(qū)動設(shè)計,即領(lǐng)域模型驅(qū)動程序設(shè)計,它的核心是保證系統(tǒng)的實現(xiàn)與實際的業(yè)務(wù)規(guī)則一致,完整實現(xiàn)了領(lǐng)域模型。它包含了兩個部分:領(lǐng)域模型、領(lǐng)域模型的編程實現(xiàn)。
在軟件設(shè)計和實現(xiàn)過程中要充分利用領(lǐng)域模型,設(shè)計過程中,領(lǐng)域模型作為與業(yè)務(wù)專家的溝通語言;實現(xiàn)過程中,領(lǐng)域模型作為與開發(fā)人員溝通的語言。領(lǐng)域模型在軟件生命周期過程作為通用語言。
1 領(lǐng)域模型
領(lǐng)域建模(這里不重點介紹如何建模)方法論產(chǎn)出領(lǐng)域模型。我們可以使用UML建模,使用最簡單、最容易理解的名詞-形容詞-動詞法對領(lǐng)域知識進行建模,使用該模型作為與業(yè)務(wù)、技術(shù)團隊溝通的通用語言。
在名詞-形容詞-動詞法建模方法中,領(lǐng)域知識中的名詞一般對應(yīng)模型、形容詞對應(yīng)模型屬性、動詞對應(yīng)模型方法。模型之間的關(guān)系有:組合、聚合、關(guān)聯(lián)、依賴,四者關(guān)系由強到弱。
依賴(Dependency)關(guān)系是類與類之間的聯(lián)接。依賴關(guān)系表示一個類依賴于另一個類的定義。一般而言,依賴關(guān)系在Java語言中體現(xiàn)為局域變量、方法的形參,或者對靜態(tài)方法的調(diào)用。?
關(guān)聯(lián)(Association)關(guān)系是類與類之間的聯(lián)接,它使一個類知道另一個類的屬性和方法。關(guān)聯(lián)可以是雙向的,也可以是單向的。在Java語言中,關(guān)聯(lián)關(guān)系一般使用成員變量來實現(xiàn)。?
聚合(Aggregation)?關(guān)系是關(guān)聯(lián)關(guān)系的一種,是強的關(guān)聯(lián)關(guān)系。聚合是整體和個體之間的關(guān)系。例如,汽車類與引擎類、輪胎類,以及其它的零件類之間的關(guān)系便整體和個體的關(guān)系。與關(guān)聯(lián)關(guān)系一樣,聚合關(guān)系也是通過實例變量實現(xiàn)的。但是關(guān)聯(lián)關(guān)系所涉及的兩個類是處在同一層次上的,而在聚合關(guān)系中,兩個類是處在不平等層次上的,一個代表整體,另一個代表部分。?
組合(Composition)?關(guān)系是關(guān)聯(lián)關(guān)系的一種,是比聚合關(guān)系強的關(guān)系。它要求普通的聚合關(guān)系中代表整體的對象負責(zé)代表部分對象的生命周期,組合關(guān)系是不能共享的。代表整體的對象需要負責(zé)保持部分對象和存活,在一些情況下將負責(zé)代表部分的對象湮滅掉。代表整體的對象可以將代表部分的對象傳遞給另一個對象,由后者負責(zé)此對象的生命周期。換言之,代表部分的對象在每一個時刻只能與一個對象發(fā)生組合關(guān)系,由后者排他地負責(zé)生命周期。部分和整體的生命周期一樣。?
簡而言之,組合關(guān)系表示部分與整體關(guān)系,部分不能單獨存在;聚合關(guān)系表示稍弱的部分與整體關(guān)系,部分可以單獨存在;關(guān)聯(lián)關(guān)系是一個模型和另一個模型的聯(lián)接,比如一個訂單有一個顧客而一個顧客有多個訂單;依賴是最弱的關(guān)系,表示一個模型的實現(xiàn)使用到另一個模型的功能。
舉個例子,我們與業(yè)務(wù)專家溝通,梳理了如下業(yè)務(wù)知識,然后我們使用名詞-形容詞-動詞法來進行建模。
=====================
領(lǐng)域知識:裝修設(shè)計預(yù)約平臺
1 客戶通過系統(tǒng)預(yù)約設(shè)計師進行裝修設(shè)計,客戶只能預(yù)約一個設(shè)計師訂單,不能預(yù)約多個同時進行設(shè)計。
2 預(yù)約后,設(shè)計師上門進行量房,根據(jù)面積進行報價和預(yù)估設(shè)計時間。設(shè)計師訂單按照4個節(jié)點預(yù)估交付時間,在不同節(jié)點交付不同成果,這四個節(jié)點分別為平面圖、效果圖、施工
圖、交底,四個節(jié)點的付款比率分別為10%、40%、40%、10%。
3 客戶接受報價方案后,進行付款,設(shè)計師開始設(shè)計;如果拒絕,則設(shè)計師可以進行再次報價和預(yù)估設(shè)計時間。
4 客戶在付款之前,都可以進行終止。
5 客戶付款后,正式進入設(shè)計階段。設(shè)計師按階段推進設(shè)計并按階段更新進度。在每一個階段,設(shè)計師完成任務(wù)后,客戶進行階段成果確認,客戶確定后所有階段后,訂單自動完成。
6 客戶可以對完成的訂單進行評價。
7 客戶對已付款但未完成的訂單可以提出退款申請,退款計算方法依據(jù)當(dāng)前設(shè)計進度,如果當(dāng)前進度已經(jīng)達到設(shè)計師請求施工圖設(shè)計確認進度或超過該進度,則不允許退款。如果允許退款,退款金額最多為(總額 - 已完成的各階段付款之和),最少為未完成交付節(jié)點的待付款總額。
8 申請通過的退款訂單不再允許更新進度。
=====================
在這里我們可以梳理出來的名詞有:客戶、設(shè)計師訂單、設(shè)計師、訂單交付進度與交付節(jié)點、退款訂單。
和設(shè)計師訂單有關(guān)的動詞有:量房、報價、接受(拒絕)報價、取消、付款、確認進度、退款、評價等。
設(shè)計師訂單有關(guān)的屬性有:訂單金額、支付金額、面積、取消原因、評價、狀態(tài)等。
因此,我們通過使用名詞-形容詞-動詞法構(gòu)建的模型圖如下所示。
這里,模型有:客戶Customer,設(shè)計師Designer,設(shè)計師訂單DesignerOrder,退款單RefundOrder,設(shè)計進度DesigningProgressReport,設(shè)計進度節(jié)點DesigningProgressNode。模型中組合關(guān)系為:設(shè)計進度DesigningProgressReport,設(shè)計進度節(jié)點DesigningProgressNode;其它模型之間的關(guān)系為關(guān)聯(lián)關(guān)系。
這個模型就作為軟件開發(fā)和維護過程的通用語言。接下來,我們將介紹如何來實現(xiàn)領(lǐng)域模型。
2 領(lǐng)域模型實現(xiàn)
在上一節(jié),我們介紹了通過領(lǐng)域建模來構(gòu)建了領(lǐng)域模型。接下來我們要介紹如何實現(xiàn)模型驅(qū)動程序設(shè)計,即我們?nèi)绾瓮ㄟ^代碼來實現(xiàn)領(lǐng)域模型對應(yīng)的業(yè)務(wù)邏輯。領(lǐng)域模型的實現(xiàn)代碼在領(lǐng)域?qū)?#xff0c;它完整實現(xiàn)了領(lǐng)域模型的內(nèi)部結(jié)構(gòu)和模型之間的關(guān)系。
領(lǐng)域模型的實現(xiàn)代碼由以下幾個部分構(gòu)成:
? 領(lǐng)域模型關(guān)系的實現(xiàn):組合、聚合、關(guān)聯(lián)、依賴。
? 領(lǐng)域模型的實現(xiàn):實體和值對象。
? 跨領(lǐng)域模型的業(yè)務(wù)規(guī)則的實現(xiàn):領(lǐng)域服務(wù)。
2.1 領(lǐng)域模型關(guān)系的實現(xiàn)
聚合、組合、關(guān)聯(lián)關(guān)系在實現(xiàn)上的表現(xiàn)基本上是一個類(或者類的標識)作為另一個類的屬性;而依賴關(guān)系則是一個類作為另一個類在方法的實現(xiàn)上的參數(shù)、變量,為另一個類提供功能實現(xiàn)。
下面我們簡單看一下如何通過編碼來實現(xiàn)類關(guān)聯(lián)關(guān)系,比如在模型上客戶和設(shè)計師訂單是關(guān)聯(lián)關(guān)系,一個客戶可以有多個設(shè)計師訂單,但是每一個設(shè)計師訂單只能有一個客戶和一個設(shè)計師并且最多只有一個退款訂單。
(1)聚合、組合、關(guān)聯(lián)
表現(xiàn)在一個類持有另一個類的引用,引用可以是實例的引用或者標識的引用,具體實現(xiàn)為屬性。這種關(guān)系是雙向關(guān)系,為了簡化編碼,可能只需要一方持有另一方的引用即可,這依賴于具體要實現(xiàn)的業(yè)務(wù)邏輯。如下代碼實現(xiàn)了DesignerOrder對設(shè)計師、進度報告的關(guān)系。
?
(2)依賴
依賴表現(xiàn)在一個類的實現(xiàn)使用到另一個類的功能,依賴的類可能作為方法的參數(shù)、方法局部變量或者靜態(tài)引用等。如下代碼體現(xiàn)了對DesignerOrderWorkflowService的功能依賴。
?
2.2 領(lǐng)域模型的實現(xiàn)
領(lǐng)域模型在實現(xiàn)上表現(xiàn)為兩類:(1)實體(Entity):這個領(lǐng)域模型有特定的標識,但是其內(nèi)部狀態(tài)會隨著一序列的事件(對應(yīng)業(yè)務(wù)規(guī)則的執(zhí)行)發(fā)生變化,我們把這類模型的實現(xiàn)稱為實體;(2)值對象(Value Object):這個領(lǐng)域模型由屬性來定義,實例創(chuàng)建后不會發(fā)生變更,變更也意味著重新創(chuàng)建一個實例,我們把這類模型的實現(xiàn)稱為值對象。
(1)實體
在裝修設(shè)計預(yù)約平臺的領(lǐng)域模型里面,我們很容易可以發(fā)現(xiàn)設(shè)計師訂單就是一個實體,在創(chuàng)建后,每一個設(shè)計師訂單有一個唯一的訂單號,后續(xù)有量房、報價、付款、退款等系列動作的發(fā)生,從而訂單的內(nèi)部狀態(tài)(字段值)會發(fā)生變化,但是都代表的是同一個訂單。每一個實體的實現(xiàn)都有一個標識。如下所示,這里的id字段表示了訂單的唯一標識,并實現(xiàn)了Entity接口,Entity接口sameIdentityAs方法,判斷實體的Id是否相同。
實體的屬性和操作,對應(yīng)著模型的狀態(tài)和狀態(tài)的變更,他們與模型的定義使一致的。
@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;@Overridepublic 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對于實體有一段重要描述:當(dāng)一個對象由其標識而不是屬性區(qū)分時,那么在模型中應(yīng)該主要通過標識來確定該對象的定義。使類定義變得簡單,并集中關(guān)注生命周期的連續(xù)性和標識。定義一種區(qū)分每個對象的方式,這種方式應(yīng)該與其形式和歷史無關(guān)。要格外注意那些需要通過屬性來匹配對象的需求。在定義標識操作時,要確保這種操作作為每個對象生成唯一的結(jié)果,這可以通過附加一個保證唯一性的符號來實現(xiàn)。這種定義標識的方法可能來自外部,也可能是由系統(tǒng)創(chuàng)建的任意標識符,但它在模型中必須是唯一的標識。模型必須定義出“符合什么條件才算是相同的事務(wù)”。
(2)值對象
在貨物運輸系統(tǒng)中,當(dāng)我們?yōu)橐粋€貨物的運輸執(zhí)行一條路線之后,那么這條路線不能發(fā)生變更,我們傾向于把路由線路看做一個值對象。如下圖所示。對于值對象,通過屬性值即可標識。
?
值對象(Value Object)所包含的屬性應(yīng)該行程一個概念整體。當(dāng)我們只關(guān)心一個模型元素的屬性時,應(yīng)該把它歸類為Value Object。我們應(yīng)該使這個模型元素能夠表示出其屬性的意義,并為它提供相關(guān)功能。Value Object應(yīng)該是不可變的。不要為它分配粉盒標識,而且不要把它設(shè)計成像Entity那么復(fù)雜。
2.3 跨領(lǐng)域模型的業(yè)務(wù)規(guī)則的實現(xiàn)
我們使用領(lǐng)域服務(wù)來封裝不屬于領(lǐng)域模型或者領(lǐng)域模型公共的業(yè)務(wù)規(guī)則。領(lǐng)域服務(wù)的方法一般是靜態(tài)的,并且不會更改內(nèi)部狀態(tài)。在裝修設(shè)計預(yù)約平臺里面,我們使用狀態(tài)機工作流服務(wù)實現(xiàn)訂單狀態(tài)流轉(zhuǎn),它可以在設(shè)計師訂單和退款單中共用。在《領(lǐng)域驅(qū)動設(shè)計》里面有一個示例,展示了轉(zhuǎn)賬服務(wù)的實現(xiàn),轉(zhuǎn)賬動作實現(xiàn)的是從一個賬戶到另一個賬戶的資金流轉(zhuǎn),因此將轉(zhuǎn)賬設(shè)計到領(lǐng)域服務(wù)TransferService里面。關(guān)于服務(wù)的描述是:當(dāng)領(lǐng)域中的某個重要的過程或轉(zhuǎn)換操作不屬于實體或值對象的自然職責(zé)時,應(yīng)該在模型中添加一個作為獨立接口的操作,并將其聲明為Service。定義接口時要使用模型語言,并確保操作名稱是領(lǐng)域模型的術(shù)語。此外,應(yīng)該將Service定義為無狀態(tài)的。
以下是服務(wù)示例。
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 領(lǐng)域模型生命周期管理
領(lǐng)域模型的創(chuàng)建會包含業(yè)務(wù)規(guī)則,我們應(yīng)該將這些業(yè)務(wù)規(guī)則封裝起來,使創(chuàng)建過程對應(yīng)用層透明,這里引入Factory來實現(xiàn)創(chuàng)建。此外,對于實體,發(fā)生一系列事件后,其內(nèi)部狀態(tài)發(fā)生了變更,這些狀態(tài)變更需要持久化,以使得應(yīng)用程序能夠恢復(fù)實體狀態(tài)。對于值對象,我們可能也需要持久化相應(yīng)的屬性。這里,我們引入Repository來實現(xiàn)持久化管理。對于一些關(guān)聯(lián)很緊密的對象,比如采購訂單和商品,他們需要共同的滿足一個規(guī)則(比如采購訂單里面的商品的總額不能超過采購訂單的限額),如果多個用戶同時變更采購訂單或者其包含的商品,就需要引入很復(fù)雜的鎖。為了使關(guān)聯(lián)緊密的對象在整個生命周期都保持一致性,我們引入了聚合Aggregate,通過它來實現(xiàn)一致性。
?
?
總結(jié)一句話:創(chuàng)建階段——Factory用于封裝實現(xiàn)領(lǐng)域?qū)ο髣?chuàng)建的業(yè)務(wù)規(guī)則;創(chuàng)建、修改——Aggregate用于封裝緊密關(guān)聯(lián)領(lǐng)域?qū)ο笤谏芷趦?nèi)的數(shù)據(jù)一致性;存儲——Repository用于封裝領(lǐng)域?qū)ο蟪志没倪壿嫛?/p>
?
3.1 緊密關(guān)聯(lián)的領(lǐng)域?qū)ο蟮囊恢滦跃S護—Aggregate
首先,我們先看一下為什么要引入Aggregate。這里以采購訂單為例子,采購員創(chuàng)建采購訂單時需要指定限額,然后增加采購項目,因此可能存在兩個采購員對同一個創(chuàng)建的采購訂單進行操作,來更改訂單。
如下所示,對于采購訂單0012946,當(dāng)前的商品金額為700,限額為1000。采購員A可能更改商品項1的數(shù)量為5,其總額為900,滿足限額;采購員B可能更改商品項2的數(shù)量為3,其總額也為900,滿足限額。
當(dāng)采購員A、B同時提交更新后,采購訂單的總額為1100,超過了1000元限額,破壞了業(yè)務(wù)規(guī)則。
在傳統(tǒng)的方法,當(dāng)我們采用以下方式更新采購訂單商品,就會出現(xiàn)剛才破壞業(yè)務(wù)規(guī)則的情況發(fā)生。
PurchaseOrder purchaseOrder = purchaseOrderBiz.getByKey(“0012946”);List<PurchaseOrderItem> purchaseOrderItems = purchaseOrderItemBiz.getByOrderId(“0012946”); changePurchaseOrderItems(purchaseOrderItems); if (new PurchaseOrderApprovedLimitSpecify(purchaseOrderItems, purchaseOrder).isSatisfied()) {purchaseOrderItemBiz.updateBatch(purchaseOrderItems); }?
為了避免發(fā)生采購訂單限額的業(yè)務(wù)規(guī)則被破壞,對采購訂單項的變更,需要對采購訂單加排它鎖。
在DDD里面,引入了聚合(Aggregate)來解決這個問題。Aggregate時一組相關(guān)對象的集合,作為數(shù)據(jù)修改的單元,在整個生命周期中滿足固定的業(yè)務(wù)規(guī)則。每個Aggregate都有一個根(root)和一個邊界(boundary)。邊界定義了Aggregate的內(nèi)部都有什么,根則是Aggregate中所包含的一個特定Entity。在Aggregate中,根是唯一允許外部對象保持對它的引用的元素,而邊界內(nèi)部的對象則可以互相引用。基于聚合,我們來實現(xiàn)一致的采購訂單業(yè)務(wù)規(guī)則如下。
(1)應(yīng)用層通過以下方式來更新聚合根里面的內(nèi)容,這里必須滿足一致性規(guī)則:對聚合內(nèi)部實體的狀態(tài)變更,只能通過聚合根來實現(xiàn),通過聚合根來維持業(yè)務(wù)一致性。
PurchaseOrder order = purchaseOrderRepository.load(id); order.addItem(…)/removeItem(…)/updateItem(…); // 注意:這里是重點,對聚合根內(nèi)部的變更,只能通過聚合根,不能通過獲取內(nèi)部對象進行操作 purchaseOrderRepository.save(order);?
(2)聚合根對內(nèi)部實體的狀態(tài)變更如下。
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();}// …… }?
聚合根定義的規(guī)則如下:
? 根Entity具有全局標識,它最終負責(zé)檢查固定規(guī)則。
? 根Entity具有全局標識。邊界內(nèi)的Entity具有本地標識,這些標識只有在Aggregate內(nèi)部才是唯一的。
? Aggregate外部的對象不能引用除根Entity之外的任何內(nèi)部對象。根Entity可以把對內(nèi)部Entity的引用傳遞給它們,但這些對象只能臨時使用這些引用,而不能保持引用。根可以把一個Value Object的副班傳遞給另一個對象,而不必關(guān)心它發(fā)生什么變化,因為它只是一個Value,不再與Aggregate有任何關(guān)聯(lián)。
? 作為上一條規(guī)則的推論,只有Aggregate的根才能直接通過數(shù)據(jù)庫查詢獲取。所有其他對象必須通過關(guān)聯(lián)的遍歷才能找到。
? Aggregate內(nèi)部的對象可以保持對其他Aggregate根的引用。
? 刪除操作必須一次刪除Aggregate之內(nèi)的所有對象。
? 當(dāng)提交對Aggregate彬姐內(nèi)部的任何對象的修改時,整個Aggregate中的所有固定規(guī)則都必須被滿足。
3.2 領(lǐng)域模型的創(chuàng)建—Factory
當(dāng)創(chuàng)建一個對象或創(chuàng)建整個Aggregate時,如果創(chuàng)建工作很負責(zé),或者暴露了過多的內(nèi)部結(jié)構(gòu),則可以使用Factory進行封裝。領(lǐng)域模型的創(chuàng)建也可能隱含了業(yè)務(wù)規(guī)則,Factory可以向應(yīng)用層屏蔽業(yè)務(wù)規(guī)則。以下是一個設(shè)計師訂單的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;} }?
結(jié)論:應(yīng)該將創(chuàng)建復(fù)雜對象的實例和聚合的職責(zé)轉(zhuǎn)移給一個單獨的對象,這個對象本身在領(lǐng)域模型中可能沒有職責(zé),但它仍是領(lǐng)域設(shè)計的一部分。提供一個封裝所有復(fù)雜裝配操作的接口,而且這個接口應(yīng)該不需要上層引用要被實例化的對象的具體類。在創(chuàng)建Aggregate時,要把它作為一個整體,并確保它滿足固定規(guī)則。
3.3 領(lǐng)域模型的持久化—Repository
Repository的目的是實現(xiàn)領(lǐng)域?qū)ο蟮某志没?#xff0c;用于領(lǐng)域?qū)ο箨P(guān)聯(lián)查詢、重建、添加和刪除。我們只為那些確實需要直接訪問的Aggregate提供Repository,將所有對象的存儲和訪問操作交給Repository。如下是一個實例。
@Repository public class DesignerOrderRepositoryImpl implements DesignerOrderRepository {private static final String DESIGNER_ORDER_TABLE = "designer_order";@Autowiredprivate DesignerOrderMapper designerOrderMapper;@Overridepublic void create(DesignerOrder order) {if (designerOrderMapper.create(order) == 0) {TableException.throwTableException(DESIGNER_ORDER_TABLE, TableOperation.CREATE);}}@Overridepublic DesignerOrder selectByKey(int id) {DesignerOrder order = designerOrderMapper.selectByKey(id);buildConnection(order);return order;}@Overridepublic DesignerOrder selectOneBySpecification(DesignerOrder example) {DesignerOrder designerOrder = designerOrderMapper.selectOneBySpecification(example);buildConnection(designerOrder);return designerOrder;}@Overridepublic List<DesignerOrder> selectBySpecification(DesignerOrder example) {List<DesignerOrder> designerOrders = designerOrderMapper.selectBySpecification(example);buildConnection(designerOrders);return designerOrders;}@Overridepublic void update(DesignerOrder order) {if (designerOrderMapper.update(order) == 0) {TableException.throwTableException(DESIGNER_ORDER_TABLE, TableOperation.UPDATE);}} }?
4 結(jié)論
領(lǐng)域驅(qū)動設(shè)計的模式如下所示。
?
綜上,領(lǐng)域?qū)拥膶崿F(xiàn)由聚合構(gòu)成,每一個聚合通常包含了聚合根和領(lǐng)域模型實現(xiàn)、Service、工廠、Repository、領(lǐng)域異常等。
最終裝修設(shè)計預(yù)約平臺的領(lǐng)域模型如下所示。
轉(zhuǎn)載于:https://www.cnblogs.com/baihmpgy/p/10760324.html
總結(jié)
以上是生活随笔為你收集整理的解构领域驱动设计(三):领域驱动设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。