雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
概述
去年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
本文作者:油多壞不了菜
本文來自:“
掘金”,了解相關資訊可以關注“掘金”