什么是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退化为纯数据容器 |
二、实战场景差异示例
场景:电商订单创建
- PO(持久化对象):
java
@Entity
@Table(name = "orders")
public class OrderPO {
@Id private Long id;
private BigDecimal amount; // 与数据库字段完全一致
// getters/setters...
}
- 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);
}
}
- DTO(跨服务传输):
java
public class OrderDTO {
private String orderNo; // 可能组合多个PO的字段
private List<ItemDTO> items; // 嵌套其他DTO
// 不包含任何业务方法
}
- VO(前端展示):
java
public class OrderVO {
@JsonFormat(pattern = "yyyy-MM-dd")
private Date createTime; // 日期格式化
private String statusText; // 将状态码转为文字
// 可能包含前端特有的字段
}
三、关键设计原则
-
转换边界:
- Controller层:DTO → VO
- Service层:PO → BO → DTO
- DAO层:只接触PO
-
分层防腐:
graph LR DB[Database] --> PO PO -->|DAO| BO BO -->|Service| DTO DTO -->|RPC| OtherService DTO -->|Controller| VO VO -->|JSON| Frontend -
性能权衡:
- DTO提倡适度冗余(减少远程调用)
- VO允许计算字段(如
totalPrice = price * quantity) - PO必须严格映射数据库(避免ORM性能问题)
四、常见误区
- 贫血模型陷阱:将所有对象都变成只有getter/setter的贫血对象
- 层间污染 :在VO中暴露
passwordHash等敏感字段 - 过度转换:在简单CRUD场景强行引入BO导致过度设计
建议根据项目规模灵活调整:
- 小型项目:可以合并PO/BO/DTO
- 复杂领域:严格分层,使用MapStruct等工具处理转换
什么是DDD
领域驱动设计(Domain-driven-design)
一、先明确:DDD 的核心思想(解决什么问题?)
传统分层开发(如 MVC)的痛点:
- 业务逻辑分散在
Service层,BO往往变成「贫血对象」(只有 getter/setter,没有业务行为); - 技术分层(Controller/Service/DAO)主导,业务术语被技术术语覆盖(比如 "订单状态流转" 变成 "updateOrderStatus 方法");
- 跨业务模块的依赖混乱(比如订单模块直接操作商品数据库表)。
DDD 的核心解决方案:
- 以业务领域为核心:先梳理业务规则、术语,再设计代码(而不是先想 "要不要加个 DAO");
- 统一语言(Ubiquitous Language) :开发和业务人员用同一套术语(比如 "聚合根""限界上下文" 对应业务里的 "订单主体""订单模块");
- 拆分复杂领域:用「限界上下文」隔离不同业务模块,用「聚合根」管理模块内的核心逻辑;
- 领域模型富化 :让业务对象(对应之前的
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 的思路,重新走一遍「订单创建」的流程,对比传统分层的差异:
传统分层流程(你之前的示例):
- Controller 接收前端请求(
OrderVO); - Service 层调用
OrderBO,组装OrderPO; - Service 层调用
DAO,保存OrderPO; - 返回
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 的核心优势(对比传统开发)
- 业务逻辑更清晰 :核心规则集中在领域层,聚合根 / 实体自己 "懂业务"(比如订单自己能算总金额),而不是散在
Service层; - 可维护性更高:限界上下文隔离了业务模块,修改订单逻辑不会影响商品模块;
- 可扩展性更强:新增业务(比如 "拼团订单"),只需新增一个聚合根和领域服务,不改动现有代码;
- 沟通更高效:统一语言让开发和业务人员用 "订单聚合根""领域事件" 沟通,避免歧义。
六、常见误区(结合你的示例提醒)
- 把聚合根当 "贫血对象" :如果
Order只有 getter/setter,没有addOrderItem()、calculateTotal()等业务行为,就失去了 DDD 的核心价值; - 过度拆分限界上下文:小型项目不需要拆太多上下文(比如电商初创期,订单和商品可以在同一个上下文);
- 领域层依赖基础设施 :比如
Order实体直接调用OrderMapper,会导致领域层耦合数据库,违反 "依赖倒置" 原则(正确做法:领域层定义OrderRepository接口,基础设施层实现); - 混淆值对象和实体:比如把 "地址" 设计成实体(有 ID),但地址本身不需要独立存在,应该是值对象(不可变,随订单一起保存)。
总结
DDD 不是 "新的技术框架",而是「以业务为核心的设计思想」:
- 传统开发:技术分层(MVC)→ 填充业务逻辑;
- DDD 开发:业务领域(限界上下文 / 聚合根)→ 技术分层适配业务。
结合你的示例:
- 你之前的
BO如果有业务行为(比如OrderBO.calculateTotal()),就是 DDD 的「聚合根 / 实体」; - 你之前的
DAO如果封装了领域模型的持久化(而不是直接操作PO),就是 DDD 的「仓储实现」; - 你之前的
DTO如果用于跨上下文传输(比如订单→商品),就是 DDD 的「上下文间传输对象」。
如果是复杂业务系统(比如电商、金融),DDD 能帮你理清业务脉络;如果是简单 CRUD 项目,也可以简化使用(比如只保留聚合根和仓储)。