【DDD 9】领域驱动设计实践 —— Domain层实现

【DDD】领域驱动设计实践 —— Domain层实现

目录

【DDD】领域驱动设计实践 —— Domain层实现

1. Domain层

2. domain entity

3. value object

4. domain service

5. domain event

6. domain factory

7. repository

8. 领域建模示例

9. demo


正文

       本文是DDD框架实现讲解的第三篇,主要介绍了DDD的Domain层的实现,详细讲解了entity、value object、domain event、domain service的职责,以及如何识别出领域中的这些对象,并附有具体的业务建模示例。相比于《领域驱动设计》原书中的航运系统例子,社交服务系统的业务场景对于大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可参考:使用领域驱动设计思想实现业务系统

1. Domain层

  Domain层是具体的业务领域层,是发生业务变化最为频繁的地方,是业务系统最核心的一层,是DDD关注的焦点和难点。这一层包含了如下一些domain object:entity、value object、domain event、domain service、factory、repository等。DDD实践的难点其实就在于如何识别这些object。下面将一一说明他们。

2. 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);
            }
        }
    }
    //......

复制代码

3. 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;
    }

}

复制代码

4. domain service

   领域服务。区别于应用服务,他属于业务领域层。可以认为,如果某种行为无法归类给任何实体/值对象,则就为这些行为建立相应的领域服务即可。传统意义上的util static方法中,涉及到业务逻辑的部分,都可以考虑归入domain service。

  比如:‘社区’这一业务领域中的‘内容过滤’这一模块,便是领域服务,他不只属于Post实体,还会被用于评论(Comment)实体中,故我们将他独立成domain service。

  domain service的实现和使用的示例代码请参考:【DDD】业务建模实践 —— 发布帖子 中的‘示例代码’这一节。

5. domain event

   领域事件。领域中产生的一些消息事件,可以在性能和解耦层面得到好处。我们通常借助于消息中间件,通过事件通知/订阅的方式落地。

  在‘社区’业务领域中,‘发帖’之后,会同时为帖子作者生成一个‘发帖动态’,这个‘生成发帖动态’场景并不同步完成,而是通过领域事件发布异步完成。‘发帖’创建Post实体后,发布一个‘发帖动态’领域事件(PostingDynamic),‘动态’(Dynamic)相关服务消费该领域事件,并生成Dynamic实体。

  示例代码暂未给出。

6. domain factory

   领域对象工厂。用于复杂领域对象的创建/重建。重建是指通过respostory加载持久化对象后,重建领域对象。

  示例代码中暂未涉及,试实际情况而定是否引入factory。

7. 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);

}

复制代码

8. 领域建模示例

  接下来附上‘社区’业务领域中‘帖子’实体建模过程的blog,讲述了如何通过不断迭代完善业务模型,希望对你有用:

9. demo

  此demo的代码已上传至github,欢迎下载和讨论,但拒绝被用于任何商业用途。

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

  branch:master

猜你喜欢

转载自blog.csdn.net/zhuiqiuuuu/article/details/87965810