云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!
概述
去年4月份入职后第一次了解到DDD,当时觉得很晦涩(现在来看应该是红皮书将战略设计放在前半部分的缘由)。今年4月份的时候想学学系统设计方面的知识,便开始静下心来好好研究了两个月,感觉收益颇多。
DDD是什么
DDD是一种'面向对象'的软件设计思想。
领域驱动设计DDD作为一种软件设计方式, 有利于创造一个可测试的、可伸缩的、组织良好的软件模型
贫血症与失忆症
写Java差不多刚好两年, 接触过的项目中,几乎所有的业务逻辑都是写在service里面,对象都只是数据持有器(POJO),这些没有任何业务方法的对象就是贫血对象。
在这样的系统中,代码里面往往充斥者大量与业务无关的getter、setter方法,开发人员会将将大量的精力放在数据映射上面,而不是真正有价值的对象行为。
下面是一个更新用户的例子:
public void updateUser(String userId, String firstName, String lastName, String mobile,String address, Integer sex, String avator, ...) {
User user = new User();
if(userId != null) {
user.setUserId(userId);
}
if(firstName != null){
user.setFirstName(firstName);
}
if(lastName != null){
user.setLastName(lastName);
}
if(mobile != null){
user.setMobile(mobile);
}
if(address != null){
user.setAddress(address);
}
if(sex != null){
user.setSex(sex);
}
if(avator != null){
user.setAvator(avator);
}
...
userDao.updateUser(user);
}
首先:这个方法的业务意图不明确。应用层应该一个方法对应一个明确的业务用例,如更新用户手机号, 更新居住地址等。
其次:方法的实现增加了潜在的复杂性。 我们应该怎么测试这段代码呐?这么多if, 你确定你能愉快的修改代码吗,内心是不是很排斥?
最后:User对象只是一个数据持有器。
这种情况也被称为由贫血症导致的失忆症。
如何DDD
通用语言
通用语言是团队之间用于交流的语言,一般指当前业务场景提炼出来的领域术语、词组和句子。比如绩效系统里"271排名"就是一种领域术语。
限界上下文
限界上下文可以看成是整个应用程序之内的一个概念性边界。这个边界之内的每种领域术语、词组或句子 --- 也即通用语言,都有确定的上下文含义。而在边界之外,这些术语可能有不同的意思。
比如盘点,当HR说盘点时是对员工进行盘点;而IT说盘点的时候,是对设备进行盘点。两者的上下文是完全不一样的,盘点的语义自然不同。
以日程管理系统开始DDD学习
部分用例
- 企业员工发起一个日程,包含日程的主题、开始时间、结束时间、参与人员。
- 发起人修改日程时间
- 参与人确认日程
- 员工查看自己当天的所有日程
传统面向过程的写法
/**
创建日程:1.日程表中插入一条记录, 2.人关联日程
**/
@Transactional
public void createSchedule(String title, Date startTime, Date endTime, List<Long> participantIds){
Long sponsorId = loginUser.get().getUserId();
//创建日程
Schedule schedule = new Schedule();
schedule.setTitle(title);
schedule.setStartTime(startTime);
schedule.setEndTime(endTime);
schedule.setSponsorId(sponsorId);
Long scheduleId = scheduelDao.insert(schedule);
//人关联日程
List<UserSchedule> userSchedules = new LinkedList();
for(participantId : participantIds){
userSchedules.add(new UserSchedule(participantId, scheduleId, 0));
}
if(!userSchedules.isEmpty()){
userScheduleDao.batchInsert(userSchedules);
}
}
这样写其实也暴露了底层数据结构
DDD写法
日程对象
public class Schedule{
private String title;
private Date startTime;
private Date endTime;
private User sponsor;
Set<User> participants;
Set<User> confirmUsers;
Set<User> refuseUsers;
/**
通过构造函数创建一个日程
**/
pubulic Schedule(String title, Date startTime, Date endTime, User sponsor, Set<User> participants){
if(title == null || title == ""){
throw new IllegalArgumentException("schedule title empty");
}
if(startTime == null || endTime == null || !startTime.before(endTime)){
throw new IllegalArgumentException("schedule time range invalid.");
}
if(sponsor == null){
throw new IllegalArgumentException("schedule sponsor not found");
}
//remove duplicate
participants = participants.strem().filter(participant -> !participant.equals(sponsor)).collecot(Collectors.toSet());
this.title = title;
this.startTime = startTime;
this.endTime = endTime;
this.participants = participants;
}
/**
更新日程时间
**/
void updateTimeRange(Date startTime, Date endTime){
if(startTime == null || endTime == null || !startTime.before(endTime)){
throw new IllegalArgumentException("schedule time range invalid.");
}
this.startTime = startTime;
this.endTime = endTime;
}
/**
日程确认
**/
void confirm(User participant){
if(!this.participants.contains(participant)){
throw new IllegalArgumentException("you are not invited, can`t confirm");
}
if(this.confirmUsers.contains(participant)){
throw new IllegalArgumentException("you have confirmed");
}
this.confirmUsers.add(participant);
}
}
应用层 -- ApplicationService
/**
创建日程
**/
@Transactional
public void createSchedule(String title, Date startTime, Date endTime, List<Long> participantIds){
Long sponsorId = loginUser.get().getUserId();
User sponsor = useRepository.userOfId(sponsorId);
List<User> participants = useRepository.usersOfIds(participantIds);
Schedule schedule = new Schedule(title, startTime, endTime, sponsor, participants);
scheduleRepository.add(schedule);
}
/**
更新日程时间
**/
@Transactional
public void updateScheduleTime(Long scheduleId, Date startTime, Date endTime){
Schedule schedule = scheduleRepository.scheduleOfId(scheduleId);
schedule.updateTimeRange(startTime, endTime);
scheduleRepository.save(schedule);
}
/**
确定日程
**/
@Transactional
public void confirmSchedule(Long scheduleId){
Long userId = loginUser.get().getUserId();
User user = useRepository.userOfId(userId);
Schedule schedule = scheduleRepository.scheduleOfId(scheduleId);
schedule.confirm(user);
scheduleRepository.save(schedule);
}
The end
通过上面日程管理的demo,你是否可以重构updateUser方法呐?
【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK
原文发布时间:2020-06-15
本文作者:油多坏不了菜
本文来自:“掘金”,了解相关信息可以关注“掘金”