天天看點

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

本文作者:油多壞不了菜

本文來自:“

掘金

”,了解相關資訊可以關注“掘金”