主頁 > 軟體設計 > 戲說領域驅動設計(十七)——物體實戰

戲說領域驅動設計(十七)——物體實戰

2022-03-24 08:18:11 軟體設計

  上一節中講了物體的一些概念,作為DDD中最為復雜的組件,想用好了還需要在實踐中慢慢去摸索,都是摸爬滾打過來的,本章著重演示一些物體相關的代碼,通過建立一個基類和通用方法,能讓您在開發程序中少寫一些重復的代碼同時也減少在使用第三方開源框架時的學習成本,此外,是從0寫代碼,不需要付出太多的精力便可以加深自身對理論的理解,友情提示一下,您在看的同時也需要回憶一下前面文章中所說的各類規則、限制,理論與實踐相互印證才能更高效,其實在業務系統開發程序中很少會直接從零寫物體的,多多少少也得有一些基類供使用,畢竟有很多東西是通用的,建一個物體就重寫一次您不累嗎?本章我會從一些基礎的內容開始展示在不用任何架構的情況下如果實踐DDD,代碼僅供參考,每個人的實作方式都會不一樣,了解思路即可,

一、領域模型基類

  領域模型基類是物體和值物件共同的父類,雖然物體和值物件作用不一樣但都屬于領域模型,這個基類無任何屬性,只是起到了占位符的作用,后面有些功能比如“領域模型驗證工具”要求待驗證的目標應該是領域模型,具體代碼如下,

/**
 * 領域模型基類
 */
public abstract class DomainModel extends ValidatableBase {

    /**
     * 初始化當前狀態
     */
    public void initializeForNewCreation() {

    }
}

  方法“initializeForNewCreation”用于初始化新建的物件,比如在new物件后進行一些屬性的默認值設定,現實中有些場景可能還需要特殊的初始化方式,一般會放到領域物件工廠中完成,您可能會注意到,領域模型從類“ValidatableBase”繼承,這樣做的目的表示領域模型是可被驗證的,比如領域模型持久化前或從反序列后需要進行物件合法性的驗證,而我又不想在每次對屬性賦值后都判斷值的合法性,好的方式是進行統一的驗證并將不合法的內容統一拋出去,物件內部提供驗證方法我稱之為“內驗”,內驗的目標是物件的屬性或屬性組合,也就是只驗證模型本身是否合法,不驗證外部條件,

  有人認為把物件驗證的方法放到領域模型中會造成模型的責任變重,所以會建立專門用于驗證的類或服務,我個人覺得一個物件屬性是否合法是一種業務規則,應由物件自已責任,由其自己驗證可產生較好的內聚性,就和人生病一樣,自己最了解哪里最不爽,此外,我所用的“內驗”并未讓物件自己執行驗證(雖然你也可以進行手動的呼叫)而是在其中設定驗證規則并由專門的驗證服務負責執行驗證邏輯,下面代碼演示了如何在領域模型中嵌入驗證規則,需要注意的是本章重點并不在驗證上面,這方面內容會啟動一個新的章節做專門講解,

/**
 * 可驗證物件的基類
 */
public abstract class ValidatableBase implements Validatable {
   ……
    protected void addRule(RuleManager ruleManager){

    }
  
   final public ParameterValidationResult validate() {
     ……
   }
   …… }
public class DeploymentApprover extends ApproverBase {
   private
PhaseType targetPhase;    …… @Override protected void addRule(RuleManager ruleManager) { super.addRule(ruleManager); ruleManager.addRule(new ObjectNotNullRule("targetPhase", this.targetPhase, OperationMessages.INVALID_ROLE_TYPE)); ruleManager.addRule(new NotEqualsRule("targetPhase", this.targetPhase, PhaseType.UNKNOWN, OperationMessages.INVALID_ROLE_TYPE)); }    …… }

  上述代碼中的“addRule”方法定義于父類“ValidatableBase”中,用于為領域模型增加驗證規則,比如屬性“status”的值不能是“null”和“PhaseType.UNKNOWN”,這種只加規則不驗證的方式實際上有點規約模式(Specification )的味道,算是一個簡化版,

二、物體型別基類

  物體型別也算是一種領域模型,所以我們就可以在既有的領域模型的基礎上設計物體型別的基類,所有的業務物體都從這個基類繼承,請參看如下代碼,

public abstract class EntityModel<TID extends Comparable> extends DomainModel implements Versionable {
    //ID
    private TID id;
    //版本資訊,用于控制并發
    private int version;
    //創建日期
    private LocalDateTime createdDate = LocalDateTime.now();
    //變更日期
    private LocalDateTime updatedDate = LocalDateTime.now();
    //狀態
    private  Status status =  Status.ACTIVE;

    protected EntityModel(TID id) {
        this(id, null, null);
    }

    protected EntityModel(TID id, LocalDateTime createdDate, LocalDateTime updatedDate) {
        this(id,  Status.ACTIVE, 0, createdDate, updatedDate);
    }

    protected EntityModel(TID id,  Status status, LocalDateTime createdDate, LocalDateTime updatedDate) {
        this(id, status, 0, createdDate, updatedDate);
    }

    protected EntityModel(TID id,  Status status, int version, LocalDateTime createdDate, LocalDateTime updatedDate) {
        this.id = id;
        this.version = version;
        this.initializeForNewCreation();
        if (status != null && status !=  Status.UNKNOWN) {
            this.status = status;
        }
        if (createdDate != null) {
            this.createdDate = createdDate;
        }
        if (updatedDate != null) {
            this.updatedDate = updatedDate;
        }
    }

    /**
     * 當前物件置無效
     */
    public void disable() throws InvalidOperationException {
        this.status =  Status.INACTIVE;
        this.updatedDate = LocalDateTime.now();
    }
 
    @Override
    protected void addRule(RuleManager ruleManager) {
        super.addRule(ruleManager);
        ruleManager.addRule(new ObjectNotNullRule("id", this.id, OperationMessages.INVALID_ID));
    }

    @Override
    public boolean equals(Object object){
        if(object == null){
            return false;
        }
        if(!(object instanceof EntityModel)){
            return false;
        }
        if(object == this){
            return true;
        }
        return this.id.compareTo(((EntityModel)object).getId()) == 0;
    }
    
    @Override
    public int hashCode(){
        return this.id.hashCode();
    }

    /**
     * 獲取版本資訊,
     * @return 版本資訊
     */
    @Override
    public int getVersion() {
        return this.version;
    }
}

  上面的代碼作為演示用沒有把所有的方法列出來,您需要了解和關注其中一些重要的概念,案例中引入了一個新的介面“Versionable”,這個介面用于為物體模型增加樂觀鎖支撐,通過在類中引入屬性“version”,每次對物體進行變更時此欄位加1,涉及樂觀鎖的概念及使用方式可參看網路上其它文章,在DDD中,使用樂觀鎖可以說是一種最起碼的要求且并不需要付出太多的精力,還是十分推薦的,

  第二個重點內容是標識屬性“id”,每一個物體必須有一個標識屬性用于對其生命周期進行跟蹤,案例中使用了泛型表示物體的ID型別,不過仍然要求ID是可以比較的(Comparable),您可以通過重寫方法“equals”及“hashCode”來實作物體間的比較,這兩個方法一般都是成對出現,具體原因可自行參考相關文章,需要注意的是“equals”的實作,兩個物件相等不看屬性只看ID,所以代碼實作的時候只對ID做比較,針對ID的設計其實還有一個方式,就是設計一個專門表示ID的類,將ID的操作如等價判斷直接放在類中,這樣可以讓ID的設計更加優雅也能減少物體物件的責任,請看如下代碼,

public class Identity<TID extends Comparable> extends ValueModel {
    private TID id;

    @Override
    public boolean equals(Object obj) {
        if(obj == null){
            return false;
        }
        if(!(obj instanceof Identity)){
            return false;
        }
        if(obj == this){
            return true;
        }
        return this.id.compareTo(((Identity)obj).getId()) == 0;
    }
}
public abstract class EntityModel {
    private Identity<? extends Comparable> id;

    protected EntityModel(Identity<? extends Comparable> id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null || !(obj instanceof EntityModel)){
            return false;
        }
        return this.getId().equals(((EntityModel) obj).getId());
    }
}

  上述的案例在ID設計方面要比第一個版本漂亮得多,也顯得更加專業,實際在做面向物件編程的時候,將責任細化到各個小一點的物件中是一種非常常見的情況,這也是為什么我在前面說使用OOP的時候成本比較高,單一責任的目的倒是達到了,不過出現一堆稀碎的物件,組裝起來也挺費勁的,

  我們再回到物體模型的設計上,您會發現我嚴格遵循了一些原則:1)使用建構式的方式來實作對所有物件屬性的賦值,雖然沒有在賦值的時候對屬性的是否合法進行保障,但由于使用了前面所說的“內驗”的方式對物件進行驗證,也就是物件工廠在創建物體后呼叫其驗證方法“validate()”,也可以保障物體的合法性,實際上,在我寫文本篇文章時進行了代碼的走查,才發現基類“EntityModel”中未對ID的正確性進行驗證又沒有限定物件必須使用工廠創建,而是把驗證放到了持久化前的階段,這樣還是有一定的風險的,那么文章結束后我肯定需要對代碼進行調整的,創建物體的原則您需要格外注意:不論使用工廠還是建構式,一旦業務物件被成功創建就應該是合法的,不需要也不應該再呼叫其它方法進行補償(比如物件創建后手動呼叫某個初始化方法);2)物體中引入了一些通用屬性比如“status”,表示物件是活越的還是已經被廢了,在資料的角度看就是資料是否被邏輯洗掉,一般來說,我們不會對物件做物理洗掉,物體物件只要被創建且進行了持久化,就表示其曾經來到過這個世界上,只是因一些事件他已經不活越了,所以不應該將其直接干掉,

三、業務物體的設計

  上面通過代碼展示了物體基類的設計方式,在此基礎上就可以進行業務物體模型的設計,下面展示了作業流業務模型的代碼片段,其設計方式仍然遵循我們談及的規范,如果您是一個有強迫癥的設計師,可能會對“forward”方法比較糾結,通常情況下,跨領域模型的操作應當由“領域服務”來完成,不過我們這里并沒有采用這種模式,因為此段代碼是作業流的基類,往大了說算是作業流框架的一部分,在專案中引入“由領域服務完成跨領域模型的業務操作”是一個很好的規范,值得遵守,

public abstract class WorkFlowInstanceBase extends EntityModel<Long> {
    public static final long EMPTY_WORK_NODE = -1;

    private Long templateId;//作業流模板
    private Creator creator;//創建人
    private String request;//請求資訊
    private String title;//標題
    private Long currentWorkNodeId = EMPTY_WORK_NODE;//當前處理節點

 
    protected WorkFlowInstanceBase(Long id, String title, DataStatus dataStatus, LocalDateTime createdDate,
                                   LocalDateTime updatedDate, Long templateId, Creator creator, String request,
                                   Long currentWorkNodeId) {
        super(id, dataStatus, createdDate, updatedDate);
        this.title = title;
        this.templateId = templateId;
        this.creator = creator;
        this.request = request;
        this.currentWorkNodeId = currentWorkNodeId;
    }

    /**
     * 轉向下一個處理節點
     * @param comment 備注
     * @param template 模板
     * @return 處理記錄
     */
    protected ProcessRecord forward(String comment, WorkFlowTemplateBase template)
            throws InvalidOperationException {
        if (StringUtils.isEmpty(comment)) {
            throw new InvalidOperationException(OperationMessages.INVALID_COMMENT);
        }
        
        return this.forwardCore(comment, template, currentWorkNode);
    }   

    @Override
    protected void addRule(RuleManager ruleManager) {
        super.addRule(ruleManager);
        ruleManager.addRule(new ObjectNotNullRule("currentWorkNodeId", this.currentWorkNodeId, OperationMessages.INVALID_CURRENT_WORK_NODE));
        ruleManager.addRule(new ObjectNotNullRule("templateId", this.templateId, OperationMessages.INVALID_TEMPLATE));
        ruleManager.addRule(new ObjectNotNullRule("creator", this.creator, OperationMessages.INVALID_CREATOR_INFO));
        ruleManager.addRule(new EmbeddedObjectRule("creator", this.creator));
        ruleManager.addRule(new StringNotNullOrEmptyRule("request", this.request, OperationMessages.INVALID_REQUEST));
        ruleManager.addRule(new StringNotNullOrEmptyRule("title", this.title, OperationMessages.INVALID_TITLE));
    }
}

總結

  本章中所示的代碼相對簡單明了,沒有那么多的花里胡哨,別看東西少但足夠在真實的專案中使用,類似于事件溯源這種,個覺得真正需要的場景并不是很多,所以也沒有加到基類中來,我見過一些個人開發的框架,把代碼設計的特別復雜,可以說是包羅萬象,但其價值有幾何,估計也是仁者見仁、智者見智罷了,另外呢,個人建議在實踐DDD的時候,從這種簡單的途徑開始即可,自己寫一點東西能幫助您在實戰中多積累一些經驗,類似AXON這種大型框架,您別看他東西多,其實并沒有脫離DDD戰術中所說的那點事情,

  下一章我們討論內驗,較早之前我寫過驗證相關的文章,不過在決定開啟DDD系列后就將其屏蔽掉了,沒頭沒尾的不太好,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/448170.html

標籤:領域驅動設計

上一篇:將curl和json與express服務器一起使用

下一篇:JDK動態代理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more