怎样注册自己的网站蕲春县住房和城乡建设局网站

张小明 2026/1/11 23:15:42
怎样注册自己的网站,蕲春县住房和城乡建设局网站,wordpress标签筛选,wordpress 调用文章简介“领域模型画完了#xff0c;限界上下文也定了#xff0c;但一写代码就打回原形——领域层里全是MyBatis注解#xff0c;业务逻辑和SQL语句搅在一起”——这是DDD落地中最常见的“断层”问题。第二阶段我们完成了业务蓝图的绘制#xff0c;第三阶段的核心就是搭建“翻译桥梁…“领域模型画完了限界上下文也定了但一写代码就打回原形——领域层里全是MyBatis注解业务逻辑和SQL语句搅在一起”——这是DDD落地中最常见的“断层”问题。第二阶段我们完成了业务蓝图的绘制第三阶段的核心就是搭建“翻译桥梁”把领域模型转化为符合企业级规范的代码架构解决“模型与代码两张皮”的痛点。本阶段共4篇文章从架构设计到模式落地全程配套可复用的Java代码模板与工程规范。第一篇六边形架构让领域逻辑不依赖任何框架“用Spring Boot开发就必须把Service放在领域层吗”“换个数据库就要改遍业务代码这到底是哪里出了问题”——很多团队在DDD落地时会被框架绑架导致领域逻辑失去独立性。而六边形架构又称端口-适配器架构的核心价值就是让领域层“跳出框架束缚”成为系统真正的核心。一、为什么传统三层架构会导致“代码腐化”传统MVC三层架构控制层→服务层→数据访问层的最大问题是领域逻辑被技术细节包裹服务层既包含业务规则如订单价格计算又依赖数据访问层的MyBatis接口业务与技术高度耦合。当需要对接第三方支付如从微信支付换成支付宝或更换数据库MySQL→MongoDB时会穿透多层修改代码风险极高。领域逻辑分散在各个Service方法中无法形成独立的业务资产新功能开发只能“堆代码”。而六边形架构通过“端口”和“适配器”在领域层与外部依赖之间建立隔离墙让领域逻辑只关注“业务是什么”不关心“技术怎么实现”。二、六边形架构四层拆解以电商订单系统为例六边形架构以领域层为核心向外辐射端口层、适配器层、应用层每层职责清晰依赖关系严格遵循“内层不依赖外层”原则。我们以Spring Boot工程为例拆解各层的职责与代码结构1. 核心层领域层Domain——业务规则的“大本营”领域层是系统的核心包含实体、值对象、聚合根、领域服务只依赖JDK不引入任何框架依赖如Spring、MyBatis。它定义“业务能做什么”不关心“如何实现”。订单领域层代码结构com.ddd.order.domain ├── aggregate // 聚合根 │ └── Order.java // 订单聚合根含cancel()等业务方法 ├── entity // 实体非聚合根 │ └── OrderItem.java // 订单项实体 ├── valueobject // 值对象 │ └── Address.java // 收货地址值对象 ├── service // 领域服务跨聚合业务 │ └── OrderPriceCalculateService.java // 订单价格计算依赖商品领域 └── repository // 仓储端口领域定义接口 └── OrderRepository.java // 订单仓储接口不包含实现关键亮点领域层的OrderRepository是接口只定义“需要获取订单数据”的能力如findById、save不包含任何SQL或框架注解这就是“端口”的核心作用——定义领域对外的交互规则。2. 端口层领域对外的“交互规则”端口层是领域层对外暴露的“接口集合”分为两类输入端口供外部调用领域功能的接口如“创建订单”“取消订单”通常由应用服务实现。输出端口领域依赖外部资源的接口如仓储接口OrderRepository、第三方服务接口PaymentService由适配器层实现。端口层的价值是“解耦依赖方向”——不是领域层依赖外部而是外部通过端口对接领域比如订单领域需要查询商品价格就定义一个ProductPort接口由商品领域的适配器实现。3. 适配器层连接领域与外部的“翻译官”适配器层负责实现端口层的接口将外部依赖如数据库、第三方服务、API接口“翻译”为领域能理解的语言同时也将领域的输出“翻译”为外部能接受的格式。常见的适配器类型适配器类型职责说明代码示例Spring Boot仓储适配器实现OrderRepository对接数据库Repository注解的OrderRepositoryMyBatis外部服务适配器实现PaymentPort对接第三方支付Service注解的PaymentAdapterAPI适配器将领域模型转化为DTO供Controller调用OrderDTOConverter转换工具类4. 应用层流程编排的“指挥官”应用层不包含业务规则只负责将领域层的功能按业务流程串联起来比如“创建订单”流程需要调用“价格计算”“库存校验”“支付发起”等领域功能应用层就负责协调这些操作的顺序和事务。应用层代码示例Service public class OrderApplicationService { // 依赖领域层的仓储端口接口 private final OrderRepository orderRepository; // 依赖领域服务 private final OrderPriceCalculateService priceCalculateService; // 依赖外部服务端口 private final PaymentPort paymentPort; // 构造器注入避免字段注入的耦合问题 public OrderApplicationService(OrderRepository orderRepository, OrderPriceCalculateService priceCalculateService, PaymentPort paymentPort) { this.orderRepository orderRepository; this.priceCalculateService priceCalculateService; this.paymentPort paymentPort; } // 订单创建流程应用服务核心编排流程 Transactional public OrderDTO createOrder(OrderCreateCommand command) { // 1. 转换DTO为领域模型通过适配器 Address address new Address(command.getProvince(), command.getCity(), command.getDetail()); ListOrderItem orderItems command.getItems().stream() .map(item - new OrderItem(new ProductId(item.getProductId()), item.getQuantity())) .collect(Collectors.toList()); // 2. 调用领域服务计算价格业务规则在领域层 BigDecimal totalPrice priceCalculateService.calculate(orderItems); // 3. 构建聚合根领域逻辑 Order order new Order(new OrderId(UUID.randomUUID().toString()), orderItems, address, totalPrice); // 4. 调用仓储端口保存实现由适配器提供 Order savedOrder orderRepository.save(order); // 5. 调用外部服务发起支付实现由适配器提供 paymentPort.initPayment(savedOrder.getId().getValue(), totalPrice); // 6. 转换领域模型为DTO返回适配器 return OrderDTOConverter.toDTO(savedOrder); } }三、企业级工程规范包结构与依赖原则六边形架构落地的关键是严格遵守包结构和依赖规则避免“越界调用”。企业级Spring Boot工程的标准包结构com.ddd.order // 应用根包 ├── application // 应用层 │ ├── service // 应用服务 │ ├── command // 命令对象接收前端参数 │ └── dto // 数据传输对象 ├── domain // 领域层核心无框架依赖 │ ├── aggregate │ ├── entity │ ├── valueobject │ ├── service │ └── port // 端口输入输出 ├── infrastructure // 基础设施层适配器 │ ├── repository // 仓储适配器MyBatis实现 │ ├── client // 外部服务适配器Feign调用 │ └── converter // DTO-领域模型转换器 └── interface // 接口层Controller └── rest // REST接口核心依赖原则内层包不依赖外层包即domain层不能依赖application、infrastructure、interface层application层只能依赖domain层infrastructure层依赖domain层的端口interface层依赖application层。通过六边形架构当需要更换数据库时只需新增一个实现OrderRepository的MongoDB适配器无需修改领域层一行代码当需要对接新的支付渠道时只需实现PaymentPort的新适配器——这就是企业级系统“高内聚、低耦合”的核心实现方式。第二篇仓储模式领域与数据库的“隔离墙”怎么建“领域模型里直接写JPA注解很方便为什么还要搞个仓储接口”——这是很多开发者对仓储模式的疑问。但在企业级系统中“方便”往往意味着“后期灾难”当业务从单体迁移到微服务或从关系型数据库迁移到分布式数据库时直接耦合数据库的领域模型会让修改成本呈指数级增长。仓储模式的核心就是在领域与数据库之间建立“隔离墙”。一、仓储模式的本质领域定义“需求”技术实现“供给”DDD中的仓储Repository不是传统的DAO层它有两个核心定位领域的“数据访问代理人”领域层通过仓储接口提出“需要获取/保存订单数据”的需求不关心数据存在MySQL还是MongoDB也不关心用MyBatis还是JPA。聚合的“完整性守护者”仓储操作的最小单位是“聚合根”而不是单个实体。比如保存订单时会自动保存聚合内的订单项和地址确保聚合的完整性。举个通俗的例子你领域层想吃火锅需要数据不需要自己去菜市场买菜操作数据库只需告诉外卖员仓储接口你的需求外卖员仓储适配器会去不同的菜市场不同数据库帮你完成你只需要等待结果即可。二、仓储模式三步落地以订单仓储为例仓储模式的落地分为“定义端口”“实现适配器”“使用仓储”三步全程遵循“领域驱动技术”的原则。1. 第一步在领域层定义仓储端口接口仓储端口属于领域层只定义“业务需要的数据操作能力”不包含任何技术细节。订单仓储接口需满足按ID查询订单、保存订单、按用户ID查询待支付订单这三个核心业务需求。// 领域层端口com.ddd.order.domain.port.repository.OrderRepository public interface OrderRepository { /** * 按订单ID查询订单聚合 * 注返回的是聚合根包含完整的订单项和地址 */ OptionalOrder findById(OrderId orderId); /** * 保存订单聚合 * 注自动保存聚合内的所有实体和值对象 */ Order save(Order order); /** * 按用户ID查询待支付订单 * 注业务场景驱动的查询方法而非通用的findAll */ ListOrder findPendingPaymentByUserId(UserId userId); } // 领域层值对象订单ID封装ID逻辑 public class OrderId { private String value; public OrderId(String value) { // 领域规则订单ID必须是32位UUID if (value null || value.length() ! 32) { throw new BusinessException(订单ID格式错误); } this.value value; } // 只暴露getter无setter确保不可变 public String getValue() { return value; } }关键设计仓储接口的参数和返回值都是领域模型Order、OrderId而非DTO或数据库实体确保领域层的独立性。同时方法名是“业务化”的如findPendingPaymentByUserId而非技术化的如selectByUserIdAndStatus体现领域驱动的思想。2. 第二步在基础设施层实现仓储适配器适配器层负责将领域的仓储接口通过具体的技术框架如MyBatis实现。这里需要解决两个核心问题领域模型与数据库实体的映射、聚合完整性的保证。1定义数据库实体PO数据库实体PO属于基础设施层只与数据库表结构对应不包含任何业务逻辑。订单相关的数据库表有t_order订单主表、t_order_item订单项表对应的PO// 基础设施层POcom.ddd.order.infrastructure.repository.po.OrderPo Data TableName(t_order) public class OrderPo { TableId(type IdType.INPUT) private String orderId; // 对应OrderId的value private String userId; private BigDecimal totalPrice; private String status; // 存储状态码如PAID private String province; // 地址信息冗余存储便于查询 private String city; private String detail; private LocalDateTime createTime; // 关联订单项POMyBatis-Plus的关联查询 TableField(exist false) private ListOrderItemPo orderItemPos; } Data TableName(t_order_item) public class OrderPo { TableId(type IdType.AUTO) private Long id; private String orderId; private String productId; private Integer quantity; private BigDecimal price; }2实现仓储接口MyBatis适配器适配器通过MyBatis-Plus操作数据库将PO转化为领域模型确保领域层完全不感知数据库的存在。// 基础设施层适配器com.ddd.order.infrastructure.repository.OrderRepositoryMyBatis Repository public class OrderRepositoryMyBatis implements OrderRepository { private final OrderMapper orderMapper; private final OrderItemMapper orderItemMapper; // 构造器注入Mapper public OrderRepositoryMyBatis(OrderMapper orderMapper, OrderItemMapper orderItemMapper) { this.orderMapper orderMapper; this.orderItemMapper orderItemMapper; } Override public OptionalOrder findById(OrderId orderId) { // 1. 查询订单主表PO OrderPo orderPo orderMapper.selectById(orderId.getValue()); if (orderPo null) { return Optional.empty(); } // 2. 查询关联的订单项PO ListOrderItemPo itemPos orderItemMapper.selectByOrderId(orderId.getValue()); // 3. PO转化为领域模型通过转换器 return Optional.of(OrderConverter.toDomain(orderPo, itemPos)); } Override public Order save(Order order) { // 1. 领域模型转化为PO OrderPo orderPo OrderConverter.toPo(order); ListOrderItemPo itemPos OrderConverter.toItemPos(order.getOrderItems(), order.getId().getValue()); // 2. 保存订单主表新增/更新 if (orderMapper.existsById(order.getId().getValue())) { orderMapper.updateById(orderPo); // 先删后增保证订单项与订单一致聚合完整性 orderItemMapper.deleteByOrderId(order.getId().getValue()); } else { orderMapper.insert(orderPo); } // 3. 保存订单项聚合内强一致性 orderItemMapper.batchInsert(itemPos); // 4. 返回保存后的领域模型 return this.findById(order.getId()).orElseThrow(); } Override public ListOrder findPendingPaymentByUserId(UserId userId) { ListOrderPo orderPos orderMapper.selectByUserIdAndStatus( userId.getValue(), OrderStatus.PENDING_PAYMENT.getCode()); return orderPos.stream() .map(po - { ListOrderItemPo itemPos orderItemMapper.selectByOrderId(po.getOrderId()); return OrderConverter.toDomain(po, itemPos); }) .collect(Collectors.toList()); } }3. 第三步依赖注入领域层使用仓储领域层和应用层通过依赖注入使用仓储接口完全不依赖具体的实现类。比如应用服务调用仓储Service public class OrderApplicationService { // 依赖领域层的接口而非基础设施层的实现 private final OrderRepository orderRepository; // 构造器注入Spring会自动注入MyBatis实现 public OrderApplicationService(OrderRepository orderRepository) { this.orderRepository orderRepository; } // 订单取消后更新订单 public void cancelOrder(String orderId) { Order order orderRepository.findById(new OrderId(orderId)) .orElseThrow(() - new BusinessException(订单不存在)); // 调用领域模型的业务方法 order.cancel(); // 调用仓储保存 orderRepository.save(order); } }三、企业级仓储优化解决聚合查询的N1问题仓储模式中最常见的性能问题是“N1查询”查询N个订单再分别查询每个订单的订单项。企业级解决方案有两种关联查询结果映射通过MyBatis的resultMap配置一次SQL查询出订单和订单项避免多次查询。示例resultMap idOrderWithItemsMap typecom.ddd.order.infrastructure.repository.po.OrderPo id columnorder_id propertyorderId/ result columnuser_id propertyuserId/gt; !-- 其他字段映射 -- !-- 关联订单项集合 -- collection propertyorderItemPos ofTypecom.ddd.order.infrastructure.repository.po.OrderItemPo id columnitem_id propertyid/ result columnproduct_id propertyproductId/gt; !-- 其他订单项字段映射 -- /collection /resultMap select idselectByIdWithItems resultMapOrderWithItemsMap SELECT o.*, i.id as item_id, i.product_id, i.quantity FROM t_order o LEFT JOIN t_order_item i ON o.order_id i.order_id WHERE o.order_id #{orderId} /select读写分离查询操作走只读数据源使用关联查询写入操作走主数据源确保数据一致性。通过Spring的Transactional(readOnly true)实现。仓储模式的核心价值是让领域模型“摆脱数据库的束缚”成为真正的业务资产。在企业级系统中这种“隔离”带来的不仅是技术灵活性更是业务快速迭代的能力。第三篇应用服务设计不写业务逻辑的“流程指挥官”“应用服务和领域服务到底有什么区别”“订单创建的逻辑该放在应用服务还是领域服务”——这是DDD落地中最容易混淆的问题。很多团队把所有业务逻辑都堆在应用服务里导致应用服务变成“大泥球”也有团队把流程编排逻辑放进领域服务导致领域服务依赖外部资源。其实两者的边界很清晰应用服务是“流程指挥官”领域服务是“业务专家”。一、应用服务与领域服务的核心区别要明确两者的边界首先要理解它们的定位差异。我们用电商“创建订单”流程举例对比两者的职责对比维度应用服务Application Service领域服务Domain Service核心定位流程编排者负责“怎么做”业务规则实现者负责“是什么”依赖对象依赖领域服务、仓储接口、外部服务端口只依赖领域模型实体、值对象无外部依赖业务范围跨领域/跨聚合的流程如创建订单需调用支付服务单领域内的业务规则如订单价格计算示例逻辑1. 接收前端参数→2. 转换为领域模型→3. 调用价格计算→4. 保存订单→5. 发起支付根据商品单价、数量、优惠券计算订单总价记忆口诀应用服务管“流程”领域服务管“规则”应用服务对外“协调资源”领域服务对内“实现业务”。二、应用服务设计三原则避免“大泥球”企业级应用服务设计必须遵守三个核心原则否则容易陷入“逻辑混乱、难以维护”的困境。1. 原则一只做流程编排不写业务规则应用服务的代码应该是“线性的流程步骤”而不是包含复杂条件判断的业务逻辑。反例与正例对比// 反例应用服务包含业务规则价格计算 Service public class BadOrderApplicationService { public OrderDTO createOrder(OrderCreateCommand command) { // 错误价格计算的业务规则放在了应用服务 BigDecimal totalPrice BigDecimal.ZERO; for (OrderItemCommand item : command.getItems()) { // 商品单价从外部接口获取应用服务依赖外部资源 BigDecimal productPrice productClient.getPrice(item.getProductId()); totalPrice totalPrice.add(productPrice.multiply(new BigDecimal(item.getQuantity()))); } // 错误订单状态判断的业务规则也在应用服务 OrderStatus status command.getPayType() PayType.NOW ? PENDING_PAYMENT : UNPAID; // ... 后续逻辑 } } // 正例应用服务只做流程编排 Service public class GoodOrderApplicationService { private final OrderPriceCalculateService priceService; // 领域服务 private final OrderRepository orderRepository; // 仓储接口 private final PaymentPort paymentPort; // 外部服务端口 public OrderDTO createOrder(OrderCreateCommand command) { // 1. 转换参数为领域模型流程步骤 ListOrderItem items convertToOrderItems(command); // 2. 调用领域服务计算价格业务规则交给领域 BigDecimal totalPrice priceService.calculate(items, command.getCouponId()); // 3. 调用领域模型构建订单状态规则在领域 Order order Order.create(items, convertToAddress(command), totalPrice); // 4. 调用仓储保存数据操作交给仓储 Order savedOrder orderRepository.save(order); // 5. 调用外部服务发起支付外部交互交给端口 paymentPort.initPayment(savedOrder.getId().getValue(), totalPrice); // 6. 转换为DTO返回流程收尾 return convertToDTO(savedOrder); } }2. 原则二一个应用服务对应一个业务场景避免设计“万能应用服务”比如一个OrderApplicationService包含创建订单、取消订单、查询订单、修改订单等所有逻辑。正确的做法是按业务场景拆分// 按场景拆分应用服务 com.ddd.order.application.service ├── OrderCreationService.java // 订单创建场景 ├── OrderCancellationService.java // 订单取消场景 ├── OrderQueryService.java // 订单查询场景 └── OrderModificationService.java // 订单修改场景这样拆分的好处是职责单一便于维护不同场景可以独立扩展如查询场景需要优化性能不影响创建场景符合“微服务按场景拆分”的演进方向。3. 原则三通过“命令对象”接收参数避免DTO泛滥应用服务接收前端参数时应使用“命令对象”Command而非通用DTO。命令对象与DTO的区别命令对象是“动词性”的对应一个具体操作如OrderCreateCommand只包含该操作需要的参数DTO是“名词性”的用于数据传输。// 命令对象只包含创建订单需要的参数 public class OrderCreateCommand { NotNull(message 用户ID不能为空) private String userId; NotEmpty(message 订单项不能为空) private ListOrderItemCommand items; private String couponId; // 可选参数优惠券 NotNull(message 收货地址不能为空) private String province; private String city; private String detail; // getter setter } // 订单项命令对象嵌套 public class OrderItemCommand { NotNull(message 商品ID不能为空) private String productId; Min(value 1, message 数量不能小于1) private Integer quantity; // getter setter }命令对象可以通过JSR-380注解NotNull、Min做参数校验确保进入应用服务的参数合法减轻领域层的校验压力。三、应用服务的测试策略聚焦流程正确性应用服务的测试不需要连接真实数据库或外部服务只需通过Mock框架模拟依赖的领域服务、仓储接口和外部端口测试流程是否按预期执行。SpringBootTest public class OrderCreationServiceTest { // Mock依赖的对象 MockBean private OrderPriceCalculateService priceService; MockBean private OrderRepository orderRepository; MockBean private PaymentPort paymentPort; // 待测试的应用服务 Autowired private OrderCreationService orderCreationService; Test public void testCreateOrder() { // 1. 构造测试参数 OrderCreateCommand command new OrderCreateCommand(); command.setUserId(123); // ... 填充其他参数 // 2. 模拟依赖的返回值 BigDecimal mockPrice new BigDecimal(100); when(priceService.calculate(any(), any())).thenReturn(mockPrice); Order mockOrder new Order(new OrderId(456), ...); when(orderRepository.save(any())).thenReturn(mockOrder); // 3. 执行测试方法 OrderDTO result orderCreationService.createOrder(command); // 4. 断言结果流程是否正确执行 assertNotNull(result); assertEquals(456, result.getOrderId()); // 验证依赖的方法是否被调用流程步骤是否完整 verify(priceService, times(1)).calculate(any(), any()); verify(orderRepository, times(1)).save(any()); verify(paymentPort, times(1)).initPayment(eq(456), eq(mockPrice)); } }通过这种方式我们可以快速验证应用服务的流程是否正确而不受外部依赖的影响这也是企业级系统“测试驱动开发”的核心实践。第四篇CQRS模式读写分离的企业级实践“电商大促时订单查询接口响应慢到超时但下单流程却很顺畅这该怎么优化”——这是高并发场景下的典型问题。原因在于“读写混合”的架构中大量的查询请求占用了数据库连接导致写入请求排队。而CQRS模式命令查询职责分离通过“读写分离”让查询和写入各自优化是解决高并发问题的企业级方案。一、CQRS的核心把“读”和“写”彻底分开CQRS的全称是Command Query Responsibility Segregation即“命令查询职责分离”它基于一个简单的原则任何一个操作要么是修改系统状态的“命令”Command要么是查询系统状态的“查询”Query两者不能同时存在。在DDD工程落地中CQRS的具体体现为命令端写端负责修改数据如创建订单、取消订单基于领域模型实现保证业务规则的正确性使用事务确保数据一致性。查询端读端负责查询数据如订单列表、订单详情不依赖领域模型直接针对查询场景设计优化的查询模型和SQL追求查询性能。举个电商的例子创建订单命令端需要调用领域服务计算价格、校验库存、保存聚合确保业务规则而查询订单列表查询端只需要从数据库查询“订单ID用户ID状态总价”不需要任何业务规则直接返回给前端即可。二、CQRS在DDD中的工程落地三大核心组件CQRS不是“独立架构”而是融入DDD工程体系的“模式”。基于之前的六边形架构我们通过“命令处理器”“查询处理器”“读写数据源分离”三个组件实现CQRS。1. 命令端基于领域模型的“写流程”命令端的核心是“确保业务正确性”复用之前的领域模型、应用服务和仓储模式无需额外修改。命令端的流程前端提交“创建订单”命令Command。应用服务接收命令转换为领域模型。调用领域服务和聚合根执行业务规则。通过仓储将数据写入“主数据源”MySQL主库。发布领域事件如OrderCreatedEvent通知查询端更新查询模型。命令端的代码与之前的应用服务完全一致核心是通过领域模型保证“写操作”的业务正确性。2. 查询端基于查询模型的“读流程”查询端的核心是“提升查询性能”因此需要设计独立的查询模型Read Model不依赖领域模型直接对接数据库。查询端的组件1查询模型Read Model为查询场景定制查询模型是专门为查询场景设计的POJO只包含查询需要的字段避免“大字段查询”和“关联查询冗余”。比如订单列表的查询模型// 订单列表查询模型只包含前端需要的字段 public class OrderListReadModel { private String orderId; private String userId; private String status; // 状态描述如“待支付” private BigDecimal totalPrice; private LocalDateTime createTime; // getter setter } // 订单详情查询模型包含更多字段 public class OrderDetailReadModel { private String orderId; private String userId; private String status; private BigDecimal totalPrice; private AddressReadModel address; // 地址查询模型 private ListOrderItemReadModel items; // 订单项查询模型 // getter setter }2查询处理器Query Handler独立的查询逻辑查询处理器负责接收查询请求调用查询仓储返回查询模型。查询处理器不依赖领域层直接通过MyBatis查询“从数据源”MySQL从库。// 订单查询处理器 Service public class OrderQueryHandler { private final OrderQueryRepository orderQueryRepository; public OrderQueryHandler(OrderQueryRepository orderQueryRepository) { this.orderQueryRepository orderQueryRepository; } // 处理“查询用户订单列表”查询 public ListOrderListReadModel handle(OrderListQuery query) { // 直接调用查询仓储返回查询模型 return orderQueryRepository.findByUserId(query.getUserId(), query.getStatus()); } // 处理“查询订单详情”查询 public OrderDetailReadModel handle(OrderDetailQuery query) { return orderQueryRepository.findById(query.getOrderId()) .orElseThrow(() - new BusinessException(订单不存在)); } } // 查询参数与命令对象类似针对查询场景 public class OrderListQuery { private String userId; private String status; // 可选参数用于筛选 private Integer pageNum 1; private Integer pageSize 10; // getter setter }3查询仓储Query Repository优化查询SQL查询仓储专门用于查询操作通过优化的SQL如索引、分页、避免关联提升性能对接MySQL从库。// 订单查询仓储 Repository public interface OrderQueryRepository { // 优化的分页查询SQL只查需要的字段 Select(SELECT order_id, user_id, status, total_price, create_time FROM t_order WHERE user_id #{userId} AND (#{status} IS NULL OR status #{status}) ORDER BY create_time DESC LIMIT #{pageSize} OFFSET #{(pageNum-1)*pageSize}) ListOrderListReadModel findByUserId(Param(userId) String userId, Param(status) String status, Param(pageNum) Integer pageNum, Param(pageSize) Integer pageSize); // 关联查询订单详情使用索引优化 Select(SELECT o.order_id, o.user_id, o.status, o.total_price, o.province, o.city, o.detail, i.product_id, i.quantity, i.price FROM t_order o LEFT JOIN t_order_item i ON o.order_id i.order_id WHERE o.order_id #{orderId}) Results({ Result(column order_id, property orderId), // 其他字段映射 Result(column order_id, property items, many Many(select findOrderItemsByOrderId)) }) OptionalOrderDetailReadModel findById(String orderId); Select(SELECT product_id, quantity, price FROM t_order_item WHERE order_id #{orderId}) ListOrderItemReadModel findOrderItemsByOrderId(String orderId); }3. 读写数据源分离提升并发能力CQRS的性能优化核心是“读写数据源分离”命令端写入MySQL主库查询端从MySQL从库读取数据通过数据库的主从复制实现数据同步。在Spring Boot中通过动态数据源实现// 1. 数据源配置 Configuration public class DataSourceConfig { // 主数据源写 Bean ConfigurationProperties(spring.datasource.master) public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } // 从数据源读 Bean ConfigurationProperties(spring.datasource.slave) public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } // 动态数据源路由 Bean public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) { Maplt;Object, Objectgt; dataSources new HashMap(); dataSources.put(master, masterDataSource); dataSources.put(slave, slaveDataSource); DynamicDataSource dynamicDataSource new DynamicDataSource(); dynamicDataSource.setTargetDataSources(dataSources); dynamicDataSource.setDefaultTargetDataSource(masterDataSource); return dynamicDataSource; } } // 2. 数据源注解 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface DataSource { String value() default master; } // 3. AOP实现数据源切换 Aspect Component public class DataSourceAspect { Before(annotation(dataSource)) public void before(JoinPoint joinPoint, DataSource dataSource) { DynamicDataSourceContextHolder.setDataSource(dataSource.value()); } After(annotation(dataSource)) public void after(DataSource dataSource) { DynamicDataSourceContextHolder.clearDataSource(); } }使用注解标记读写数据源// 命令端写使用主数据源 Service public class OrderCreationService { Transactional DataSource(master) public OrderDTO createOrder(OrderCreateCommand command) { // ... 写逻辑 } } // 查询端读使用从数据源 Service public class OrderQueryHandler { DataSource(slave) public ListOrderListReadModel handle(OrderListQuery query) { // ... 读逻辑 } }三、企业级优化CQRS事件溯源解决数据一致性问题CQRS的核心挑战是“读写数据一致性”——命令端写入主库后主从复制存在延迟查询端可能查询不到最新数据。企业级解决方案是“CQRS事件溯源”命令端执行写操作后发布领域事件如OrderCreatedEvent。查询端订阅领域事件在事件处理器中同步更新查询模型如写入Redis缓存或更新ES索引。查询端优先从缓存/ES查询数据若未查询到再从数据库查询确保数据实时性。以电商订单查询为例引入ES后的查询流程// 1. 命令端发布事件 Service public class OrderCreationService { private final DomainEventPublisher publisher; public OrderDTO createOrder(OrderCreateCommand command) { // ... 写逻辑 // 发布事件 publisher.publish(new OrderCreatedEvent(savedOrder)); } } // 2. 查询端订阅事件更新ES Component public class OrderEventProcessor { private final RestHighLevelClient esClient; EventListener public void handleOrderCreatedEvent(OrderCreatedEvent event) { // 将订单数据同步到ES OrderDocument document convertToEsDocument(event.getOrder()); IndexRequest request new IndexRequest(order_index).id(document.getOrderId()); request.source(JSON.toJSONString(document), XContentType.JSON); esClient.index(request, RequestOptions.DEFAULT); } } // 3. 查询端从ES查询 Service public class OrderQueryHandler { public ListOrderListReadModel handle(OrderListQuery query) { // 从ES查询性能比数据库高10倍以上 SearchSourceBuilder sourceBuilder new SearchSourceBuilder(); BoolQueryBuilder boolQuery QueryBuilders.boolQuery() .must(QueryBuilders.termQuery(userId, query.getUserId())); if (query.getStatus() ! null) { boolQuery.must(QueryBuilders.termQuery(status, query.getStatus())); } sourceBuilder.query(boolQuery); // ... 执行查询并转换为查询模型 } }通过这种方式查询端的响应时间从原来的300ms优化到10ms以内完全满足电商大促的高并发查询需求。四、CQRS的适用场景与避坑点CQRS不是“银弹”需要根据业务场景选择✅ 适用场景高并发查询如电商订单列表、商品详情、读写比例失衡读:写10:1、查询场景复杂需要多表关联或大字段过滤。❌ 不适用场景简单CRUD系统、读写比例均衡如后台管理系统、对数据实时性要求极高如金融交易的实时查询。避坑点不要为了用CQRS而用CQRS小型系统或简单场景使用CQRS会增加系统复杂度得不偿失。只有当查询性能成为瓶颈时才引入CQRS模式。第三阶段的核心是“将模型转化为可落地的代码”六边形架构解决了“业务与技术的隔离”仓储模式解决了“领域与数据库的隔离”应用服务明确了“流程与规则的边界”CQRS模式则优化了“高并发场景的性能”。这四篇文章的内容构成了企业级DDD工程落地的核心体系下一个阶段我们将进入“架构演进”讲解如何从单体DDD架构平滑迁移
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

网站的空间什么意思网站效果图设计方案

SEO 分析与优化全攻略 在当今数字化的时代,搜索引擎优化(SEO)对于网站的成功至关重要。通过有效的 SEO 策略,网站能够在搜索引擎结果中获得更高的排名,从而吸引更多的潜在客户。本文将深入探讨竞争分析、转化分析、服务器日志分析以及针对主要搜索引擎的优化方法,帮助你…

张小明 2026/1/11 1:41:07 网站建设

软文营销的本质杭州seo网站

YOLO模型镜像提供多种CUDA版本选择,兼容老旧GPU 在智能制造车间的边缘服务器上,一台搭载 Tesla K80 的工控机正试图加载最新的 YOLOv8 推理服务——结果却因 libcudart.so.12 缺失而崩溃。这并非个例:大量企业仍在使用基于 Kepler、Maxwell 或…

张小明 2026/1/10 8:56:41 网站建设

有经验的常州网站建设aso关键词搜索优化

创新学术写作助手:GB/T 7714国标参考文献智能排版系统完全指南 【免费下载链接】gbt7714-bibtex-style GB/T 7714-2015 BibTeX Style 项目地址: https://gitcode.com/gh_mirrors/gb/gbt7714-bibtex-style 还在为论文参考文献格式调整而耗费宝贵时间吗&#x…

张小明 2026/1/10 8:56:45 网站建设

做网络推广的网站做网站的会淘宝美工么

BongoCat架构重构:从依赖地狱到组件解耦术的实战演进 【免费下载链接】BongoCat 让呆萌可爱的 Bongo Cat 陪伴你的键盘敲击与鼠标操作,每一次输入都充满趣味与活力! 项目地址: https://gitcode.com/gh_mirrors/bong/BongoCat 在桌面应…

张小明 2026/1/10 8:56:50 网站建设

找人做效果土去那网站找敬请期待的句子

跨平台上位机串口通信模块开发实战:从原理到落地的完整路径你有没有遇到过这样的场景?——在实验室里,你的Windows电脑能完美连接下位机读取数据;可客户一拿到Linux系统上运行,串口直接“失联”;或者macOS用…

张小明 2026/1/10 8:56:46 网站建设

网站后台会员管理电商型企业网站建设

Lens实战指南:5分钟掌握Kubernetes日志聚合高效方案 【免费下载链接】lens Lens - The way the world runs Kubernetes 项目地址: https://gitcode.com/gh_mirrors/le/lens 在Kubernetes应用运维过程中,日志管理往往是开发者面临的最大挑战之一。…

张小明 2026/1/10 8:56:47 网站建设