• 【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

  • 相关阅读:
    暑假日报-35
    非确定性有穷状态决策自动机练习题Vol.1 题解 & 总结
    loj数列分块入门 1~9
    第12周作业
    第二阶段考试
    UOJ NOI Round 4
    事件
    爬楼梯
    构造函数输出
    比较版本号大小
  • 原文地址:https://www.cnblogs.com/daoqidelv/p/7648392.html
Copyright © 2020-2023  润新知