【DDD】領域驅動設計實踐 —— Domain層實現


       本文是DDD框架實現講解的第三篇,主要介紹了DDD的Domain層的實現,詳細講解了entity、value object、domain event、domain service的職責,以及如何識別出領域中的這些對象,並附有具體的業務建模示例。相比於《領域驅動設計》原書中的航運系統例子,社交服務系統的業務場景對於大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可參考:使用領域驅動設計思想實現業務系統

Domain層

  Domain層是具體的業務領域層,是發生業務變化最為頻繁的地方,是業務系統最核心的一層,是DDD關注的焦點和難點。這一層包含了如下一些domain object:entity、value object、domain event、domain service、factory、repository等。DDD實踐的難點其實就在於如何識別這些object。下面將一一說明他們。

domain entity

   領域實體是domain的核心成員。domain entity具有如下三個特征:

  • 唯一業務標識
  • 持有自己的業務屬性和業務行為
  • 屬性可變,有着自己的生命周期

   在社區這一業務領域中,‘帖子’就是一個業務實體,它需要有一個唯一性業務標識表征,擁有這個業務實體相關的業務屬性(作者、標題、內容等)和業務行為(關聯話題、刪帖等),同時他的狀態和內容可以不斷發生變化。

   示例代碼如下:

public class Post {
    
    /**
    * 帖子id
    */
    private long id; //1、‘帖子’實體有唯一業務標識
    /**
     *帖子作者
     */  
    private long authorId;    
    /**
     * 帖子標題
     */
    private String title;//2、‘帖子’實體擁有自己的業務屬性
    /**
     * 帖子源內容
     */
    private String sourceContent;
    /**
     * 發帖時間
     */
    private Timestamp postingTime;    
    /**
     * 帖子狀態
     * NOTE:使用enum實現,限定status的字典值
     * @see com.dqdl.community.domain.model.post.PostStatus
     */
    private PostStatus status;
    /**
     * 帖子作者
     */
    private PostAuthor postAuthor;
    
    /**
     * 帖子加入的話題
     */
    private Set<TopicPost> topics = new HashSet<TopicPost>();
    
    private Post() {
        this.postingTime = new Timestamp(System.currentTimeMillis());        
    }
    
    public Post(long id) {
        this.setId(id);
    }
    
    public Post(long authorId, String title, String sourceContent) {
        this();
        this.setAuthorId(authorId);
        this.setTitle(title);
        this.setSourceContent(sourceContent);
        this.setPostAuthor(new PostAuthor(authorId));
    }
    
    /**
     * 刪除帖子
     */
    public void delete() {
        this.setStatus(PostStatus.HAS_DELETED);//3、帖子的狀態可以改變
    }
        
    /**
     * 將帖子關聯話題 
     * @param topicIds 話題集合
     */
    public void joinTopics(String topicIds) throws BusinessException{//2、‘帖子’實體擁有自己的業務行為
        if(StringUtils.isEmpty(topicIds)) {
            return;
        }
        String[] topicIdArray = topicIds.split(CommonConstants.COMMA);
        for(int i=0; i<topicIdArray.length; i++) {
            TopicPost topicPost = new TopicPost(Long.valueOf(topicIdArray[i]), this.getId());
            this.topics.add(topicPost);
            if(topicSize() > MAX_JOINED_TOPICS_NUM) {
                throw new BusinessException(ReturnCode.ONE_POST_MOST_JOIN_INTO_FIVE_TOPICS);
            }
        }
    }
//......

 

value object

       領域值對象。value object是相對於domain entity來講的,對照起來value object有如下特征:

  • 可以有唯一業務標識    【區別於domain entity】
  • 持有自己的業務屬性和業務行為 【同domain entity】
  • 一旦定義,他是不可變的,它通常是短暫的,這和java中的值對象(基本類型和String類型)類似 【區別於domain entity】

  比如社區業務領域中,‘帖子的置頂信息’可以理解為是一個值對象,不需要為這一值對象定義獨立的業務唯一性標識,直接使用‘帖子id‘便可表征,同時,它只有’置頂狀態‘和’置頂位置‘,一旦其中一個屬性需要發生變化,則重建值對象並賦值給’帖子‘實體的引用,不會對領域帶來任何負面影響。

  代碼示例:(TODO:關於PostTopInfo 這個value object的使用,示例代碼中暫未涉及。)

/**
 * 帖子置頂消息,value object
 * @author daoqidelv
 * @createdate 2017年10月10日
 */
public class PostTopInfo {
    /**
     * 帖子id
     */
    private long postId;
    /**
     * 置頂標志。true -- 置頂, false -- 不置頂。
     */
    private boolean isTop;
    /**
     * 置頂位置,當isTop == true時,該字段有意義。
     */
    private int topIndex;
    
    public PostTopInfo(long postId, boolean isTop, int topIndex) {
        this.setPostId(postId);
        this.setTop(isTop);
        this.setTopIndex(topIndex);
    }

    public long getPostId() {
        return postId;
    }

    public void setPostId(long postId) {
        this.postId = postId;
    }

    public boolean isTop() {
        return isTop;
    }

    public void setTop(boolean isTop) {
        this.isTop = isTop;
    }

    public int getTopIndex() {
        return topIndex;
    }

    public void setTopIndex(int topIndex) {
        this.topIndex = topIndex;
    }

}

 

domain service

   領域服務。區別於應用服務,他屬於業務領域層。可以認為,如果某種行為無法歸類給任何實體/值對象,則就為這些行為建立相應的領域服務即可。傳統意義上的util static方法中,涉及到業務邏輯的部分,都可以考慮歸入domain service。

  比如:‘社區’這一業務領域中的‘內容過濾’這一模塊,便是領域服務,他不只屬於Post實體,還會被用於評論(Comment)實體中,故我們將他獨立成domain service。

  domain service的實現和使用的示例代碼請參考:【DDD】業務建模實踐 —— 發布帖子 中的‘示例代碼’這一節。

domain event

   領域事件。領域中產生的一些消息事件,可以在性能和解耦層面得到好處。我們通常借助於消息中間件,通過事件通知/訂閱的方式落地。

  在‘社區’業務領域中,‘發帖’之后,會同時為帖子作者生成一個‘發帖動態’,這個‘生成發帖動態’場景並不同步完成,而是通過領域事件發布異步完成。‘發帖’創建Post實體后,發布一個‘發帖動態’領域事件(PostingDynamic),‘動態’(Dynamic)相關服務消費該領域事件,並生成Dynamic實體。

  示例代碼暫未給出。

domain factory

   領域對象工廠。用於復雜領域對象的創建/重建。重建是指通過respostory加載持久化對象后,重建領域對象。

  示例代碼中暫未涉及,試實際情況而定是否引入factory。

repository

  倉庫。我們將倉庫的接口定義歸類在domain層,因為他和domain entity聯系緊密。倉庫接口定義了和基礎實施的持久化層交互契約,完成領域對應的增刪改查操作。domain層的repository只是定義契約的接口,實際實現仍然由infrastructure完成。

  倉庫的實際實現根據不同的存儲介質而不同,可以是redis、oracle、mongodb等。具體倉庫的實現會講給infrastructure層完成,我們會在下一篇blog中詳細闡述repository的實現。

  對於repository的接口定義,建議規范接口名命名,比如:查詢都叫着query等等,減小溝通成本。

  示例代碼只包含了‘社區’領域模型中Post實體相關的repository接口定義,如下:

  

public interface IPostRepository {
    
    Post query(long postId);
    
    int save(Post post);
    
    int delete(Post post);

}

 

領域建模示例

  接下來附上‘社區’業務領域中‘帖子’實體建模過程的blog,講述了如何通過不斷迭代完善業務模型,希望對你有用:

 

demo

  此demo的代碼已上傳至github,歡迎下載和討論,但拒絕被用於任何商業用途。

  github地址:https://github.com/daoqidelv/community-ddd-demo/tree/master

  branch:master


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM