本文是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

【DDD】领域驱动设计实践 —— Domain层实现的更多相关文章

  1. 【DDD】领域驱动设计实践 —— UI层实现

    前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来.本文是架构实现讲解的第一篇,主要 ...

  2. DDD领域驱动设计之运用层代码

    1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 3.DDD领域驱动设计之领域基础设施层 4.DDD领域驱动设计之领域服务 5.整体DEMO代码 什么是运用层,说 ...

  3. 【DDD】领域驱动设计实践 —— Application层实现

    本文是DDD框架实现讲解的第二篇,主要介绍了DDD的Application层的实现,详细讲解了service.assemble的职责和实现.文末附有github地址.相比于<领域驱动设计> ...

  4. DDD领域驱动设计和实践(转载)

    -->目录导航 一. DDD领域驱动设计介绍 1. 什么是领域驱动设计(DDD) 2. 领域驱动设计的特点 3. 如果不使用DDD? 4. 领域驱动设计的分层架构和构成要素 5. 事务脚本和领域 ...

  5. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...

  6. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...

  7. DDD 领域驱动设计-如何完善 Domain Model(领域模型)?

    上一篇:<DDD 领域驱动设计-如何 DDD?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 阅读目录: ...

  8. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)

    好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 DDD 架构设计的一些东西运用在上面,但发现了很多问题,这些在之前的短消息项目中 ...

  9. DDD领域驱动设计之领域基础设施层

    1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 其实这里说的基础设施层只是领域层的一些接口和基类而已,没有其他的如日子工具等代码,仅仅是为了说明领域层的一些基础 ...

随机推荐

  1. 线性表之何时使用ArrayList、LinkedList?

    前言 线性表不仅可以存储重复的元素,而且可以指定元素存储的位置并根据下表访问元素. List接口的两个具体实现:数组线性表类ArrayList.链表类LinkedList. ArrayList Arr ...

  2. java基于注解的redis自动缓存实现

    目的: 对于查询接口所得到的数据,只需要配置注解,就自动存入redis!此后一定时间内,都从redis中获取数据,从而减轻数据库压力. 示例: package com.itliucheng.biz; ...

  3. Java学习记录:降低耦合度

    耦合度定义 耦合度(Coupling)是对模块间关联程度的度量.耦合的强弱取决与模块间接口的复杂性.调用模块的方式以及通过界面传送数据的多少. 模块间的耦合度是指模块之间的依赖关系,包括控制关系.调用 ...

  4. NHibernate教程(14)--使用视图

    本节内容 引入 1.持久化类 2.映射文件 3.测试 结语 引入 在数据库操作中,我们除了对表操作,还有视图.存储过程等操作,这一篇和下篇来学习这些内容.这篇我们来学习如何在NHibernate中使用 ...

  5. 四,ESP8266 TCP服务器

    我要赶时间赶紧写完所有的内容....朋友的东西答应的还没做完呢!!!!!!!没想到又来了新的事情,,....... 配置模块作为TCP服务器然后呢咱们连接服务器发指令控制继电器吸合和断开 控制的指令呢 ...

  6. 【Beta】阶段 第一次Daily Scrum Meeting

    每日任务 1.本次会议为第一次 Meeting会议: 2.本次会议在周一下午16:40,课间休息时间在禹州楼召开,召开本次会议为10分钟. 一.今日站立式会议照片 二.每个人的工作 (有work it ...

  7. 201521123051《Java程序设计》第七周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 使用工具:百度脑图 2. 书面作业 1.ArrayList代码分析 1.1 解释ArrayList的contains源代码 ...

  8. Android事件机制

    一句话描述: 用户和程序之间的互动机制 什么是事件? 用户和程序交互时触发的程序操作. 只要是事件,必须具备三方面: 1 事件的发生者 2 事件接受者 3 事件触发和传递 事件处理的方法 观察者模式: ...

  9. 201521123075 《Java程序设计》第13周学习总结

    1. 本周学习总结 协议 网络中为了进行数据交换(通信)而建立的规则.标准或约定(=语义+语法+规则),比如http, ftp等 IP层协议(Internet Protocol) Internet上的 ...

  10. 201521123119《Java程序设计》第11周学习总结

    1. 本周学习总结 Q1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 Q1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问 ...