<返回更多

基于DDD的微服务落地

2023-03-08  今日头条  天之道居
加入收藏

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

领域事件的执行逻辑:

  1. 执行业务逻辑,产生领域事件。
  2. 调用仓储接口,完成业务数据持久化。
  3. 调用仓储接口,完成事件数据持久化。
  4. 完成领域事件发布。

仓储模式

仓储接口

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;
	}
}
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>