后端代码skill实战:如何设计高可维护性的服务层架构

2次阅读
没有评论

共计 2474 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

1. 问题场景:当代码变成 ” 面条式 ” 结构

最近接手一个电商促销模块时,发现修改满减规则需要同时改动 Controller、Service 和 DAO 层——这正是一个典型的 霰弹式修改 症状。更严重的是,核心下单接口的响应时间从最初的 200ms 逐渐劣化到 1.2 秒,日志显示其中有 6 次重复的库存查询操作。

后端代码 skill 实战:如何设计高可维护性的服务层架构

2. 架构选型:DDD vs 传统 MVC

传统 MVC 的痛点

  • 贫血模型 :Service 层充斥setter/getter 的搬运逻辑
  • 强耦合:数据库表结构直接渗透到业务逻辑
  • 复用困难:一个 Service 方法可能被多个 Controller 调用

DDD 分层优势(架构图示意)

┌─────────────────┐
│   Interface 层   │  ◄── HTTP/RPC 适配
├─────────────────┤
│ Application 层   │  ◄── 用例编排 / 事务控制
├─────────────────┤
│   Domain 层      │  ◄── 核心业务逻辑
├─────────────────┤
│ Infrastructure  │  ◄── 技术实现(DB/ 缓存等)
└─────────────────┘

3. 核心实现

3.1 领域服务示例

// 订单领域服务
@RequiredArgsConstructor // Lombok 生成构造函数
public class OrderDomainService {
    private final InventoryRepository inventoryRepo;

    // 保持领域语言:placeOrder 而非 createOrder
    public Order placeOrder(Order order) {
        // 领域规则校验
        if (order.getItems().isEmpty()) {throw new BusinessException("订单项不能为空");
        }

        // 聚合根操作
        order.getItems().forEach(item -> {Inventory inventory = inventoryRepo.findById(item.getSkuId());
            inventory.reduceStock(item.getQuantity()); // 修改库存聚合根
        });

        return order.confirm(); // 返回已确认状态的订单}
}

3.2 DTO 转换技巧

@Data // Lombok 注解
public class OrderDTO {
    private String orderNo;
    private List<OrderItemDTO> items;

    // 转换方法保持单向依赖
    public Order toDomain() {Order order = new Order();
        order.setOrderNo(this.orderNo);
        order.setItems(this.items.stream()
                .map(OrderItemDTO::toDomain)
                .collect(Collectors.toList())
        );
        return order;
    }
}

3.3 单元测试示范

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock
    private OrderRepository orderRepo;

    @InjectMocks
    private OrderDomainService service;

    @Test
    void should_throw_exception_when_empty_items() {Order order = new Order();
        assertThrows(BusinessException.class, 
            () -> service.placeOrder(order));
    }

    @Test
    void should_reduce_stock_for_each_item() {
        // Given
        OrderItem item = new OrderItem("SKU001", 2);
        Order order = new Order(Arrays.asList(item));

        // When
        when(inventoryRepo.findById("SKU001"))
            .thenReturn(new Inventory("SKU001", 10));

        // Then
        Order result = service.placeOrder(order);
        assertEquals(8, inventoryRepo.findById("SKU001").getStock());
    }
}

4. 性能优化

4.1 N+ 1 查询解决方案

// 使用 @BatchSize 优化关联查询
@Entity
public class Order {@OneToMany(mappedBy = "order")
    @BatchSize(size = 50) // 一次加载 50 个子项
    private List<OrderItem> items;
}

// 查询时使用 JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Order findByIdWithItems(@Param("id") Long id);

4.2 缓存策略选择

场景 推荐方案 示例
高频读低频写 Redis+ 本地缓存 商品分类数据
强一致性要求 数据库乐观锁 库存扣减
复杂计算结果 Guava Cache 促销规则匹配结果

5. 避坑指南

5.1 过度抽象反模式

  • 抽象泄漏:基础设施细节渗透到领域层(如:在领域对象中处理 JSON 序列化)
  • 万能服务 :一个CommonService 处理 20 种业务场景
  • 接口膨胀:为每个方法都创建 interface 导致维护负担

5.2 事务划分原则

  1. 聚合根操作必须在一个事务内完成
  2. 跨聚合操作考虑最终一致性(Saga 模式)
  3. 查询服务通常不需要事务
  4. 避免在事务中调用外部服务

6. 开放讨论

在实践中发现,严格的领域模型会导致初期开发效率下降约 30%。是否应该在 MVP 阶段采用 贫血模型 + 事务脚本,待业务稳定后再重构?欢迎分享你的项目经验。

特别提醒:领域驱动不是银弹,适合复杂核心业务场景。简单的 CRUD 应用使用 MVC 反而更高效。

正文完
 0
评论(没有评论)