MVC 和 DDD

什么是MVC

Moudel、View、Controller,一般我们的项目,前端View层请求到controller层由Controller层将请求转发到对应的Moudel层去做数据处理然后将结果沿着调用链返回给View。

什么是POJO

POJO(plain old java object)普通的java对象,指那些既没有实现接口也没有继承其他类的对象。

用途: POJO对象通常用于封装数据,它可以包含一些私有字段(属性)和公共的getter和setter方法,用于对属性进行读写操作。POJO对象通常不包含业务逻辑,主要用于传输数据、数据存储和数据交换。 在Java开发中,POJO对象被广泛应用于各种场景,例如:

  • 数据传输: POJO对象用于在不同层之间传输数据,如在业务逻辑层(Service层)和表示层(Presentation层)之间传输数据。

  • 数据存储: POJO对象用于封装数据库中的数据,通常用于与数据库进行交互的ORM(对象关系映射)操作。

  • 数据交换: POJO对象用于与外部系统进行数据交换,如与其他服务进行RESTful API调用、与消息队列进行数据传输等。

  • 单元测试: 由于POJO对象通常不依赖于外部环境,因此在单元测试中往往可以更加方便地创建和操作POJO对象。

什么是DTO、DAO、VO、PO、DO、BO

(基本按照一次请求流程所经历的)

  • VO:(value object)值对象,VO也是用于数据传输的对象,VO通常更加专注于视图层的数据展示。VO对象通常包含了在前端页面展示所需的数据。屏蔽掉密码、创建时间、状态等敏感信息。

  • DTO:(data transfer object)数据传输对象,跨进程/服务数据传输。

  • DO:(domain object)领域对象,通常用于表示业务领域中的实体或业务对象。DO对象通常包含了业务逻辑和数据,是业务逻辑的实体表示。在某些情况下,DO对象可能与PO对象相似,但它们的用途和含义不同。DO对象通常用于表示业务领域中的复杂业务逻辑和业务实体。

  • BO:(business object)业务对象层,BO用于封装业务逻辑,它通常包含了一系列的业务方法,用于实现业务规则和业务流程。BO对象通常会调用DAO对象来实现数据的持久化和访问。

  • DAO:(data access object)数据访问对象,DAO用于封装数据访问逻辑,它负责与数据库进行交互,执行CRUD(创建、读取、更新、删除)操作。DAO对象通常封装了数据库访问的细节,使业务逻辑层能够更加简洁地操作数据。

  • PO:(persistant object)持久对象,通常用于表示与数据库中的表(或文档)相映射的Java对象。PO对象的属性对应数据库表的字段,每个PO对象通常表示数据库中的一条记录。PO对象通常用于ORM(对象关系映射)框架中,如Hibernate、MyBatis等。

示例:

一、本质差异与设计意图对比

类型 核心职责 生命周期 典型特征 反模式
DTO 跨进程/服务数据传输 仅存在于RPC/API调用过程 扁平化结构,可能包含多个聚合根的数据 在DTO中嵌入业务逻辑
VO 面向视图展示 存在于Controller→View层 包含格式化字段(如日期转字符串)、前端状态标记 将VO直接透传给持久层
PO 数据库表映射 与ORM框架生命周期绑定 字段与表严格对应,可能有@Table注解 在PO中写业务逻辑
BO 封装业务规则 存在于Service层 包含领域方法(如calculateTax() BO退化为纯数据容器

二、实战场景差异示例

场景:电商订单创建
  1. PO(持久化对象):
java 复制代码
@Entity
@Table(name = "orders")
public class OrderPO {
    @Id private Long id;
    private BigDecimal amount; // 与数据库字段完全一致
    // getters/setters...
}
  1. BO(业务对象):
java 复制代码
public class OrderBO {
    private OrderPO po;
    private List<ItemBO> items;
    
    public BigDecimal calculateDiscount() {
        // 复杂的业务规则计算
        return items.stream().map(ItemBO::getDiscount).reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
  1. DTO(跨服务传输):
java 复制代码
public class OrderDTO {
    private String orderNo; // 可能组合多个PO的字段
    private List<ItemDTO> items; // 嵌套其他DTO
    // 不包含任何业务方法
}
  1. VO(前端展示):
java 复制代码
public class OrderVO {
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createTime; // 日期格式化
    private String statusText; // 将状态码转为文字
    // 可能包含前端特有的字段
}

三、关键设计原则

  1. 转换边界

    • Controller层:DTO → VO
    • Service层:PO → BO → DTO
    • DAO层:只接触PO
  2. 分层防腐

    graph LR DB[Database] --> PO PO -->|DAO| BO BO -->|Service| DTO DTO -->|RPC| OtherService DTO -->|Controller| VO VO -->|JSON| Frontend
  3. 性能权衡

    • DTO提倡适度冗余(减少远程调用)
    • VO允许计算字段(如totalPrice = price * quantity
    • PO必须严格映射数据库(避免ORM性能问题)

四、常见误区

  1. 贫血模型陷阱:将所有对象都变成只有getter/setter的贫血对象
  2. 层间污染 :在VO中暴露passwordHash等敏感字段
  3. 过度转换:在简单CRUD场景强行引入BO导致过度设计

建议根据项目规模灵活调整:

  • 小型项目:可以合并PO/BO/DTO
  • 复杂领域:严格分层,使用MapStruct等工具处理转换

什么是DDD

领域驱动设计(Domain-driven-design)

一、先明确:DDD 的核心思想(解决什么问题?)

传统分层开发(如 MVC)的痛点:

  • 业务逻辑分散在 Service 层,BO 往往变成「贫血对象」(只有 getter/setter,没有业务行为);
  • 技术分层(Controller/Service/DAO)主导,业务术语被技术术语覆盖(比如 "订单状态流转" 变成 "updateOrderStatus 方法");
  • 跨业务模块的依赖混乱(比如订单模块直接操作商品数据库表)。

DDD 的核心解决方案:

  1. 以业务领域为核心:先梳理业务规则、术语,再设计代码(而不是先想 "要不要加个 DAO");
  2. 统一语言(Ubiquitous Language) :开发和业务人员用同一套术语(比如 "聚合根""限界上下文" 对应业务里的 "订单主体""订单模块");
  3. 拆分复杂领域:用「限界上下文」隔离不同业务模块,用「聚合根」管理模块内的核心逻辑;
  4. 领域模型富化 :让业务对象(对应之前的BO)拥有自己的业务行为(比如订单自己能计算总金额、流转状态)。

二、DDD 核心概念拆解(结合电商订单示例)

我们以「电商订单创建」为核心场景,对应讲解 DDD 的关键术语:

1. 限界上下文(Bounded Context)------ 业务模块的 "边界"
  • 定义:围绕某一业务功能的独立领域,内部有统一的语言和规则,外部通过明确接口交互(类似 "微服务的边界");

  • 电商示例

    • 订单上下文(核心):负责订单创建、状态流转、金额计算;
    • 商品上下文:负责商品信息管理、库存扣减;
    • 用户上下文:负责用户信息、收货地址管理;
    • 支付上下文:负责支付发起、回调处理;
  • 与传统分层的区别:传统分层是 "技术维度"(Controller/Service),限界上下文是 "业务维度"(订单 / 商品 / 用户),一个上下文可能包含多个技术分层。

2. 聚合根(Aggregate Root)------ 领域模型的 "主心骨"
  • 定义:某一业务场景的核心实体,聚合了其他相关实体 / 值对象,对外提供统一接口,负责维护内部数据的一致性;

  • 核心特点

    • 有唯一标识(比如订单 ID);
    • 内部实体不能脱离聚合根独立存在(比如 "订单项" 不能没有 "订单");
    • 外部只能通过聚合根访问内部数据(比如要改订单项,必须通过订单对象操作);
  • 电商示例Order(订单)是聚合根,聚合了 OrderItem(订单项)、OrderAmount(订单金额)、OrderStatus(订单状态)等。

3. 实体(Entity)------ 有唯一标识的业务对象
  • 定义:具有唯一标识、可变化的业务对象(比如订单、订单项、商品);

  • 与值对象的区别

    • 实体:有唯一 ID,状态可改(比如订单状态从 "待支付" 变 "已支付");
    • 值对象(Value Object):无唯一 ID,不可变,仅承载数据(比如金额、地址、时间区间);
  • 电商示例

    • 实体:Order(订单 ID 唯一)、OrderItem(订单项 ID 唯一)、Product(商品 ID 唯一);
    • 值对象:Money(金额:100 元,包含数值和货币单位,不可变)、Address(收货地址:省市区街道,不可变)、OrderStatus(订单状态:枚举值,不可变)。
4. 领域服务(Domain Service)------ 跨实体的业务逻辑
  • 定义:当业务逻辑不属于某一个实体 / 值对象时,封装为领域服务(类似 "业务工具类",但聚焦领域规则);

  • 核心特点

    • 无状态(不存储业务数据,仅处理逻辑);
    • 依赖多个实体 / 聚合根(比如订单创建需要检查商品库存、计算折扣);
  • 电商示例

    • OrderCreationService(订单创建服务):协调「订单聚合根」「商品上下文」「折扣规则」,完成订单创建;
    • DiscountCalculationService(折扣计算服务):根据商品类型、用户等级计算折扣金额。
5. 仓储(Repository)------ 领域模型的 "持久化管家"
  • 定义 :封装领域模型的持久化操作(增删改查),隔离领域层和数据访问层(对应你之前的DAO,但更聚焦领域);

  • 核心特点

    • 接口定义在领域层(比如OrderRepository),实现放在基础设施层;
    • 方法名用业务术语(比如findByOrderNo而不是selectByOrderNo);
    • 仅聚合根需要仓储(内部实体通过聚合根间接持久化);
  • 电商示例

    • 领域层接口:OrderRepository(定义save(Order order)findById(Long id));
    • 基础设施层实现:OrderRepositoryImpl(用 MyBatis 操作OrderPO,将Order聚合根转换为OrderPO持久化)。
6. 领域事件(Domain Event)------ 业务事件的 "通知器"
  • 定义:领域内发生的重要业务事件,用于解耦跨聚合根 / 上下文的逻辑(比如 "订单创建成功" 后,需要通知库存扣减、积分增加);

  • 电商示例

    • OrderCreatedEvent(订单创建事件):订单创建成功后发布该事件;
    • 商品上下文订阅该事件,执行库存扣减;
    • 用户上下文订阅该事件,执行积分增加。

三、DDD 分层架构(与传统 MVC/POJO 分层对应)

DDD 的分层架构更聚焦业务,与你之前了解的分层有明确对应关系,我们用表格对比:

DDD 分层 核心职责 对应传统分层 核心组件 / 对象
表示层(Presentation) 接收请求、返回响应,转换数据格式 Controller 层 OrderVO(视图对象)、OrderDTO(传输对象)
应用层(Application) 协调领域对象,编排业务流程(无核心业务逻辑) Service 层(协调逻辑) OrderApplicationService(订单应用服务)
领域层(Domain) 核心业务逻辑、领域模型(实体 / 值对象 / 聚合根) Service 层(核心逻辑) Order(聚合根)、OrderItem(实体)、OrderCreationService(领域服务)
基础设施层(Infrastructure) 提供技术支持(持久化、缓存、消息队列) DAO 层、工具类 OrderRepositoryImpl(仓储实现)、OrderPO(持久对象)、数据库连接池
关键对应关系:
  • 你之前的BO:对应 DDD 的「聚合根 / 实体」(如果BO有业务行为);
  • 你之前的DAO:对应 DDD 的「仓储实现」(RepositoryImpl);
  • 你之前的DTO:在 DDD 中用于「应用层与表示层 / 跨上下文传输」;
  • 你之前的PO:对应 DDD 的「基础设施层的持久化对象」(仅用于仓储与数据库交互)。

四、DDD 实战:电商订单创建的完整流转(结合你的示例)

我们用 DDD 的思路,重新走一遍「订单创建」的流程,对比传统分层的差异:

传统分层流程(你之前的示例):
  1. Controller 接收前端请求(OrderVO);
  2. Service 层调用OrderBO,组装OrderPO
  3. Service 层调用DAO,保存OrderPO
  4. 返回OrderDTO给前端。
DDD 流程(聚焦业务):
1. 表示层:接收请求,转换为应用层命令
  • 前端传来创建订单的请求(包含用户 ID、商品 ID 列表、收货地址);
  • Controller 将请求转换为「应用层命令对象」CreateOrderCommand(类似DTO,但更聚焦命令语义);
  • 调用应用层服务 OrderApplicationService.createOrder(command)
2. 应用层:编排业务流程,协调领域对象

OrderApplicationService.createOrder() 的逻辑:

java 复制代码
// 应用层服务:仅编排流程,不写核心业务逻辑
public OrderDTO createOrder(CreateOrderCommand command) {
    // 1. 从用户上下文获取用户信息(跨上下文调用,通过API或事件)
    UserDTO user = userContextClient.getUserById(command.getUserId());
    
    // 2. 从商品上下文获取商品信息(跨上下文调用)
    List<ProductDTO> products = productContextClient.getProductsByIds(command.getProductIds());
    
    // 3. 调用领域服务,创建订单聚合根(核心业务逻辑在领域层)
    Order order = orderDomainService.createOrder(user, products, command.getAddress());
    
    // 4. 调用仓储,保存订单(领域层不依赖数据库,由基础设施层实现)
    orderRepository.save(order);
    
    // 5. 发布领域事件(通知其他上下文)
    domainEventPublisher.publish(new OrderCreatedEvent(order.getId(), user.getId(), products));
    
    // 6. 转换为DTO,返回给表示层
    return OrderDTOConverter.toDTO(order);
}
3. 领域层:核心业务逻辑,由聚合根 / 领域服务实现

OrderDomainService.createOrder() 的逻辑(核心中的核心):

java 复制代码
// 领域服务:封装跨实体的业务逻辑
public Order createOrder(UserDTO user, List<ProductDTO> products, Address address) {
    // 1. 校验商品库存(调用商品领域服务,跨上下文)
    productDomainService.checkStock(products);
    
    // 2. 创建订单聚合根(聚合根负责维护内部一致性)
    Order order = new Order();
    order.setUserId(user.getId());
    order.setAddress(address);
    order.setStatus(OrderStatus.PENDING_PAYMENT); // 初始状态:待支付
    
    // 3. 添加订单项(通过聚合根的方法操作内部实体,保证一致性)
    BigDecimal totalAmount = BigDecimal.ZERO;
    for (ProductDTO product : products) {
        OrderItem item = new OrderItem();
        item.setProductId(product.getId());
        item.setProductName(product.getName());
        item.setPrice(product.getPrice());
        item.setQuantity(1); // 简化:默认购买1件
        
        // 聚合根内部计算小计
        item.calculateSubtotal(); 
        order.addOrderItem(item);
        
        totalAmount = totalAmount.add(item.getSubtotal());
    }
    
    // 4. 计算订单总金额(聚合根的业务行为)
    order.setTotalAmount(new Money(totalAmount, "CNY"));
    
    // 5. 应用用户折扣(领域服务:跨实体逻辑)
    Discount discount = discountDomainService.calculateDiscount(user, order);
    order.applyDiscount(discount);
    
    return order;
}
4. 基础设施层:持久化、跨上下文调用的技术实现
  • 仓储实现OrderRepositoryImpl 实现 OrderRepository 接口,将 Order 聚合根转换为 OrderPO,用 MyBatis 保存到数据库;

    java 复制代码
    // 仓储实现:基础设施层,依赖数据库
    @Repository
    public class OrderRepositoryImpl implements OrderRepository {
        @Autowired
        private OrderMapper orderMapper;
        
        @Override
        public void save(Order order) {
            // 转换:Order(领域对象)→ OrderPO(持久对象)
            OrderPO po = OrderPOConverter.toPO(order);
            orderMapper.insert(po);
            
            // 保存订单项(通过聚合根获取内部实体)
            for (OrderItem item : order.getOrderItems()) {
                OrderItemPO itemPO = OrderItemPOConverter.toPO(item);
                itemPO.setOrderId(po.getId());
                orderItemMapper.insert(itemPO);
            }
        }
    }
  • 跨上下文调用:通过 Feign 调用商品 / 用户上下文的 API(或用消息队列解耦);

  • 领域事件发布 :用 RocketMQ/Kafka 发布 OrderCreatedEvent,商品上下文订阅后扣减库存。

五、DDD 的核心优势(对比传统开发)

  1. 业务逻辑更清晰 :核心规则集中在领域层,聚合根 / 实体自己 "懂业务"(比如订单自己能算总金额),而不是散在Service层;
  2. 可维护性更高:限界上下文隔离了业务模块,修改订单逻辑不会影响商品模块;
  3. 可扩展性更强:新增业务(比如 "拼团订单"),只需新增一个聚合根和领域服务,不改动现有代码;
  4. 沟通更高效:统一语言让开发和业务人员用 "订单聚合根""领域事件" 沟通,避免歧义。

六、常见误区(结合你的示例提醒)

  1. 把聚合根当 "贫血对象" :如果Order只有 getter/setter,没有addOrderItem()calculateTotal()等业务行为,就失去了 DDD 的核心价值;
  2. 过度拆分限界上下文:小型项目不需要拆太多上下文(比如电商初创期,订单和商品可以在同一个上下文);
  3. 领域层依赖基础设施 :比如Order实体直接调用OrderMapper,会导致领域层耦合数据库,违反 "依赖倒置" 原则(正确做法:领域层定义OrderRepository接口,基础设施层实现);
  4. 混淆值对象和实体:比如把 "地址" 设计成实体(有 ID),但地址本身不需要独立存在,应该是值对象(不可变,随订单一起保存)。

总结

DDD 不是 "新的技术框架",而是「以业务为核心的设计思想」:

  • 传统开发:技术分层(MVC)→ 填充业务逻辑;
  • DDD 开发:业务领域(限界上下文 / 聚合根)→ 技术分层适配业务。

结合你的示例:

  • 你之前的BO如果有业务行为(比如OrderBO.calculateTotal()),就是 DDD 的「聚合根 / 实体」;
  • 你之前的DAO如果封装了领域模型的持久化(而不是直接操作PO),就是 DDD 的「仓储实现」;
  • 你之前的DTO如果用于跨上下文传输(比如订单→商品),就是 DDD 的「上下文间传输对象」。

如果是复杂业务系统(比如电商、金融),DDD 能帮你理清业务脉络;如果是简单 CRUD 项目,也可以简化使用(比如只保留聚合根和仓储)。

相关推荐
一只叫煤球的猫1 小时前
从 JDK1.2 到 JDK21:ThreadLocal的进化解决了什么问题
java·后端·面试
BingoGo2 小时前
PHP8.6 新的 RFC 提案 Context Managers 优雅管理资源生命周期
后端·php
南雨北斗2 小时前
kotlin抽象类(与接口的区别)
后端
sino爱学习2 小时前
Arthas 线上常用命令速查手册:Java 诊断神器,5 分钟定位线上问题!
后端
songroom2 小时前
Rust: 量化策略回测与简易线程池构建(MPMC)
开发语言·后端·rust
绝无仅有3 小时前
面试日志elk之ES数据查询与数据同步
后端·面试·架构
码农BookSea3 小时前
用好PowerMock,轻松搞定那些让你头疼的单元测试
后端·单元测试
绝无仅有3 小时前
大场面试之最终一致性与分布式锁
后端·面试·架构
晨晖24 小时前
springboot的Thymeleaf语法
java·spring boot·后端