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

本章将深入探讨如何基于领域驱动设计(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;}@Overridepublic 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);}@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate LeaveJpaRepository leaveJpaRepository;@Overridepublic LeaveApplication findById(String leaveId) {LeavePO po = leaveJpaRepository.findById(leaveId).orElse(null);return po == null ? null : LeaveMapper.toDomain(po);}@Overridepublic 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 {@Autowiredprivate LeaveRepository leaveRepository;@Autowiredprivate 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 {@Autowiredprivate LeaveRepository leaveRepository;@Autowiredprivate LeaveApprovalService leaveApprovalService;// 只负责业务逻辑,持久化操作由仓储负责
}
8、服务接口的提供

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

8.1、facade接口

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

facade接口示例代码:LeaveFacade.java

@RestController
@RequestMapping("/leaves")
public class LeaveFacade {@Autowiredprivate LeaveApplicationService leaveApplicationService;@PostMappingpublic 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在微服务开发中的应用,并在实际项目中灵活运用这些知识和技能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/877338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“八股文”在实际工作中是助力、阻力还是空谈?

一&#xff1a;浅谈 关于“八股文”在程序员面试中的重要性和实际工作中的作用&#xff0c;确实是一个引发广泛讨论的话题。以下是我对这个问题的看法&#xff1a; 1. “八股文”的定义与特征 “八股文”通常指的是面试中常见的标准化问答或经典理论知识&#xff0c;例如…

Apollo:源码分析之cyber/mainboard启动入口介绍-my write, test ok

软件结构图 cyber入口 cyber的入口在"cyber/mainboard"目录中: ├── mainboard.cc // 主函数 ├── module_argument.cc // 模块输入参数 ├── module_argument.h ├── module_controller.cc // 模块加载,卸载 └── module_controller.…

idea如何配置tomcat

1&#xff0c;点击Run---EDit Configurations... 2.点击左侧“”号&#xff0c;找到Tomcat Server---Local&#xff08;若是没有找到Tomcat Server 可以点击最后一行 34 items more&#xff09; 3.在Tomcat Server -> Unnamed -> Server -> Application server项目下&…

JumpServer关闭admin mfa验证

背景 因为上一次启动了mfa验证&#xff0c;但是没有验证就关机重启&#xff0c;导致再开机输入密码后需要mfa绑定&#xff0c;但是怎么也无法绑定成功&#xff0c;导致无法登录。 故希望通过后台取消mfa的验证 解决方法 1. 进入docker docker exec -it jms_core /bin/bash…

关于Docker Engine AuthZ 插件授权绕过漏洞 (CVE-2024-41110)

一、漏洞概述 漏洞名称&#xff1a;Docker Engine AuthZ 插件授权绕过漏洞 &#xff08;CVE-2024-41110&#xff09; 漏洞等级&#xff1a;高危 漏洞描述&#xff1a;DockerEngine是Docker的核心组件&#xff0c;是一 个开源的容器引擎&#xff0c;负责构建、运行和管理容器…

蓝牙BlueZ验证使用记录

最近使用的一款AICSemi AIC8800D8芯片做的WiFiBT二合一模组&#xff0c;该模组WiFi使用SDIO通信&#xff0c;BT使用UART通信&#xff0c;供应商丢了一份驱动&#xff0c;包含了三个目录&#xff1a;aic8800_bsp、aic8800_fdrv和aic8800_btlpm&#xff0c;而蓝牙部分提供了lbh_s…

【Vue】权限控制

权限管理 分类&#xff1a; 页面权限功能(按钮)权限接口权限 vue3-element-admin 的实现方案 一般我们在业务中将 路由可以分为两种&#xff0c;constantRoutes 和 asyncRoutes。 constantRoutes&#xff1a; 代表那些不需要动态判断权限的路由&#xff0c;如登录页、404(或…

标题:“八股文”在实际工作中是助力、阻力还是空谈?

标题&#xff1a;“八股文”在实际工作中是助力、阻力还是空谈&#xff1f; “八股文”&#xff0c;在程序员的面试和工作中一直是一个备受争议的话题。它既是许多程序员进入职场的敲门砖&#xff0c;也被一些人认为是脱离实际的空谈。本文将从“八股文”对招聘过程的影响、在…

【echarts】 柱状图,最后带“竖线”

具体&#xff1a; https://echarts.zhangmuchen.top/#/detail?cid28ea6-0601-e9f5-9cc29-c022b758 let data [{value: 0,name: 数据格式一},{value: 55,name: 数据格式二},{value: 66,name: 数据格式三},{value: 75,name: 数据格式四},{value: 20,name: 数据格式五}];getAr…

2、Flink 在 DataStream 和 Table 之间进行转换

1.概述 Table API 和 DataStream API 都可以处理有界流和无界流。 DataStream API 提供了流处理的基础(时间、状态和数据流管理);Table API 抽象了许多内部内容,并提供了一个结构化和声明性的 API;在处理历史数据时,需要管理有边界的流;无边界流出现在实时处理场景中,…

wordpress调用栏目最新内容、调用栏目推荐内容、调用栏目随机内容

想要在首页调用wordpress某个栏目的内容&#xff0c;可以按照分类ID来调用&#xff0c;调用出来的内容一般有&#xff1a;调用栏目最新内容、调用栏目推荐内容、调用栏目随机内容这三种形式。简站wordpress小编在此为大家放出三种不同方式调用的代码如下&#xff1a; 通过指定…

盘点.软件测试模型

软件开发模型   软件开发模型(Software Development Model)是指软件开发全部过程、活动和任务的结构框架。软件开发包括需求、设计、编码和测试等阶段&#xff0c;有时也包括维护阶段。 软件开发模型能清晰、直观地表达软件开发全过程&#xff0c;明确规定了要完成的主要活动…

云计算day15

⼀、web基本概念和常识 Web&#xff1a;为⽤户提供的⼀种在互联⽹上浏览信息的服务&#xff0c;Web 服 务是动态的、可交 互的、跨平台的和图形化的。 Web 服务为⽤户提供各种互联⽹服务&#xff0c;这些服务包括信息浏览 服务&#xff0c;以及各种交互式服务&#xff0c;…

C++类与对象-六大成员函数

默认成员函数就是用户没有显式实现&#xff0c;编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个空类编译器会默认⽣成8个默认成员函数。本文只介绍其中6个&#xff0c;C11增加两个函数见后续博客。 目录 一、构造函数 1.1 概念 1.2 特性 1.3 使用举例 1.4 初始化列表 1…

探索LLM编程新纪元:AI赋能的编程之旅

探索LLM编程新纪元&#xff1a;AI赋能的编程之旅 引言 在人工智能&#xff08;AI&#xff09;的浪潮中&#xff0c;大型语言模型&#xff08;LLM&#xff09;作为自然语言处理&#xff08;NLP&#xff09;领域的璀璨明珠&#xff0c;正逐步渗透到编程的每一个角落。从代码自动…

【C#】文件流和文本处理

1. 文件流的基本概念 文件流是C#中处理文件读写的抽象&#xff0c;它提供了对文件内容进行顺序访问的能力。在文件流中&#xff0c;数据按照字节或块的方式传输&#xff0c;而不受文件中数据的格式影响。文件流通常与System.IO命名空间中的类一起使用&#xff0c;包括FileStrea…

如何通过前端表格控件实现自动化报表?

背景 最近伙伴客户的项目经理遇见一个问题&#xff0c;他们在给甲方做自动化报表工具&#xff0c;项目已经基本做好了&#xff0c;但拿给最终甲方&#xff0c;业务人员不太买账&#xff0c;项目经理为此也是天天抓狂&#xff0c;没有想到合适的应对方案。 现阶段主要面临的问…

geotools 读取shape文件

对于GIS开发者而言&#xff0c;矢量数据是我们经常要用到的&#xff0c;而shape数据是矢量数据中最常用的格式&#xff0c;因此解析shape数据也是作为GIS软件开发人员必备的基础技能&#xff0c;而GeoTools无疑是Java最好用来处理GIS数据的三方库&#xff0c;下面例子是简单的g…

算法训练(leetcode)第三十四天 | 56. 携带矿石资源(第八期模拟笔试)、198. 打家劫舍、213. 打家劫舍 II、337. 打家劫舍 III

刷题记录 *56. 携带矿石资源&#xff08;第八期模拟笔试&#xff09;198. 打家劫舍213. 打家劫舍 II*337. 打家劫舍 III解法一 &#xff08;记忆化递推&#xff09;*解法二 &#xff08;动态规划&#xff09; *56. 携带矿石资源&#xff08;第八期模拟笔试&#xff09; leetco…