Dao和Repository,你还傻傻分不清吗?

博客讨论了DAO和Repository在贫血模型与领域驱动设计中的角色。DAO用于隔离数据库操作,而Repository作为聚合根的集合,负责保持对象的完整性和一致性。作者主张即使ORM框架进步,仍将需要DAO来分离持久化逻辑,以避免模型职责过重,并支持多种数据库。同时,Repository关注于业务逻辑,确保集合操作的正确性。

DAO到底有没有必要?

        贫血模型中的DAO或领域模型中的Repository到底有没有必要?有人认为DAO或者说Repository是充血模型的大敌,对此我无论如何也不赞同。DAO或Repository是负责持久化逻辑的,如果取消掉DAO或Repository,将持久化逻辑直接写入到model对象中,势必造成model对象承担不必要的职责。虽然现在的ORM框架已经做得很好了,持久化逻辑还是需要大量的代码,持久化逻辑的掺入会使model中的业务逻辑变得模糊。允许去掉DAO的一个必要条件就是Java的的持久化框架必须足够先进,持久化逻辑的引入不会干扰业务逻辑,我认为这在很长一段时间内将无法做到。在rails中能够将DAO去掉的原因就是rail中实现持久化逻辑的代码很简洁直观,这也与ruby的表达能力强有关系。DAO的另外一个好处隔离数据库,这可以支持多个数据库,甚至可以支持文件存储。基于DAO的这些优点,我认为,即使将来Java的持久化框架做得足够优秀,使用DAO将持久化逻辑从业务逻辑中分离开来还是十分必要的,况且它们本身就应该分离。

        争论的焦点到了DAO上,确实,实践中很多项目最终实施DDD的结果就是把所有的DAO重命名为Repository。但是我认为DAO和Repository很像,但是不是一个东西,因为它们出发点不同。

为什么要有DAO?
        因为之前,很早之前,我们对于框架中立性还很受用。DAO给了我们可以随时把Hibernate换成ibatis的幻觉,所以我们要有一个地方隔离了框架。
        而且DAO集中了所有的查询,方便了性能调优人员。同时也鼓励了查询的重用,同样方便了调优。

为什么要有Repository?
        在我看来,与其说Publication Repository,不如说Publications。Repository是一个集合对象,它封装了集合的逻辑。因为它具有封装性,所以它应该负责保持这个集合的状态,比如拒绝一些非法的的修改。Repository的入口是聚合根。

Java代码 

  1. class PublicationRepository {  
  2.   public void save(Publication pub) {  
  3.    if (hasSameName(pub)) {  
  4.      throw new InvalidPublicationException();  
  5.    }  
  6.    dao.save(pub);  
  7.   }  
  8. }  


        另外,Repository只应该负责Aggregate Root。对于被Aggregate的对象,应该用Navigation,也就是在关系之间游走来获取。所以不是所有的查询都必须由Repository来完成,比如说:

Java代码 

  1. class Contact {  
  2.   private List<ContactNote> contactNotes = new ArrayList<ContactNote>();  
  3.   public void contactedBy(User accountManager, DateTime time){  
  4.     ContactNote contactNote = new ContactNote(this, accountManager, time);  
  5.     if (isDuplicated(contactNote)) {  
  6.       throw new InvalidContactNote();  
  7.     }  
  8.     contactNotes.add(contactNote);  
  9.   }  
  10.   private boolean isDuplicated(ContactNote contactNote) {  
  11.     // 查询contactNotes  
  12.     return xxx;  
  13.   }  
  14. }  


        现状是,对象之间的关联不可查询导致了,很多这样的查询必须通过xxxDao,xxxRepository来完成。其实它们都不应该插手。

        理想情况下,只有业务开始的时候用repository加载对象,在结束的时候用repository把对象存储回去,中间都是领域对象在互相作用。而DAO,可以以Generic Query Builder的形式存在,不过和它之前被发明出来的意图已经不是一个东西了。

        DAO原本的作用就是隔离数据库的影响,没有业务逻辑。而Repository更抽象,从概念上来说是一个可以全局访问的集合,从这个意义上来讲对你所举Publication Repository,使用add(Publication pub)作为方法签名要更好一些。Repository也负责保持完整对象的完整性,PublicationRepository的例子也说明了这一点,另外一个例子,但从数据库重建一个对象时,由于外部原因,对象已经变得不完整,将它恢复为一个完整的对象或者直接抛异常也是Repostory的责任,它可以将这种保证对象完整性的责任委托给别的对象(如Factory)。

<think> 在领域驱动设计(DDD)中,**仓储(RepositoryDAO(Data Access Object)都是数据访问模式,但职责抽象层次不同**。以下是详细区别: --- ### 1. **职责差异** #### DAO(数据访问对象) - **核心职责**:封装对数据库的CRUD操作,直接操作数据表。 - **作用范围**:针对单个实体或表,提供细粒度的数据访问。 - **代码示例**: ```java public interface UserDAO { // 直接操作数据库表 void insert(UserRecord record); void update(UserRecord record); void delete(Long id); UserRecord findById(Long id); List<UserRecord> findAll(); } ``` #### 仓储(Repository) - **核心职责**: - 作为聚合根的集合视图,模拟内存中的对象集合。 - 提供领域层所需的聚合根操作(如`add()`, `get()`, `remove()`)。 - **作用范围**:以聚合根为单位操作数据,隐藏底层持久化细节。 - **代码示例**: ```java public interface OrderRepository { // 面向领域模型(聚合根) OrderId save(Order order); // 保存整个聚合 Optional<Order> findById(OrderId id); void delete(Order order); } ``` --- ### 2. **抽象层次** | **维度** | DAO | Repository | |----------------|----------------------|---------------------| | **操作对象** | 数据表记录(Record) | 领域对象(聚合根) | | **方法语义** | 基于数据操作(CRUD)| 基于领域语义 | | **返回结果** | 原始数据或DTO | 聚合根(领域模型) | | **依赖方向** | 依赖数据库实现 | 领域层定义接口 | --- ### 3. **实现差异** #### DAO实现(与表结构强耦合) ```java // DAO操作的数据对象(与数据库表对应) public class UserRecord { private Long id; private String name; // 数据库字段直接映射 private LocalDateTime createTime; } // DAO实现(MyBatis示例) public class UserDAOImpl implements UserDAO { @Override public void insert(UserRecord record) { sqlSession.insert("insertUser", record); } // ... } ``` #### 仓储实现(聚合根转换) ```java // 仓储接口定义在领域层 public interface UserRepository { User findById(UserId id); void save(User user); } // 仓储实现在基础设施层(依赖DAO) public class UserRepositoryImpl implements UserRepository { private final UserDAO userDAO; private final RoleDAO roleDAO; // 可能关联其他DAO @Override public User findById(UserId id) { UserRecord record = userDAO.findById(id.getValue()); List<RoleRecord> roleRecords = roleDAO.findByUserId(id.getValue()); // 将多个DAO结果组装为聚合根 return UserAssembler.toDomain(record, roleRecords); } @Override public void save(User user) { // 将聚合根拆分为多个DAO操作 UserRecord record = UserAssembler.toRecord(user); userDAO.update(record); List<RoleRecord> roles = RoleAssembler.toRecords(user.getRoles()); roleDAO.batchUpdate(roles); } } ``` --- ### 4. **为什么需要仓储?** - **领域模型完整性**:仓储确保聚合根作为整体被加载保存(例如,加载订单时同时加载订单项)。 - **解耦领域与持久化**:领域层仅依赖仓储接口,不关心具体数据库(SQL/NoSQL)。 - **统一聚合操作入口**:避免业务逻辑分散在多个DAO中。 --- ### 5. **关键区别总结** | **特性** | DAO | Repository | |------------------|-------------------------|--------------------------| | **所属层** | 数据访问层 | 领域层(接口) + 基础设施层(实现) | | **聚合处理** | 不支持(单表操作) | 支持(跨多表组装聚合) | | **事务边界** | 不管理事务 | 通常与聚合的事务一致 | | **领域逻辑感知** | 无 | 理解聚合的完整生命周期 | --- ### 典型误用 ```java // ❌ 错误:在应用服务中直接使用DAO public class OrderService { private final OrderDAO orderDAO; // 直接依赖DAO(低层抽象) private final OrderItemDAO itemDAO; public void addItem(Long orderId, Item item) { // 业务逻辑与持久化细节混杂 OrderRecord order = orderDAO.findById(orderId); itemDAO.insert(item); orderDAO.updateTotalPrice(orderId, order.getTotalPrice() + item.getPrice()); } } ``` --- §§相关问题§§: 1. 在DDD中,仓储接口应该定义在哪一层? 2. 如何实现跨聚合的事务一致性? 3. 仓储模式是否违反依赖倒置原则(DIP)? 4. 值对象(Value Object)是否需要单独的仓储? </think>在领域驱动设计(DDD)中,**仓储(RepositoryDAO(Data Access Object)都是数据访问模式,但设计目标抽象层次有本质区别**。以下是详细对比: --- ### 1. **核心职责差异** #### DAO(数据访问对象) - **定位**:数据持久化的**技术实现细节** - **职责**:封装对单张数据库表的CRUD操作 - **操作对象**:数据库记录(如`UserRecord`) - **方法示例**: ```java // DAO接口(面向数据表) public interface UserDAO { void insert(UserRecord record); void update(UserRecord record); void delete(Long id); UserRecord findById(Long id); List<UserRecord> findAll(); } ``` #### 仓储(Repository) - **定位**:领域模型的**集合抽象** - **职责**: - 管理聚合根的完整生命周期 - 维护聚合的业务一致性边界 - **操作对象**:聚合根(如`Order`) - **方法示例**: ```java // 仓储接口(面向领域模型) public interface OrderRepository { OrderId save(Order order); // 保存整个聚合 Optional<Order> findById(OrderId id); void remove(Order order); List<Order> findPendingOrders(); } ``` --- ### 2. **抽象层次对比** | **维度** | DAO | Repository | |------------------|--------------------------------|--------------------------------| | **目标** | 解决数据如何存储 | 解决领域对象如何被访问 | | **操作粒度** | 细粒度(单表操作) | 粗粒度(聚合根为单位) | | **返回结果** | 原始数据/DTO | 领域对象(聚合根) | | **业务语义** | 无业务含义(纯技术接口) | 含领域语义(如`findPendingOrders()`) | | **依赖方向** | 依赖具体数据库实现 | 领域层定义接口,基础设施层实现 | --- ### 3. **实现差异示例** #### DAO实现(直接操作表) ```java // MyBatis实现的DAO public class UserDAOImpl implements UserDAO { @Override public UserRecord findById(Long id) { // 直接执行SQL查询 return sqlSession.selectOne("findUserById", id); } } ``` #### 仓储实现(聚合根操作) ```java // 仓储实现(依赖DAO) public class OrderRepositoryImpl implements OrderRepository { private final OrderDAO orderDAO; private final OrderItemDAO itemDAO; @Override public Order findById(OrderId id) { // 1. 查询主订单 OrderRecord orderRecord = orderDAO.findById(id.getValue()); // 2. 查询关联的子项 List<OrderItemRecord> itemRecords = itemDAO.findByOrderId(id.getValue()); // 3. 组装聚合根 return OrderAssembler.toDomain(orderRecord, itemRecords); } } ``` --- ### 4. **关键区别总结** 1. **聚合根支持** - DAO:不知道聚合根概念,可能破坏业务一致性 - Repository:保证聚合根内所有对象被整体加载/保存 2. **事务边界** - DAO:每个方法通常是独立事务 - Repository:单个方法内可包含跨实体的原子操作 3. **领域知识** - DAO:不了解业务规则(如`Order.calculateTotal()`) - Repository:封装领域约束(如保存前检查`Order.isValid()`) 4. **接口设计** - DAO:方法名偏向技术(`insert/update/delete`) - Repository:方法名体现领域语言(`addProduct/cancelOrder`) --- ### 5. **为什么DDD强调Repository而不是DAO?** - **解决阻抗失衡**:数据库模型 ≠ 领域模型 - **保护领域层**:避免业务逻辑污染持久化代码 - **明确上下文边界**:仓储是限界上下文的重要组成部分 - **测试友好**:领域层通过接口依赖仓储,易于Mock --- ### 典型误用 ```java // ❌ 错误:在领域服务中使用DAO public class OrderService { private final OrderDAO orderDAO; // 应使用Repository public void placeOrder(Order order) { // 业务逻辑中混入持久化细节 orderDAO.insert(order); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值