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<ApprovalInfo> 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<Leave> queryLeaveInfosByApplicant(String applicantId) {
List<LeavePO> leavePOList = iLeaveRepository.queryByApplicantId(applicantId);
return leavePOList.stream()
.map(leavePO -> leaveFactory.getLeave(leavePO))
.collect(Collectors.toList());
}
public List<Leave> queryLeaveInfosByApprover(String approverId) {
List<LeavePO> 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<LeavePO> queryByApplicantId(String applicantId);
List<LeavePO> 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<LeavePO> queryByApplicantId(String applicantId) {
List<LeavePO> leavePOList = leaveDao.queryByApplicantId(applicantId);
leavePOList.forEach(leavePO -> {
List<ApprovalInfoPO> approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());
leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);
});
return leavePOList;
}
@Override
public List<LeavePO> queryByApproverId(String approverId) {
List<LeavePO> leavePOList = leaveDao.queryByApproverId(approverId);
leavePOList.forEach(leavePO -> {
List<ApprovalInfoPO> approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());
leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);
});
return leavePOList;
}
}
这里为什么没有使用一对多、多对对呢?时间紧任务重或者并发量不高时可以使用,后期并发量起来了后,数据库将成为瓶颈。
JPA/Hibernate注解:@.NEToMany、@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<ApprovalInfoPO> 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<ApprovalInfo> 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<ApprovalInfoPO> 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<ApprovalInfo> getApprovalInfos(List<ApprovalInfoPO> 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<Leave> queryLeaveInfosByApplicant(String applicantId){
return leaveDomainService.queryLeaveInfosByApplicant(applicantId);
}
public List<Leave> 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<Leave> leaveList = leaveApplicationService.queryLeaveInfosByApplicant(applicantId);
List<LeaveDTO> 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<Leave> leaveList = leaveApplicationService.queryLeaveInfosByApprover(approverId);
List<LeaveDTO> 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<ApprovalInfoDTO> 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<ApprovalInfo> historyApprovalInfoDTOList = dto.getHistoryApprovalInfoDTOList()
.stream()
.map(ApprovalInfoAssembler::toDO)
.collect(Collectors.toList());
leave.setHistoryApprovalInfos(historyApprovalInfoDTOList);
return leave;
}
}