19、基于DDD的微服务代码详解

avatar
作者
猴君
阅读量:0

本章将深入探讨如何基于领域驱动设计(DDD)开发微服务代码,并提供具体的代码示例和详细解释。我们将基于第十八章中的请假案例进行讲解,确保每个细节都不放过。

1、项目背景

回顾第十八章中请假案例的需求和设计,我们已经拆分出两个微服务:请假服务和考勤服务。请假服务的核心业务流程如下:

  1. 请假人填写请假单提交审批。
  2. 根据请假人身份、请假类型和请假天数进行校验并确定审批规则。
  3. 根据审批规则确定审批人,逐级提交上级审批,核批通过则完成审批,核批不通过则退回申请人。

请假微服务使用了许多DDD的设计思想和方法,如聚合、实体、值对象、领域服务和仓储模式。

2、聚合中的对象

请假微服务包含三个聚合:请假(leave)、人员(person)和审批规则(rule)。我们将详细解释每个聚合中的对象及其职责。

2.1、聚合根

聚合根是聚合的入口,负责维护聚合内所有对象的一致性。对于请假聚合,LeaveApplication是聚合根。

聚合根示例代码:LeaveApplication.java

public class LeaveApplication {     private String leaveId;     private String applicantId;     private String type;     private int days;     private String status;      public LeaveApplication(String leaveId, String applicantId, String type, int days) {         this.leaveId = leaveId;         this.applicantId = applicantId;         this.type = type;         this.days = days;         this.status = "PENDING";     }      public void approve() {         if (!"PENDING".equals(this.status)) {             throw new IllegalStateException("Leave application is not in a pending state");         }         this.status = "APPROVED";     }      public void reject() {         if (!"PENDING".equals(this.status)) {             throw new IllegalStateException("Leave application is not in a pending state");         }         this.status = "REJECTED";     }      // Getters and setters } 
2.2、实体

实体是具有唯一标识的对象,其生命周期和状态会发生变化。在请假聚合中,LeaveApplication本身也是一个实体,因为它具有唯一的leaveId

2.3、值对象

值对象是不可变的对象,只包含属性,用于描述领域中的特征。在请假聚合中,可以定义LeaveType作为值对象,表示请假的类型。

值对象示例代码:LeaveType.java

public class LeaveType {     private final String type;      public LeaveType(String type) {         this.type = type;     }      public String getType() {         return type;     }      @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getClass() != o.getClass()) return false;         LeaveType leaveType = (LeaveType) o;         return Objects.equals(type, leaveType.type);     }      @Override     public int hashCode() {         return Objects.hash(type);     } } 
2.4、领域服务

领域服务封装核心业务逻辑,实现需要多个实体协作的领域逻辑。在请假聚合中,审批逻辑可以被封装到一个领域服务中。

领域服务示例代码:LeaveApprovalService.java

public class LeaveApprovalService {     private final LeaveRepository leaveRepository;      public LeaveApprovalService(LeaveRepository leaveRepository) {         this.leaveRepository = leaveRepository;     }      public void approveLeave(String leaveId) {         LeaveApplication leaveApplication = leaveRepository.findById(leaveId);         if (leaveApplication == null) {             throw new IllegalArgumentException("Leave application not found");         }         leaveApplication.approve();         leaveRepository.save(leaveApplication);     }      public void rejectLeave(String leaveId) {         LeaveApplication leaveApplication = leaveRepository.findById(leaveId);         if (leaveApplication == null) {             throw new IllegalArgumentException("Leave application not found");         }         leaveApplication.reject();         leaveRepository.save(leaveApplication);     } } 
3、领域事件

领域事件是领域驱动设计中解耦的重要手段,通过事件机制实现领域对象之间的松耦合。

3.1、领域事件基类

领域事件基类定义了领域事件的基本结构和属性。

领域事件基类示例代码:DomainEvent.java

public abstract class DomainEvent {     private final LocalDateTime occurredOn;      protected DomainEvent() {         this.occurredOn = LocalDateTime.now();     }      public LocalDateTime getOccurredOn() {         return occurredOn;     } } 
3.2、领域事件实体

具体的领域事件实体继承自领域事件基类,表示具体的业务事件。

领域事件实体示例代码:LeaveApprovedEvent.java

public class LeaveApprovedEvent extends DomainEvent {     private final String leaveId;     private final String applicantId;      public LeaveApprovedEvent(String leaveId, String applicantId) {         this.leaveId = leaveId;         this.applicantId = applicantId;     }      public String getLeaveId() {         return leaveId;     }      public String getApplicantId() {         return applicantId;     } } 
3.3、领域事件的执行逻辑

领域事件的执行逻辑可以通过事件发布器和事件处理器实现。

事件发布器示例代码:EventPublisher.java

public class EventPublisher {     private final List<EventSubscriber> subscribers = new ArrayList<>();      public void subscribe(EventSubscriber subscriber) {         subscribers.add(subscriber);     }      public void publish(DomainEvent event) {         for (EventSubscriber subscriber : subscribers) {             subscriber.handle(event);         }     } } 

事件处理器示例代码:LeaveApprovedEventHandler.java

public class LeaveApprovedEventHandler implements EventSubscriber {     @Override     public void handle(DomainEvent event) {         if (event instanceof LeaveApprovedEvent) {             LeaveApprovedEvent leaveApprovedEvent = (LeaveApprovedEvent) event;             // 处理请假批准事件的逻辑         }     } } 
3.4、领域事件数据持久化

为了保证事件的持久化,可以将事件存储到数据库中。

事件持久化示例代码:EventStore.java

public class EventStore {     private final List<DomainEvent> events = new ArrayList<>();      public void save(DomainEvent event) {         events.add(event);     }      public List<DomainEvent> findAll() {         return new ArrayList<>(events);     } } 
4、仓储模式

仓储模式用于管理聚合的持久化和检索,通过依赖倒置原则实现基础资源和业务逻辑的解耦。

4.1、DO与PO对象的转换

在持久化过程中,需要将领域对象(DO)转换为持久化对象(PO),以适应不同的持久化需求。

DO与PO转换示例代码:LeaveMapper.java

public class LeaveMapper {     public static LeaveApplication toDomain(LeavePO po) {         return new LeaveApplication(po.getLeaveId(), po.getApplicantId(), po.getType(), po.getDays());     }      public static LeavePO toPersistence(LeaveApplication domain) {         LeavePO po = new LeavePO();         po.setLeaveId(domain.getLeaveId());         po.setApplicantId(domain.getApplicantId());         po.setType(domain.getType());         po.setDays(domain.getDays());         return po;     } } 
4.2、仓储实现逻辑

仓储实现负责具体的持久化操作,可以使用JPA或其他ORM框架。

仓储实现示例代码:LeaveRepository.java

public interface LeaveRepository {     LeaveApplication findById(String leaveId);     void save(LeaveApplication leaveApplication); }  @Repository public class JpaLeaveRepository implements LeaveRepository {     @Autowired     private LeaveJpaRepository leaveJpaRepository;      @Override     public LeaveApplication findById(String leaveId) {         LeavePO po = leaveJpaRepository.findById(leaveId).orElse(null);         return po == null ? null : LeaveMapper.toDomain(po);     }      @Override     public void save(LeaveApplication leaveApplication) {         LeavePO po = LeaveMapper.toPersistence(leaveApplication);         leaveJpaRepository.save(po);     } }  public interface LeaveJpaRepository extends JpaRepository<LeavePO, String> {} 
5、 工厂模式

工厂模式用于创建复杂的聚合根对象,封装其创建过程,确保对象的一致性。

工厂模式示例代码:LeaveFactory.java

public class LeaveFactory {     public static LeaveApplication createLeave(String applicantId  , String type, int days) {         String leaveId = UUID.randomUUID().toString();         return new LeaveApplication(leaveId, applicantId, type, days);     } } 
6、服务的组合与编排

服务的组合与编排通过应用服务实现,将领域服务和基础服务组合起来,实现复杂的业务流程。

应用服务示例代码:LeaveApplicationService.java

@Service public class LeaveApplicationService {     @Autowired     private LeaveRepository leaveRepository;     @Autowired     private LeaveApprovalService leaveApprovalService;      public void applyLeave(String applicantId, String type, int days) {         LeaveApplication leaveApplication = LeaveFactory.createLeave(applicantId, type, days);         leaveRepository.save(leaveApplication);     }      public void approveLeave(String leaveId) {         leaveApprovalService.approveLeave(leaveId);     }      public void rejectLeave(String leaveId) {         leaveApprovalService.rejectLeave(leaveId);     } } 
7、微服务拆分时的代码调整

在微服务架构演进过程中,需要对代码进行重构和调整,以适应新的架构需求。

7.1、微服务拆分前的代码

在拆分前,所有功能都集中在一个单体应用中,代码耦合度高,难以维护和扩展。

public class LeaveApplicationService {     // 包含所有业务逻辑和持久化操作 } 
7.2、微服务拆分后的代码

在拆分后,功能模块化,代码解耦,每个微服务独立负责特定的业务领域。

public class LeaveApplicationService {     @Autowired     private LeaveRepository leaveRepository;     @Autowired     private LeaveApprovalService leaveApprovalService;      // 只负责业务逻辑,持久化操作由仓储负责 } 
8、服务接口的提供

微服务对外提供接口,以便其他服务或前端应用进行调用。接口设计需要考虑到数据传输对象(DTO)和视图对象(VO)的转换。

8.1、facade接口

facade接口用于封装复杂的业务逻辑,对外提供统一的服务接口。

facade接口示例代码:LeaveFacade.java

@RestController @RequestMapping("/leaves") public class LeaveFacade {     @Autowired     private LeaveApplicationService leaveApplicationService;      @PostMapping     public ResponseEntity<Void> applyLeave(@RequestBody LeaveDto leaveDto) {         leaveApplicationService.applyLeave(leaveDto.getApplicantId(), leaveDto.getType(), leaveDto.getDays());         return new ResponseEntity<>(HttpStatus.CREATED);     }      @PutMapping("/{leaveId}/approve")     public ResponseEntity<Void> approveLeave(@PathVariable String leaveId) {         leaveApplicationService.approveLeave(leaveId);         return new ResponseEntity<>(HttpStatus.OK);     }      @PutMapping("/{leaveId}/reject")     public ResponseEntity<Void> rejectLeave(@PathVariable String leaveId) {         leaveApplicationService.rejectLeave(leaveId);         return new ResponseEntity<>(HttpStatus.OK);     } } 
8.2、DTO数据组装

DTO用于数据传输,简化前端与后端的交互,避免领域对象的直接暴露。

DTO示例代码:LeaveDto.java

public class LeaveDto {     private String applicantId;     private String type;     private int days;      // Getters and setters } 
9、微服务解耦策略小结

通过本章代码详解,我们了解了用DDD设计和开发的微服务代码具体实现,重点关注了聚合、实体、值对象、领域服务、领域事件、仓储模式和工厂模式等DDD概念的实现细节。我们还探讨了微服务拆分过程中的代码调整和服务接口的设计。

解耦策略总结

  1. 聚合内解耦:通过聚合根管理聚合内对象的一致性,避免直接引用。
  2. 聚合间解耦:通过事件总线实现聚合间的异步通信,避免直接依赖。
  3. 服务解耦:通过领域服务和应用服务分层实现业务逻辑和持久化操作的解耦。
  4. 数据传输解耦:通过DTO和VO实现前后端的数据解耦。

本章小结

本章详细介绍了基于DDD的微服务代码实现,包括聚合中的对象、领域事件、仓储模式、工厂模式、服务的组合与编排以及微服务拆分时的代码调整。通过这些具体的代码示例和解释,读者可以深入理解DDD在微服务开发中的应用,并在实际项目中灵活运用这些知识和技能。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!