基于DDD的微服务落地
DDD四层架构
对实时性要求高的强一致性业务场景,可采取分布式事务。分布式事务有性能代价,在设计时需要平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。
领域事件驱动的异步方式是分布式架构常用的设计方式,可以解决非实时性场景下的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好的解耦微服务。通过削峰填谷,实现读写分离、减轻数据库实时访问压力,提高业务吞吐量和业务处理能力。
DDD设计思想和方法
DDD设计思想和方法
聚合根
聚合根包括聚合根属性、关联的实体和值对象以及自身的业务行为等。通过引用实体和值对象,协调聚合内的多个实体,在聚合根类方法中完成多实体的复杂业务逻辑。
充血模型:即领域模型模式。有自己的业务行为(方法),如下充血模型中提供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 { @Autowired EventPublisher eventPublisher; @Autowired ILeaveRepository iLeaveRepository; @Autowired LeaveFactory leaveFactory; @Transactional public 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); } @Transactional public void updateLeaveInfo(Leave leave) { LeavePO po = leaveRepositoryInterface.findById(leave.getId()); if (null == po) { throw new RuntimeException("leave不存在"); } iLeaveRepository.save(leaveFactory.createLeavePO(leave)); } @Transactional public 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; } }
领域事件的执行逻辑:执行业务逻辑,产生领域事件。调用仓储接口,完成业务数据持久化。调用仓储接口,完成事件数据持久化。完成领域事件发布。
仓储模式
仓储接口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 { @Autowired LeaveDao leaveDao; @Autowired ApprovalInfoDao approvalInfoDao; @Autowired LeaveEventDao 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); } @Override public LeavePO findById(String id) { return leaveDao.findById(id) .orElseThrow(() -> new RuntimeException("leave不存在")); } @Override public List queryByApplicantId(String applicantId) { List leavePOList = leaveDao.queryByApplicantId(applicantId); leavePOList.forEach(leavePO -> { List approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId()); leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList); }); return leavePOList; } @Override public 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 { @Autowired LeaveDomainService leaveDomainService; @Autowired PersonDomainService personDomainService; @Autowired ApprovalRuleDomainService 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 approver Person 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 { @Autowired LeaveApplicationService 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; } }
看似帅气,实则脱发严重!沙溢彭于晏白敬亭,个个心酸又好笑文丨2号探秘人编辑丨2号探秘人当今社会,要问最让年轻人焦虑的问题是什么?回答之一,必有脱发。根据卫健委发布的一组数据,我国脱发人群数量已经超过2。5亿。其中,年轻人是脱发的主力军,
双十一后,教你如何防范这些陷阱一年一度的双十一刚刚过去,许多消费者的购物战果还在递送当中,然而骗子也在伺机而动。对于下列虚假招数,消费者需要慧眼识别,防止一不留神踏入陷阱。虚假客服,诱导客户办贷款前不久,林阿姨
散文秋去冬来可入诗,凡尘冷暖皆自然作者子墨时光过得真快,季节一晃就老了,一个转身,在不经意间,我们又走过了一季秋天。仿佛,秋天里的许多的故事,还来不及细品,一个季节离去,另一个季节无缝衔接,秋天就这样悄然无声地滑了
遇见就好了,余生就算了不是所有的相遇,都能开花结果。在感情的因果里,出场顺序很重要,最先出现的人,不一定能陪你走到最后。很多感情,都败给了年轻很多遗憾,只恨相遇太晚。太早的遇见,常常热情大于爱情,却又想
生活就是一个圆至美连云港的花果山垂钓人生就是一个圆,从起点到终点。想起当年的少女时代,便喜欢静静地坐在书桌前看书,写诗。如今,经过三十多年的生活打磨,依旧还是喜欢坐在书桌的一角看书,写字,寻求的是内
白发所感所思头条创作挑战赛无论我们知觉与否,时间一直都在悄无声息地流逝,我们的身体和心理也在微妙的回应,白发的出现应证着温水煮青蛙的效应大局已定,黑发失守了。以前看着老人的白发时,想到的更多的
如果你有女儿,请告诉她结婚,尽量找这3种家庭01hr有人说婚姻不是一张彩票,哪怕是输了也不能一撕了事。婚姻需要质量,而不是数量。每增加一次婚姻,就会带来一次痛苦。很多父母,总有一种嫁出去就心安了的想法。如果女儿到了三十岁还单
新初境第二章最初之战下一念回想着刚刚被结界封印的暗黑之主暗姆斯,若有所思,却始终想不出哪里出现了问题。休息了片刻,一念法师站起,发动次元传送阵。一念进入阵中,来到暗域与星域之间的失却谷。一念站在失却谷暗
告别传统,新住宿经济向阳而生日前,亚朵集团在美国纳斯达克IPO上市,成为中国新住宿经济第一股。新住宿经济,成为近十年来中国住宿业变革的一个新注脚。在备受疫情冲击的近三年来,文旅产业加速变革与重构,其中,以轻资
万物皆数,这个世界底层逻辑从来都不难,刷新认知,你需要多读书这是宸妈2022年发布的第96篇原创内容全文共计2272字,大约需要24分钟时间阅读这两年看书看得多,个人感觉最大的收获是自己对外界新鲜事物的接受程度变高了,从一个埋头在一线教学的
为什么闭环管理的高风险人员可以调整成5天居家健康监测?日前国务院联防联控机制综合组发布关于进一步优化新冠肺炎疫情防控措施科学精准做好防控工作的通知(以下简称通知)公布进一步优化防控工作的二十条措施通知指出党中央对进一步优化防控工作的二