企业级Java架构分层

57 阅读6分钟

企业级Java架构分层实践

作者: shura | 日期: 2025-01-14

说明

本文介绍的是4层架构+充血模型的分层方案,不是传统三层,也不是标准DDD。

国内使用这种方案的公司约10-15%,大多数公司(85%)还在用传统的贫血模型+Service。

适合场景: 有复杂业务规则、Service已经臃肿(>500行)、需要长期维护的项目

不适合场景: 纯CRUD、快速开发、小团队(<3人)

本文作为一种思路参考,不是唯一方案。


问题

传统三层架构在实际项目中的问题:

Controller → Service → Mapper

Service层成为"垃圾桶",一个方法做所有事情:

public void createOrder(Long userId, Long productId, int quantity) {
    // 1. 查用户
    User user = userMapper.selectById(userId);
    if (user.getStatus() != 1) throw...
    
    // 2. 查商品
    Product product = productMapper.selectById(productId);
    if (product.getStock() < quantity) throw...
    
    // 3. 计算价格(业务规则散落在这里)
    BigDecimal price = product.getPrice() * quantity;
    if ("VIP".equals(user.getLevel())) {
        price = price * 0.9;  // VIP打9折
    }
    if (price > 100) {
        price = price - 10;   // 满100减10
    }
    
    // 4. 扣库存
    product.setStock(product.getStock() - quantity);
    productMapper.update(product);
    
    // 5. 创建订单
    orderMapper.insert(order);
    
    // 800行代码...
}

问题:

  • 职责不清
  • 业务规则散落
  • 难以测试
  • 难以复用

方案

4层架构:

Controller  → 接收请求,调用Service
Service     → 编排流程,管理事务
Domain      → 业务规则,业务计算
Repository  → 数据库操作

包结构:

src/main/java/com/company/order/
├── controller/
├── service/
├── domain/
│   ├── model/
│   └── service/
└── infrastructure/
    ├── repository/
    └── mapper/

各层职责:

职责禁止
Controller接收请求,调用Service写业务逻辑
Service编排流程,管理事务写业务规则
Domain业务规则,业务计算依赖框架
Infrastructure数据库,外部调用业务判断

实现

Controller层

只做3件事:接收请求、调用Service、返回结果

@PostMapping("/orders")
public Result create(@RequestBody OrderRequest request) {
    OrderDTO dto = orderService.createOrder(
        request.getUserId(),
        request.getProductId(),
        request.getQuantity()
    );
    return Result.success(dto);
}

Service层

只编排流程,不写业务规则

@Transactional
public OrderDTO createOrder(Long userId, Long productId, int quantity) {
    // 1. 查数据
    User user = userRepository.getById(userId);
    Product product = productRepository.getById(productId);
    
    // 2. 调用Domain层处理业务
    Order order = orderDomainService.createOrder(user, product, quantity);
    
    // 3. 保存
    orderRepository.save(order);
    productRepository.save(product);
    
    // 4. 返回
    return toDTO(order);
}

Domain层 - 充血模型

业务方法写在实体内部:

public class User {
    private String level;
    private Integer status;
    
    // 业务方法
    public void checkCanOrder() {
        if (this.status != 1) {
            throw new BizException("用户状态异常");
        }
    }
    
    public BigDecimal getDiscount() {
        return "VIP".equals(level) ? 0.9 : 1.0;
    }
}

public class Product {
    private Integer stock;
    
    // 业务方法
    public void checkStock(int quantity) {
        if (this.stock < quantity) {
            throw new BizException("库存不足");
        }
    }
    
    public void decreaseStock(int quantity) {
        checkStock(quantity);
        this.stock -= quantity;
    }
}

Domain层 - 领域服务

跨对象的业务逻辑:

public class OrderDomainService {
    
    public Order createOrder(User user, Product product, int quantity) {
        // 业务规则1: 检查用户
        user.checkCanOrder();
        
        // 业务规则2: 检查库存
        product.checkStock(quantity);
        
        // 业务规则3: 计算价格
        BigDecimal amount = calculateAmount(product, quantity, user);
        
        // 业务规则4: 扣库存
        product.decreaseStock(quantity);
        
        // 业务规则5: 创建订单
        Order order = new Order();
        order.setAmount(amount);
        order.setStatus(PENDING);
        
        return order;
    }
    
    private BigDecimal calculateAmount(Product product, int quantity, User user) {
        BigDecimal base = product.getPrice() * quantity;
        BigDecimal afterDiscount = base * user.getDiscount();  // VIP折扣
        return afterDiscount > 100 ? afterDiscount - 10 : afterDiscount;  // 满减
    }
}

Infrastructure层

隔离数据库实现:

public class OrderRepositoryImpl implements OrderRepository {
    
    @Autowired
    private OrderMapper mapper;
    
    public void save(Order order) {
        OrderPO po = toPO(order);  // Domain对象 -> PO
        mapper.insert(po);
    }
    
    public Order getById(Long id) {
        OrderPO po = mapper.selectById(id);
        return toDomain(po);  // PO -> Domain对象
    }
}

对比

代码行数:

传统写法:
├─ OrderService.java     800行  ← 全在这里
└─ Order.java            50行   ← 只有getter/setter

分层写法:
├─ OrderController       20行   ← 只接收请求
├─ OrderService          30行   ← 只编排流程
├─ OrderDomainService    80行   ← 核心业务
├─ Order                 40行   ← 有业务方法
├─ User                  30行   ← 有业务方法
└─ Product               30行   ← 有业务方法

修改业务规则:

场景: VIP折扣改为8.5折

传统写法:
└─ 在800行Service里找到这一行改
   (担心改错,不敢动)

分层写法:
└─ 在User.getDiscount()里改
   return "VIP".equals(level) ? 0.85 : 1.0;
   (只改一处,清晰明确)

测试:

// 传统写法: 需要Mock数据库
@Test
public void test() {
    // Mock: userMapper, productMapper, orderMapper
    // 准备: 数据库数据
}

// 分层写法: 不需要数据库
@Test
public void test() {
    User user = new User();
    user.setLevel("VIP");
    
    Product product = new Product();
    product.setPrice(100);
    product.setStock(10);
    
    Order order = domainService.createOrder(user, product, 2);
    
    assertEquals(170, order.getAmount());  // 200 * 0.9 - 10
    assertEquals(8, product.getStock());
}

原则

  1. 各司其职 - Controller传话、Service编排、Domain干活、Repository搬砖

  2. 业务在Domain - 业务判断和计算不写在Service里

  3. 充血模型 - Entity有业务方法,不是只有getter/setter

示例:

// 错误: 业务判断在Service
if ("VIP".equals(user.getLevel())) {
    price = price * 0.9;
}

// 正确: 业务判断在User
BigDecimal discount = user.getDiscount();
price = price * discount;

何时使用

判断信号:

需要分层:

  • Service超过200行
  • 业务规则散落各处
  • if/else嵌套超过3层
  • 同样逻辑复制多处
  • 测试很难写

不需要分层:

  • 纯CRUD,没有业务规则
  • 团队只有2-3人
  • 项目周期<3个月

项目类型:

简单CRUD
└─ 传统三层即可
   Controller → Service → Mapper

有业务规则
└─ 使用4层架构
   Controller → Service → Domain → Infrastructure

复杂业务系统
└─ 完整DDD
   聚合根、值对象、限界上下文...

落地

新项目:

第1天: 建包结构
├── controller/
├── service/
├── domain/
│   ├── model/
│   └── service/
└── infrastructure/

第2天: 写第一个功能
├── 先写Domain模型和DomainService
├── 再写Service编排
└── 最后写Controller接口

老项目:

第1周: 抽取Domain模型
└─ User/Product/Order从贫血改为充血

第2周: 抽取DomainService
└─ 把业务规则从Service抽到DomainService

第3周: 加Repository
└─ 隔离Mapper,Domain不直接依赖数据库

第4周: 瘦身Service
└─ Service只保留编排代码

Code Review:

不通过:
- Service写了业务判断
- Controller直接调Mapper
- Domain对象只有getter/setter

通过:
- Service只有编排代码
- 业务规则在Domain层
- Domain对象有业务方法

疑问

Q: 转换代码太多?

会有一些转换:

Request → DTO → Domain → PO → Domain → DTO → Response

但这是值得的:每层独立变化、业务不暴露技术细节、方便测试

可以用MapStruct自动生成转换代码。

Q: 小项目有必要吗?

看情况:

项目类型建议
纯CRUD传统三层
有业务规则建议分层
复杂业务必须分层

经验:

  • Service < 200行 → 不用分层
  • Service > 200行 → 开始分层
  • Service > 500行 → 必须分层

Q: Repository和Mapper区别?

Mapper (技术概念):
└─ 直接操作数据库
└─ 返回PO对象

Repository (业务概念):
└─ 领域对象集合
└─ 返回Domain对象
└─ 接口在Domain层,实现在Infrastructure层

总结

核心:

  1. Service只编排,不写业务
  2. 业务规则在Domain层
  3. Domain对象是充血的
  4. Repository隔离数据库

价值:

  • 职责清晰
  • 易于测试
  • 易于维护
  • 逻辑可复用

再次说明:

这不是唯一方案。国内大多数公司用的还是传统三层。

本文的方案适合复杂业务、长期维护、Service臃肿的场景。

简单CRUD、快速开发、小团队不适合。

选择架构要务实。


参考:

  • 《领域驱动设计》 - Eric Evans
  • 《实现领域驱动设计》 - Vaughn Vernon
  • 《架构整洁之道》 - Robert C. Martin

欢迎关注,学习不迷路!