基于DDD的微服务落地
创始人
2024-05-30 15:53:52
0

DDD四层架构

对实时性要求高的强一致性业务场景,可采取分布式事务。分布式事务有性能代价,在设计时需要平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。

领域事件驱动的异步方式是分布式架构常用的设计方式,可以解决非实时性场景下的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好的解耦微服务。通过削峰填谷,实现读写分离、减轻数据库实时访问压力,提高业务吞吐量和业务处理能力。

DDD设计思想和方法

DDD知识领域

主要内容

聚合的管理

聚合根、实体和值对象的关系

聚合数据的初始化和持久化

工厂模式和仓储模式

聚合的解耦

聚合代码的解耦、跨聚合的服务调用和对象解耦

领域事件管理

领域事件实体结构、持久化和事件发布

DDD分层架构

基础层、领域层、应用层和用户接口层的协作

服务的分层与协作

实体方法、领域服务、应用服务、facade接口服务,服务的组合和编排,跨多个聚合的服务管理和协调

对象的分层和转换

DTO、DO、PO等对象在不同层的转换和实现过程

微服务间的访问

登录和认证服务

聚合根

聚合根包括聚合根属性、关联的实体和值对象以及自身的业务行为等。通过引用实体和值对象,协调聚合内的多个实体,在聚合根类方法中完成多实体的复杂业务逻辑。

充血模型:即领域模型模式。有自己的业务行为(方法),如下充血模型中提供getDuration、addHistoryApprovalInfo等方法。

贫血模型:即事务脚本模式。只有对象属性和setter/getter。

@Data
public class Leave {String id;Applicant applicant;Approver approver;LeaveType type;Status status;Date startTime;Date endTime;long duration;//审批领导的最大级别int leaderMaxLevel;ApprovalInfo currentApprovalInfo;List historyApprovalInfos;public long getDuration() {return endTime.getTime() - startTime.getTime();}public Leave addHistoryApprovalInfo(ApprovalInfo approvalInfo) {if (null == historyApprovalInfos)historyApprovalInfos = new ArrayList<>();this.historyApprovalInfos.add(approvalInfo);return this;}public Leave create(){this.setStatus(Status.APPROVING);this.setStartTime(new Date());return this;}public Leave agree(Approver nextApprover){this.setStatus(Status.APPROVING);this.setApprover(nextApprover);return this;}public Leave reject(Approver approver){this.setApprover(approver);this.setStatus(Status.REJECTED);this.setApprover(null);return this;}public Leave finish(){this.setApprover(null);this.setStatus(Status.APPROVED);this.setEndTime(new Date());this.setDuration(this.getEndTime().getTime() - this.getStartTime().getTime());return this;}
}

实体

实体有自己的属性和关联的值对象。

@Data
public class ApprovalInfo {String approvalInfoId;Approver approver;ApprovalType approvalType;String msg;long time;
}

值对象

public enum Status {APPROVING, APPROVED, REJECTED
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Approver {String personId;String personName;int level;public static Approver fromPerson(Person person){Approver approver = new Approver();approver.setPersonId(person.getPersonId());approver.setPersonName(person.getPersonName());approver.setLevel(person.getRoleLevel());return approver;}}

领域对象

如果一个业务行为由多个实体对象参与完成,就将该业务逻辑放在领域服务中实现。

实体方法:完成单一实体自身的业务逻辑,是相对简单的原子业务逻辑。

领域服务:由多个实体组合的相对复杂的业务逻辑。

@Service
@Slf4j
public class LeaveDomainService {@AutowiredEventPublisher eventPublisher;@AutowiredILeaveRepository iLeaveRepository;@AutowiredLeaveFactory leaveFactory;@Transactionalpublic void createLeave(Leave leave, int leaderMaxLevel, Approver approver) {leave.setLeaderMaxLevel(leaderMaxLevel);leave.setApprover(approver);leave.create();iLeaveRepository.save(leaveFactory.createLeavePO(leave));LeaveEvent event = LeaveEvent.create(LeaveEventType.CREATE_EVENT, leave);iLeaveRepository.saveEvent(leaveFactory.createLeaveEventPO(event));eventPublisher.publish(event);}@Transactionalpublic void updateLeaveInfo(Leave leave) {LeavePO po = leaveRepositoryInterface.findById(leave.getId());if (null == po) {throw new RuntimeException("leave不存在");}iLeaveRepository.save(leaveFactory.createLeavePO(leave));}@Transactionalpublic void submitApproval(Leave leave, Approver approver) {LeaveEvent event;if ( ApprovalType.REJECT == leave.getCurrentApprovalInfo().getApprovalType()) {leave.reject(approver);event = LeaveEvent.create(LeaveEventType.REJECT_EVENT, leave);} else {if (approver != null) {leave.agree(approver);event = LeaveEvent.create(LeaveEventType.AGREE_EVENT, leave);} else {leave.finish();event = LeaveEvent.create(LeaveEventType.APPROVED_EVENT, leave);}}leave.addHistoryApprovalInfo(leave.getCurrentApprovalInfo());iLeaveRepository.save(leaveFactory.createLeavePO(leave));iLeaveRepository.saveEvent(leaveFactory.createLeaveEventPO(event));eventPublisher.publish(event);}public Leave getLeaveInfo(String leaveId) {LeavePO leavePO = iLeaveRepository.findById(leaveId);return leaveFactory.getLeave(leavePO);}public List queryLeaveInfosByApplicant(String applicantId) {List leavePOList = iLeaveRepository.queryByApplicantId(applicantId);return leavePOList.stream().map(leavePO -> leaveFactory.getLeave(leavePO)).collect(Collectors.toList());}public List queryLeaveInfosByApprover(String approverId) {List leavePOList = iLeaveRepository.queryByApproverId(approverId);return leavePOList.stream().map(leavePO -> leaveFactory.getLeave(leavePO)).collect(Collectors.toList());}
}

在应用服务组合不同聚合的领域服务时,通过Id或参数传参,尽量避免领域对象传参,以减少聚合之间的耦合度。

领域事件

领域事件基类

@Data
public class DomainEvent {String id;Date timestamp;String source;String data;
}
@Service
public class EventPublisher {public void publish(LeaveEvent event){//send to MQ//mq.send(event);}
}

领域事件实体

@Data
public class LeaveEvent extends DomainEvent {LeaveEventType leaveEventType;public static LeaveEvent create(LeaveEventType eventType, Leave leave){LeaveEvent event = new LeaveEvent();event.setId(IdGenerator.nextId());event.setLeaveEventType(eventType);event.setTimestamp(new Date());event.setData(JSON.toJSONString(leave));return event;}
}

领域事件的执行逻辑:

  1. 执行业务逻辑,产生领域事件。

  1. 调用仓储接口,完成业务数据持久化。

  1. 调用仓储接口,完成事件数据持久化。

  1. 完成领域事件发布。

仓储模式

仓储接口

public interface ILeaveRepository {void save(LeavePO leavePO);void saveEvent(LeaveEventPO leaveEventPO);LeavePO findById(String id);List queryByApplicantId(String applicantId);List queryByApproverId(String approverId);}

仓储实现

@Repository
public class LeaveRepositoryImpl implements ILeaveRepository {@AutowiredLeaveDao leaveDao;@AutowiredApprovalInfoDao approvalInfoDao;@AutowiredLeaveEventDao leaveEventDao;public void save(LeavePO leavePO) {leaveDao.save(leavePO);leavePO.getHistoryApprovalInfoPOList().forEach(approvalInfoPO -> approvalInfoPO.setLeaveId(leavePO.getId()));approvalInfoDao.saveAll(leavePO.getHistoryApprovalInfoPOList());}public void saveEvent(LeaveEventPO leaveEventPO){leaveEventDao.save(leaveEventPO);}@Overridepublic LeavePO findById(String id) {return leaveDao.findById(id).orElseThrow(() -> new RuntimeException("leave不存在"));}@Overridepublic List queryByApplicantId(String applicantId) {List leavePOList = leaveDao.queryByApplicantId(applicantId);leavePOList.forEach(leavePO -> {List approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);});return leavePOList;}@Overridepublic List queryByApproverId(String approverId) {List leavePOList = leaveDao.queryByApproverId(approverId);leavePOList.forEach(leavePO -> {List approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);});return leavePOList;}}

这里为什么没有使用一对多、多对对呢?时间紧任务重或者并发量不高时可以使用,后期并发量起来了后,数据库将成为瓶颈。

JPA/Hibernate注解:@OneToMany、@ManyToOne、@ManyToMany;

Mybatis注解:

@Results(id="", value={@Result(column="", property="", jdbcType=JdbcType.INTEGER),

@Result(column="", property="", javaType=xx.class,one=@One(select="com.xx..XxMapper.selectById"))

})

@Results(id="",value = { @Result(property = "", column = ""),

@Result(property = "xxList", javaType=List.class, many=@Many(select=""), column = "")});

Mybatis xml: association、collection。

工厂模式

工厂模式将与业务无关的职能从聚合根中剥离,放在工厂中统一创建和初始化。

//可考虑使用MapStruct
@Service
public class LeaveFactory {public LeavePO createLeavePO(Leave leave) {LeavePO leavePO = new LeavePO();leavePO.setId(UUID.randomUUID().toString());leavePO.setApplicantId(leave.getApplicant().getPersonId());leavePO.setApplicantName(leave.getApplicant().getPersonName());leavePO.setApproverId(leave.getApprover().getPersonId());leavePO.setApproverName(leave.getApprover().getPersonName());leavePO.setStartTime(leave.getStartTime());leavePO.setStatus(leave.getStatus());List historyApprovalInfoPOList = approvalInfoPOListFromDO(leave);leavePO.setHistoryApprovalInfoPOList(historyApprovalInfoPOList);return leavePO;}public Leave getLeave(LeavePO leavePO) {Leave leave = new Leave();Applicant applicant = Applicant.builder().personId(leavePO.getApplicantId()).personName(leavePO.getApplicantName()).build();leave.setApplicant(applicant);Approver approver = Approver.builder().personId(leavePO.getApproverId()).personName(leavePO.getApproverName()).build();leave.setApprover(approver);leave.setStartTime(leavePO.getStartTime());leave.setStatus(leavePO.getStatus());List approvalInfos = getApprovalInfos(leavePO.getHistoryApprovalInfoPOList());leave.setHistoryApprovalInfos(approvalInfos);return leave;}public LeaveEventPO createLeaveEventPO(LeaveEvent leaveEvent){LeaveEventPO eventPO = new LeaveEventPO();eventPO.setLeaveEventType(leaveEvent.getLeaveEventType());eventPO.setSource(leaveEvent.getSource());eventPO.setTimestamp(leaveEvent.getTimestamp());eventPO.setData(JSON.toJSONString(leaveEvent.getData()));return eventPO;}private List approvalInfoPOListFromDO(Leave leave) {return leave.getHistoryApprovalInfos().stream().map(this::approvalInfoPOFromDO).collect(Collectors.toList());}private ApprovalInfoPO approvalInfoPOFromDO(ApprovalInfo approvalInfo){ApprovalInfoPO po = new ApprovalInfoPO();po.setApproverId(approvalInfo.getApprover().getPersonId());po.setApproverLevel(approvalInfo.getApprover().getLevel());po.setApproverName(approvalInfo.getApprover().getPersonName());po.setApprovalInfoId(approvalInfo.getApprovalInfoId());po.setMsg(approvalInfo.getMsg());po.setTime(approvalInfo.getTime());return po;}private ApprovalInfo approvalInfoFromPO(ApprovalInfoPO approvalInfoPO){ApprovalInfo approvalInfo = new ApprovalInfo();approvalInfo.setApprovalInfoId(approvalInfoPO.getApprovalInfoId());Approver approver = Approver.builder().personId(approvalInfoPO.getApproverId()).personName(approvalInfoPO.getApproverName()).level(approvalInfoPO.getApproverLevel()).build();approvalInfo.setApprover(approver);approvalInfo.setMsg(approvalInfoPO.getMsg());approvalInfo.setTime(approvalInfoPO.getTime());return approvalInfo;}private List getApprovalInfos(List approvalInfoPOList){return approvalInfoPOList.stream().map(this::approvalInfoFromPO).collect(Collectors.toList());}
}

服务的组合和编排

应用层的应用服务主要完成领域服务的组合与编排。

@Service
public class LeaveApplicationService {@AutowiredLeaveDomainService leaveDomainService;@AutowiredPersonDomainService personDomainService;@AutowiredApprovalRuleDomainService approvalRuleDomainService;/*** 创建一个请假申请并为审批人生成任务* @param leave*/public void createLeaveInfo(Leave leave){int leaderMaxLevel = approvalRuleDomainService.getLeaderMaxLevel(leave.getApplicant().getPersonType(), leave.getType().toString(), leave.getDuration());Person approver = personDomainService.findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);leaveDomainService.createLeave(leave, leaderMaxLevel, Approver.fromPerson(approver));}/*** 更新请假单基本信息* @param leave*/public void updateLeaveInfo(Leave leave){leaveDomainService.updateLeaveInfo(leave);}/*** 提交审批,更新请假单信息* @param leave*/public void submitApproval(Leave leave){//find next approverPerson approver = personDomainService.findNextApprover(leave.getApprover().getPersonId(), leave.getLeaderMaxLevel());leaveDomainService.submitApproval(leave, Approver.fromPerson(approver));}public Leave getLeaveInfo(String leaveId){return leaveDomainService.getLeaveInfo(leaveId);}public List queryLeaveInfosByApplicant(String applicantId){return leaveDomainService.queryLeaveInfosByApplicant(applicantId);}public List queryLeaveInfosByApprover(String approverId){return leaveDomainService.queryLeaveInfosByApprover(approverId);}
}

服务接口的提供

facade门面接口主要抽象出Controller Api作为OpenFeign调用的接口门面类。

/*** @author lyonardo* @Description* @createTime 2020年03月08日 15:06:00*/
@FeignClient(name = "leave-service", path = "/leave")
public interface LeaveFeignClient {@PostMapping("/submit")Response submitApproval(LeaveDTO leaveDTO);@PostMapping("/{leaveId}")Response findById(@PathVariable String leaveId);/*** 根据申请人查询所有请假单* @param applicantId* @return*/@PostMapping("/query/applicant/{applicantId}")Response queryByApplicant(@PathVariable String applicantId);/*** 根据审批人id查询待审批请假单(待办任务)* @param approverId* @return*/@PostMapping("/query/approver/{approverId}")Response queryByApprover(@PathVariable String approverId)();
}

实现类

@RestController
@RequestMapping("/leave")
@Slf4j
public class LeaveApi {@AutowiredLeaveApplicationService leaveApplicationService;@PostMapping("/create")public Response createLeaveInfo(LeaveDTO leaveDTO){Leave leave = LeaveAssembler.toDO(leaveDTO);leaveApplicationService.createLeaveInfo(leave);return Response.ok();}@PutMapping("/update")public Response updateLeaveInfo(LeaveDTO leaveDTO){Leave leave = LeaveAssembler.toDO(leaveDTO);leaveApplicationService.updateLeaveInfo(leave);return Response.ok();}@PostMapping("/submit")public Response submitApproval(LeaveDTO leaveDTO){Leave leave = LeaveAssembler.toDO(leaveDTO);leaveApplicationService.submitApproval(leave);return Response.ok();}@PostMapping("/{leaveId}")public Response findById(@PathVariable String leaveId){Leave leave = leaveApplicationService.getLeaveInfo(leaveId);return Response.ok(LeaveAssembler.toDTO(leave));}/*** 根据申请人查询所有请假单* @param applicantId* @return*/@PostMapping("/query/applicant/{applicantId}")public Response queryByApplicant(@PathVariable String applicantId){List leaveList = leaveApplicationService.queryLeaveInfosByApplicant(applicantId);List leaveDTOList = leaveList.stream().map(leave -> LeaveAssembler.toDTO(leave)).collect(Collectors.toList());return Response.ok(leaveDTOList);}/*** 根据审批人id查询待审批请假单(待办任务)* @param approverId* @return*/@PostMapping("/query/approver/{approverId}")public Response queryByApprover(@PathVariable String approverId){List leaveList = leaveApplicationService.queryLeaveInfosByApprover(approverId);List leaveDTOList = leaveList.stream().map(leave -> LeaveAssembler.toDTO(leave)).collect(Collectors.toList());return Response.ok(leaveDTOList);}
}

数据组装层

LeaveAssembler

//可使用MapStruct做对象转换和组装
public class LeaveAssembler {public static LeaveDTO toDTO(Leave leave){LeaveDTO dto = new LeaveDTO();dto.setLeaveId(leave.getId());dto.setLeaveType(leave.getType().toString());dto.setStatus(leave.getStatus().toString());dto.setStartTime(DateUtil.formatDateTime(leave.getStartTime()));dto.setEndTime(DateUtil.formatDateTime(leave.getEndTime()));dto.setCurrentApprovalInfoDTO(ApprovalInfoAssembler.toDTO(leave.getCurrentApprovalInfo()));List historyApprovalInfoDTOList = leave.getHistoryApprovalInfos().stream().map(historyApprovalInfo -> ApprovalInfoAssembler.toDTO(leave.getCurrentApprovalInfo())).collect(Collectors.toList());dto.setHistoryApprovalInfoDTOList(historyApprovalInfoDTOList);dto.setDuration(leave.getDuration());return dto;}public static Leave toDO(LeaveDTO dto){Leave leave = new Leave();leave.setId(dto.getLeaveId());leave.setApplicant(ApplicantAssembler.toDO(dto.getApplicantDTO()));leave.setApprover(ApproverAssembler.toDO(dto.getApproverDTO()));leave.setCurrentApprovalInfo(ApprovalInfoAssembler.toDO(dto.getCurrentApprovalInfoDTO()));List historyApprovalInfoDTOList = dto.getHistoryApprovalInfoDTOList().stream().map(ApprovalInfoAssembler::toDO).collect(Collectors.toList());leave.setHistoryApprovalInfos(historyApprovalInfoDTOList);return leave;}}

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...