对实时性要求高的强一致性业务场景,可采取分布式事务。分布式事务有性能代价,在设计时需要平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。
领域事件驱动的异步方式是分布式架构常用的设计方式,可以解决非实时性场景下的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好的解耦微服务。通过削峰填谷,实现读写分离、减轻数据库实时访问压力,提高业务吞吐量和业务处理能力。
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;}
}
领域事件的执行逻辑:
执行业务逻辑,产生领域事件。
调用仓储接口,完成业务数据持久化。
调用仓储接口,完成事件数据持久化。
完成领域事件发布。
仓储接口
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;}}