基于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;}}

相关内容

热门资讯

管门口的金毛四年级作文【推荐... 管门口的金毛四年级作文 篇一我家门口有一只非常可爱的金毛犬,它是我们的守门员,每天都在门口忠实地守卫...
我的压岁钱小学四年级作文【通... 我的压岁钱小学四年级作文 篇一我的压岁钱春节是我最喜欢的节日,因为我可以收到压岁钱。每年过年的时候,...
致那份友谊小学作文(推荐3篇... 致那份友谊小学作文 篇一友谊的力量亲爱的友谊小学的老师们和同学们:我是一名来自友谊小学的学生,今天我...
为自己喝彩小学生作文【精简6... 为自己喝彩小学生作文 篇一我是一名小学生,每天都在学校度过快乐的时光。我喜欢上学,因为学校给了我很多...
我生病了小学作文【精简6篇】 我生病了小学作文 篇一我生病了前几天,我不知道怎么了,突然感觉身体不舒服。我感到头晕目眩,喉咙痛得像...
新学期新打算小学作文450字... 新学期新打算篇一:我要努力学习新的学期开始了,我制定了新的打算,那就是要努力学习。我相信只有努力学习...
我学会了西红柿炒鸡蛋小学作文... 我学会了西红柿炒鸡蛋小学作文 篇一我学会了西红柿炒鸡蛋上周,我学会了一道简单又美味的菜——西红柿炒鸡...
花朵的小学作文【最新3篇】 花朵的小学作文 篇一花朵的奇妙世界花朵是大自然的美丽礼物,它们以各种各样的颜色和形状装点着我们的环境...
小学生赏花的作文【通用4篇】 小学生赏花的作文 篇一春天是一个充满美丽花朵的季节,我非常喜欢春天。每当春天来临,我就会和家人一起去...
中秋之夜小学生作文【优选3篇... 中秋之夜小学生作文 篇一中秋之夜,月亮圆圆的,像一块白玉挂在天空中。我和爸爸妈妈一起出门,欣赏美丽的...
油面筋塞肉小学作文(推荐3篇... 油面筋塞肉小学作文 篇一我喜欢吃美食,尤其是一些特色的小吃。最近,我发现了一种非常好吃的小吃,那就是...
学游泳的小学作文(实用3篇) 学游泳的小学作文 篇一学游泳的小学作文大家好!我是小明,今天我要给大家分享一下我学游泳的经历。我是一...
小学生作文老师我想对你说【最... 小学生作文老师我想对你说 篇一尊敬的老师:您好!我是您的学生小明。我想借这篇作文向您表达我的感激之情...
一次有趣的实验小学生作文80... 一次有趣的实验篇一昨天,我参加了一次非常有趣的实验。老师让我们小组一起进行,我非常期待这个实验的结果...
春天小学一年级作文300字【... 春天小学一年级作文300字 篇一我的春天春天来了,大地上百花盛开,绿草如茵。我喜欢春天,因为春天是个...
校园的一角的作文【优选6篇】 校园的一角的作文 篇一校园的一角在校园的一角,有一个小花园,是我最喜欢的地方。虽然它不大,但却别有一...
参观科技馆的小学作文400字... 参观科技馆的小学作文400字 篇一:奇妙的科技世界我参观了我们学校附近的科技馆,这里展示了许多令人惊...
值得的学生作文【实用3篇】 值得的学生作文 篇一突破自我,迈向成功作为一名学生,我们应该时刻保持一种积极向上的心态,勇于追求进步...
走进直播间小学作文(最新4篇... 走进直播间小学作文 篇一近年来,随着互联网技术的快速发展,直播已经成为了一种非常流行的媒体形式。除了...
我的学校小学作文350字【精... 我的学校小学作文350字 篇一我所在的学校是一所小学,位于市区的中心地带。学校占地面积较小,但是设施...