DDD- 领域驱动设计入门-阿里云开发者社区

开发者社区> 开发与运维> 正文

DDD- 领域驱动设计入门

简介: 云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 概述 去年4月份入职后第一次了解到DDD,当时觉得很晦涩(现在来看应该是红皮书将战略设计放在前半部分的缘由)。

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


概述

去年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
本文作者:油多坏不了菜
本文来自:“掘金”,了解相关信息可以关注“掘金”

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章