DTO / VO / BO / Entity 分层到底怎么用?

1. 为什么要做对象分层?

对象分层的本质不是"类越多越高级",

而是为了把不同类型的变化隔离开

实际开发里,变化大致有 4 类:

  1. 输入变化(前端新增字段、校验规则变化)
  2. 业务变化(折扣策略、计算规则、编排流程变化)
  3. 存储变化(表字段调整、索引策略变化)
  4. 展示变化(页面新增字段、展示格式/脱敏变化)

如果你只用一个大对象或者直接把 Entity 暴露到前端:

  • UI 变化会逼你改数据库对象
  • 规则变化会把入参对象污染成"临时字段垃圾桶"
  • 数据结构变化会反向破坏接口稳定性

正确分层的价值就是:

让变化有"正确的承接层"。

2. 最少概念(够用版)

记住一句话就够:

DTO 进 → BO 算 → Entity 存 → VO 出

对应职责:

  • Command DTO:新增/修改/提交的入参
  • Query DTO:列表/分页/筛选的入参
  • BO:Service 内部的业务计算/编排中间态
  • Entity/DO:数据库表映射对象
  • VO(View Object):返回给前端的展示对象
  • Converter:把"对象转换"集中起来,避免层与层乱引用

你会发现这套体系的核心不是名字,

而是"输入、计算、存储、展示"四类语义的拆分。

3. 什么时候一定要引入 BO?

很多人最大的犹豫是:
BO 是不是"多余的一层"?

判断很简单:

3.1 不用 BO 也可以的场景

  • 纯 CRUD
  • 基本无规则计算
  • 只是简单表单提交

这时你用:

DTO + Entity + VO + Converter

就足够了。

3.2 强烈建议使用 BO 的场景

  • 价格/薪资/计费/清结算
  • 多数据源聚合
  • 规则叠加、计算链长
  • 同一套计算逻辑需要复用(预览/下单/退款反算)

因为 BO 的本质是:

业务计算的"中间态容器"。

它能防止:

  • DTO 被计算字段污染
  • Entity 被业务临时状态污染
  • Controller 变成"规则堆叠现场"

4. 推荐目录结构

text 复制代码
com.example.order
 ├── controller
 ├── service
 ├── dto
 │    ├── command
 │    └── query
 ├── bo
 ├── entity
 ├── vo
 ├── converter
 └── mapper

这个结构的意义很朴素:
你看到包名就知道对象的生命周期与边界。

5. 案例目标与链路

我们用一个简化订单模块来跑通关键链路:

5.1 需求

  1. 价格预览
  2. 确认下单
  3. 订单分页查询

5.2 链路设计

  • 价格预览:

    • PlaceOrderCommandDTO
    • OrderPriceBO(完成规则计算)
    • OrderPreviewVO
  • 下单:

    • PlaceOrderCommandDTO
    • OrderPriceBO
    • OrderEntity + OrderItemEntity 入库
  • 分页:

    • OrderPageQueryDTO
    • → Mapper 查询 OrderEntity
    • → 转 OrderListVO + PageResult

这样的结构保证:

  • 预览和下单复用同一套计算逻辑
  • 计算逻辑不污染前端入参
  • 展示变化不倒灌数据库对象

6. 完整代码示例(带解释)

下面代码是一个"最小可落地模板"。

真实项目中你只需要把"模拟价格/优惠券逻辑"替换为真实查询与规则引擎即可。

6.1 Command DTO(下单入参)

为什么是 Command?

强调它表达的是"动作意图",不是"数据库结构"。

java 复制代码
package com.example.order.dto.command;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;

public class PlaceOrderCommandDTO {

    @NotNull
    private Long userId;

    @NotEmpty
    private List<Item> items;

    private Long couponId; // 可选

    public static class Item {
        @NotNull
        private Long skuId;
        @NotNull
        private Integer qty;

        public Long getSkuId() { return skuId; }
        public void setSkuId(Long skuId) { this.skuId = skuId; }
        public Integer getQty() { return qty; }
        public void setQty(Integer qty) { this.qty = qty; }
    }

    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    public List<Item> getItems() { return items; }
    public void setItems(List<Item> items) { this.items = items; }
    public Long getCouponId() { return couponId; }
    public void setCouponId(Long couponId) { this.couponId = couponId; }
}

6.2 Query DTO(分页查询入参)

Query DTO 的价值 在于:

分页、筛选字段不应该污染命令入参对象,更不应该去影响 Entity。

java 复制代码
package com.example.order.dto.query;

public class OrderPageQueryDTO {
    private Long userId;
    private Integer status;

    private Integer pageNum = 1;
    private Integer pageSize = 20;

    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    public Integer getStatus() { return status; }
    public void setStatus(Integer status) { this.status = status; }
    public Integer getPageNum() { return pageNum; }
    public void setPageNum(Integer pageNum) { this.pageNum = pageNum; }
    public Integer getPageSize() { return pageSize; }
    public void setPageSize(Integer pageSize) { this.pageSize = pageSize; }
}

6.3 BO(价格计算中间态)

这就是本例的"核心价值层"。

你会看到:所有计算结果与中间状态都留在 BO

这样 DTO 和 Entity 都能保持干净。

java 复制代码
package com.example.order.bo;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class OrderPriceBO {

    private Long userId;
    private Long couponId;

    private List<Line> lines = new ArrayList<>();

    private BigDecimal originAmount = BigDecimal.ZERO;
    private BigDecimal couponDiscount = BigDecimal.ZERO;
    private BigDecimal freight = BigDecimal.ZERO;
    private BigDecimal tax = BigDecimal.ZERO;

    private BigDecimal payAmount = BigDecimal.ZERO;

    public static class Line {
        private Long skuId;
        private Integer qty;
        private BigDecimal unitPrice = BigDecimal.ZERO;
        private BigDecimal lineAmount = BigDecimal.ZERO;

        public Long getSkuId() { return skuId; }
        public void setSkuId(Long skuId) { this.skuId = skuId; }
        public Integer getQty() { return qty; }
        public void setQty(Integer qty) { this.qty = qty; }
        public BigDecimal getUnitPrice() { return unitPrice; }
        public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
        public BigDecimal getLineAmount() { return lineAmount; }
        public void setLineAmount(BigDecimal lineAmount) { this.lineAmount = lineAmount; }
    }

    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    public Long getCouponId() { return couponId; }
    public void setCouponId(Long couponId) { this.couponId = couponId; }
    public List<Line> getLines() { return lines; }
    public void setLines(List<Line> lines) { this.lines = lines; }

    public BigDecimal getOriginAmount() { return originAmount; }
    public void setOriginAmount(BigDecimal originAmount) { this.originAmount = originAmount; }
    public BigDecimal getCouponDiscount() { return couponDiscount; }
    public void setCouponDiscount(BigDecimal couponDiscount) { this.couponDiscount = couponDiscount; }
    public BigDecimal getFreight() { return freight; }
    public void setFreight(BigDecimal freight) { this.freight = freight; }
    public BigDecimal getTax() { return tax; }
    public void setTax(BigDecimal tax) { this.tax = tax; }
    public BigDecimal getPayAmount() { return payAmount; }
    public void setPayAmount(BigDecimal payAmount) { this.payAmount = payAmount; }
}

6.4 Entity(落库对象)

Entity 的第一原则:

贴表结构、少业务表达、不要面向 UI。

java 复制代码
package com.example.order.entity;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class OrderEntity {
    private Long id;
    private String orderNo;
    private Long userId;
    private Integer status;

    private BigDecimal originAmount;
    private BigDecimal couponDiscount;
    private BigDecimal freight;
    private BigDecimal tax;
    private BigDecimal payAmount;

    private LocalDateTime createTime;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getOrderNo() { return orderNo; }
    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    public Integer getStatus() { return status; }
    public void setStatus(Integer status) { this.status = status; }

    public BigDecimal getOriginAmount() { return originAmount; }
    public void setOriginAmount(BigDecimal originAmount) { this.originAmount = originAmount; }
    public BigDecimal getCouponDiscount() { return couponDiscount; }
    public void setCouponDiscount(BigDecimal couponDiscount) { this.couponDiscount = couponDiscount; }
    public BigDecimal getFreight() { return freight; }
    public void setFreight(BigDecimal freight) { this.freight = freight; }
    public BigDecimal getTax() { return tax; }
    public void setTax(BigDecimal tax) { this.tax = tax; }
    public BigDecimal getPayAmount() { return payAmount; }
    public void setPayAmount(BigDecimal payAmount) { this.payAmount = payAmount; }

    public LocalDateTime getCreateTime() { return createTime; }
    public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}
java 复制代码
package com.example.order.entity;

import java.math.BigDecimal;

public class OrderItemEntity {
    private Long id;
    private Long orderId;
    private Long skuId;
    private Integer qty;
    private BigDecimal unitPrice;
    private BigDecimal lineAmount;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public Long getOrderId() { return orderId; }
    public void setOrderId(Long orderId) { this.orderId = orderId; }
    public Long getSkuId() { return skuId; }
    public void setSkuId(Long skuId) { this.skuId = skuId; }
    public Integer getQty() { return qty; }
    public void setQty(Integer qty) { this.qty = qty; }
    public BigDecimal getUnitPrice() { return unitPrice; }
    public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
    public BigDecimal getLineAmount() { return lineAmount; }
    public void setLineAmount(BigDecimal lineAmount) { this.lineAmount = lineAmount; }
}

6.5 VO(对外展示对象)

VO 的第一原则:

为页面/接口服务,允许组合字段、文案字段、格式化字段。

java 复制代码
package com.example.order.vo;

import java.math.BigDecimal;
import java.util.List;

public class OrderPreviewVO {
    private BigDecimal originAmount;
    private BigDecimal couponDiscount;
    private BigDecimal freight;
    private BigDecimal tax;
    private BigDecimal payAmount;

    private List<Line> lines;

    public static class Line {
        private Long skuId;
        private Integer qty;
        private BigDecimal unitPrice;
        private BigDecimal lineAmount;

        public Long getSkuId() { return skuId; }
        public void setSkuId(Long skuId) { this.skuId = skuId; }
        public Integer getQty() { return qty; }
        public void setQty(Integer qty) { this.qty = qty; }
        public BigDecimal getUnitPrice() { return unitPrice; }
        public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
        public BigDecimal getLineAmount() { return lineAmount; }
        public void setLineAmount(BigDecimal lineAmount) { this.lineAmount = lineAmount; }
    }

    public BigDecimal getOriginAmount() { return originAmount; }
    public void setOriginAmount(BigDecimal originAmount) { this.originAmount = originAmount; }
    public BigDecimal getCouponDiscount() { return couponDiscount; }
    public void setCouponDiscount(BigDecimal couponDiscount) { this.couponDiscount = couponDiscount; }
    public BigDecimal getFreight() { return freight; }
    public void setFreight(BigDecimal freight) { this.freight = freight; }
    public BigDecimal getTax() { return tax; }
    public void setTax(BigDecimal tax) { this.tax = tax; }
    public BigDecimal getPayAmount() { return payAmount; }
    public void setPayAmount(BigDecimal payAmount) { this.payAmount = payAmount; }
    public List<Line> getLines() { return lines; }
    public void setLines(List<Line> lines) { this.lines = lines; }
}
java 复制代码
package com.example.order.vo;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class OrderListVO {
    private Long id;
    private String orderNo;
    private Integer status;
    private String statusText;
    private BigDecimal payAmount;
    private LocalDateTime createTime;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getOrderNo() { return orderNo; }
    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
    public Integer getStatus() { return status; }
    public void setStatus(Integer status) { this.status = status; }
    public String getStatusText() { return statusText; }
    public void setStatusText(String statusText) { this.statusText = statusText; }
    public BigDecimal getPayAmount() { return payAmount; }
    public void setPayAmount(BigDecimal payAmount) { this.payAmount = payAmount; }
    public LocalDateTime getCreateTime() { return createTime; }
    public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
}

6.6 PageResult(通用分页封装)

分页对象不是必须,但做过中台的人都知道它有多省心:

java 复制代码
package com.example.common;

import java.util.List;

public class PageResult<T> {
    private List<T> records;
    private long total;
    private int pageNum;
    private int pageSize;

    public static <T> PageResult<T> of(List<T> records, long total, int pageNum, int pageSize) {
        PageResult<T> p = new PageResult<>();
        p.records = records;
        p.total = total;
        p.pageNum = pageNum;
        p.pageSize = pageSize;
        return p;
    }

    public List<T> getRecords() { return records; }
    public long getTotal() { return total; }
    public int getPageNum() { return pageNum; }
    public int getPageSize() { return pageSize; }
}

6.7 Converter(保证边界不乱)

很多团队对象分层失败的核心原因只有一个:

转换逻辑散落在 Controller/Service/Mapper 的各个角落。

Converter 就是来解决这个问题的。

java 复制代码
package com.example.order.converter;

import com.example.order.bo.OrderPriceBO;
import com.example.order.dto.command.PlaceOrderCommandDTO;
import com.example.order.entity.OrderEntity;
import com.example.order.entity.OrderItemEntity;
import com.example.order.vo.OrderListVO;
import com.example.order.vo.OrderPreviewVO;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

public class OrderConverter {

    public static OrderPriceBO toPriceBO(PlaceOrderCommandDTO dto) {
        OrderPriceBO bo = new OrderPriceBO();
        bo.setUserId(dto.getUserId());
        bo.setCouponId(dto.getCouponId());

        List<OrderPriceBO.Line> lines = dto.getItems().stream().map(i -> {
            OrderPriceBO.Line l = new OrderPriceBO.Line();
            l.setSkuId(i.getSkuId());
            l.setQty(i.getQty());
            return l;
        }).collect(Collectors.toList());

        bo.setLines(lines);
        return bo;
    }

    public static OrderPreviewVO toPreviewVO(OrderPriceBO bo) {
        OrderPreviewVO vo = new OrderPreviewVO();
        vo.setOriginAmount(bo.getOriginAmount());
        vo.setCouponDiscount(bo.getCouponDiscount());
        vo.setFreight(bo.getFreight());
        vo.setTax(bo.getTax());
        vo.setPayAmount(bo.getPayAmount());

        List<OrderPreviewVO.Line> lines = bo.getLines().stream().map(l -> {
            OrderPreviewVO.Line v = new OrderPreviewVO.Line();
            v.setSkuId(l.getSkuId());
            v.setQty(l.getQty());
            v.setUnitPrice(l.getUnitPrice());
            v.setLineAmount(l.getLineAmount());
            return v;
        }).collect(Collectors.toList());

        vo.setLines(lines);
        return vo;
    }

    public static OrderEntity toOrderEntity(OrderPriceBO bo, String orderNo) {
        OrderEntity e = new OrderEntity();
        e.setOrderNo(orderNo);
        e.setUserId(bo.getUserId());
        e.setStatus(0);

        e.setOriginAmount(bo.getOriginAmount());
        e.setCouponDiscount(bo.getCouponDiscount());
        e.setFreight(bo.getFreight());
        e.setTax(bo.getTax());
        e.setPayAmount(bo.getPayAmount());

        e.setCreateTime(LocalDateTime.now());
        return e;
    }

    public static List<OrderItemEntity> toItemEntities(OrderPriceBO bo, Long orderId) {
        return bo.getLines().stream().map(l -> {
            OrderItemEntity it = new OrderItemEntity();
            it.setOrderId(orderId);
            it.setSkuId(l.getSkuId());
            it.setQty(l.getQty());
            it.setUnitPrice(l.getUnitPrice());
            it.setLineAmount(l.getLineAmount());
            return it;
        }).collect(Collectors.toList());
    }

    public static OrderListVO toListVO(OrderEntity e) {
        OrderListVO vo = new OrderListVO();
        vo.setId(e.getId());
        vo.setOrderNo(e.getOrderNo());
        vo.setStatus(e.getStatus());
        vo.setPayAmount(e.getPayAmount());
        vo.setCreateTime(e.getCreateTime());
        vo.setStatusText(statusText(e.getStatus()));
        return vo;
    }

    private static String statusText(Integer s) {
        if (s == null) return "未知";
        return switch (s) {
            case 0 -> "待支付";
            case 1 -> "已支付";
            case 2 -> "已取消";
            default -> "未知";
        };
    }
}

6.8 Mapper(只处理 Entity)

这是持久层的"边界红线"。

java 复制代码
package com.example.order.mapper;

import com.example.order.entity.OrderEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

@Mapper
public interface OrderMapper {
    int insert(OrderEntity e);

    List<OrderEntity> page(@Param("userId") Long userId,
                           @Param("status") Integer status,
                           @Param("offset") int offset,
                           @Param("limit") int limit);

    long count(@Param("userId") Long userId,
               @Param("status") Integer status);
}
java 复制代码
package com.example.order.mapper;

import com.example.order.entity.OrderItemEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

@Mapper
public interface OrderItemMapper {
    int batchInsert(@Param("items") List<OrderItemEntity> items);
}

6.9 Service(BO 承担计算)

java 复制代码
package com.example.order.service;

import com.example.common.PageResult;
import com.example.order.dto.command.PlaceOrderCommandDTO;
import com.example.order.dto.query.OrderPageQueryDTO;
import com.example.order.vo.OrderListVO;
import com.example.order.vo.OrderPreviewVO;

public interface OrderService {
    OrderPreviewVO preview(PlaceOrderCommandDTO dto);
    Long placeOrder(PlaceOrderCommandDTO dto);
    PageResult<OrderListVO> page(OrderPageQueryDTO query);
}
java 复制代码
package com.example.order.service.impl;

import com.example.common.PageResult;
import com.example.order.bo.OrderPriceBO;
import com.example.order.converter.OrderConverter;
import com.example.order.dto.command.PlaceOrderCommandDTO;
import com.example.order.dto.query.OrderPageQueryDTO;
import com.example.order.entity.OrderEntity;
import com.example.order.entity.OrderItemEntity;
import com.example.order.mapper.OrderItemMapper;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import com.example.order.vo.OrderListVO;
import com.example.order.vo.OrderPreviewVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl implements OrderService {

    private final OrderMapper orderMapper;
    private final OrderItemMapper itemMapper;

    public OrderServiceImpl(OrderMapper orderMapper, OrderItemMapper itemMapper) {
        this.orderMapper = orderMapper;
        this.itemMapper = itemMapper;
    }

    @Override
    public OrderPreviewVO preview(PlaceOrderCommandDTO dto) {
        OrderPriceBO bo = OrderConverter.toPriceBO(dto);
        pricing(bo);
        return OrderConverter.toPreviewVO(bo);
    }

    @Override
    @Transactional
    public Long placeOrder(PlaceOrderCommandDTO dto) {
        OrderPriceBO bo = OrderConverter.toPriceBO(dto);
        pricing(bo);

        String orderNo = genOrderNo();
        OrderEntity order = OrderConverter.toOrderEntity(bo, orderNo);

        orderMapper.insert(order); // 依赖 MyBatis 主键回填
        Long orderId = order.getId();

        List<OrderItemEntity> items = OrderConverter.toItemEntities(bo, orderId);
        itemMapper.batchInsert(items);

        return orderId;
    }

    @Override
    public PageResult<OrderListVO> page(OrderPageQueryDTO query) {
        int offset = (query.getPageNum() - 1) * query.getPageSize();

        List<OrderEntity> list = orderMapper.page(
                query.getUserId(),
                query.getStatus(),
                offset,
                query.getPageSize()
        );

        long total = orderMapper.count(query.getUserId(), query.getStatus());

        List<OrderListVO> vos = list.stream()
                .map(OrderConverter::toListVO)
                .collect(Collectors.toList());

        return PageResult.of(vos, total, query.getPageNum(), query.getPageSize());
    }

    /**
     * 演示版价格计算:
     * 真实项目替换为:
     * - 查询 SKU 真实价格
     * - 查询优惠券规则
     * - 运费/税费策略
     */
    private void pricing(OrderPriceBO bo) {
        // 1) 演示:每个 sku 单价 100
        for (OrderPriceBO.Line line : bo.getLines()) {
            BigDecimal unit = BigDecimal.valueOf(100);
            line.setUnitPrice(unit);
            line.setLineAmount(unit.multiply(BigDecimal.valueOf(line.getQty())));
        }

        // 2) 原价
        BigDecimal origin = bo.getLines().stream()
                .map(OrderPriceBO.Line::getLineAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        bo.setOriginAmount(origin);

        // 3) 优惠券:满 200 减 20
        BigDecimal coupon = BigDecimal.ZERO;
        if (bo.getCouponId() != null && origin.compareTo(BigDecimal.valueOf(200)) >= 0) {
            coupon = BigDecimal.valueOf(20);
        }
        bo.setCouponDiscount(coupon);

        // 4) 运费:原价 < 200 收 10
        BigDecimal freight = origin.compareTo(BigDecimal.valueOf(200)) < 0
                ? BigDecimal.valueOf(10)
                : BigDecimal.ZERO;
        bo.setFreight(freight);

        // 5) 税费:2%
        BigDecimal tax = origin.multiply(BigDecimal.valueOf(0.02));
        bo.setTax(tax);

        // 6) 应付
        BigDecimal pay = origin.subtract(coupon).add(freight).add(tax);
        bo.setPayAmount(pay.max(BigDecimal.ZERO));
    }

    private String genOrderNo() {
        return "OD" + UUID.randomUUID().toString().replace("-", "").substring(0, 18);
    }
}

6.10 Controller(只接 DTO,只吐 VO)

控制器这一层就是"边界门禁"。
它不应该知道数据库结构,只应该处理接口语义。

java 复制代码
package com.example.order.controller;

import com.example.common.PageResult;
import com.example.order.dto.command.PlaceOrderCommandDTO;
import com.example.order.dto.query.OrderPageQueryDTO;
import com.example.order.service.OrderService;
import com.example.order.vo.OrderListVO;
import com.example.order.vo.OrderPreviewVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping("/preview")
    public OrderPreviewVO preview(@RequestBody @Valid PlaceOrderCommandDTO dto) {
        return orderService.preview(dto);
    }

    @PostMapping
    public Long place(@RequestBody @Valid PlaceOrderCommandDTO dto) {
        return orderService.placeOrder(dto);
    }

    @GetMapping
    public PageResult<OrderListVO> page(OrderPageQueryDTO query) {
        return orderService.page(query);
    }
}

7. 这一套分层真正带来的收益

你把这套结构跑一遍后,会明显感受到:

7.1 输入变化不会污染存储

前端新增字段、校验变化
只影响 DTO

7.2 业务变化有明确归宿

折扣/计费策略变化
只动 BO + Service

7.3 展示变化不会倒灌数据库

新增状态文案、展示字段、脱敏
只动 VO + Converter

7.4 复用成本降低

"预览"和"下单"共享一套规则计算
不会出现两份逻辑版本漂移。

8. 可直接写进团队规范的"硬规则"

  • Controller:只接 DTO,只返回 VO
  • Service:复杂规则必须引 BO
  • Mapper/Repository:只处理 Entity/DO
  • 禁止:Entity 直接对外返回
  • 禁止:一个 DTO 贯穿所有场景
  • 必须:对象转换集中在 Converter/MapStruct

9. 结语

对象分层不是"OO 术语游戏",

而是帮助你在真实工程里做到:

变化来了不慌,规则复杂也不乱。

你可以先从这套最小正确实践开始:

Command/Query DTO + BO(复杂时) + Entity + VO + Converter

然后随着业务复杂度升级,再逐步扩展到更完整的架构规范。

相关推荐
云梦谭2 小时前
AI 生成的FreeSWITCH 呼出流程深度分析freeswitch-1.10.12.-release
java·前端·php
随机昵称_1234562 小时前
RSA私钥解密乱码问题
java·非对称加密
龙亘川2 小时前
【课程2.4】开发环境搭建:K8s集群部署、芋道框架集成、ThingsBoard对接
java·容器·kubernetes·智慧城市·智慧城市一网统管 ai 平台
pyniu2 小时前
项目实站day7--功能之营业额统计,用户数量统计
java·开发语言·spring boot·spring
一周困⁸天.2 小时前
K8S-NetworkPolicy
java·开发语言
真上帝的左手2 小时前
3. 代码管理-构建工具-Maven
java·maven
JIngJaneIL3 小时前
基于Java旅游信息推荐系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·旅游
梦未3 小时前
Java多态性与类生命周期
java
CryptoRzz3 小时前
对接印度股票市场数据 (India api) 实时k线图表
java·开发语言·python·区块链·maven