数据访问层应该如何构建?

10
我最初设计了我的系统,遵循s#架构示例(不幸的是,我没有使用NHibernate),该示例在此codeproject文章中概述。基本思想是,对于每个需要与持久层通信的领域对象,您将在不同的库中拥有相应的数据访问对象。每个数据访问对象实现一个接口,当领域对象需要访问数据访问方法时,它总是针对一个接口编码,而不是针对DAO本身。
当时,我认为这种设计非常灵活。然而,随着我的领域模型中对象数量的增加,我发现自己开始质疑是否存在组织问题。例如,几乎每个领域中的对象都会有一个相应的数据访问对象和数据访问对象接口。不仅如此,而且每个对象都位于不同的位置,如果我想做一些简单的事情,比如移动一些命名空间,那么就更难维护了。
有趣的是,许多这些DAO(和它们对应的接口)非常简单-最常见的只有一个GetById()方法。我最终得到了一堆对象,例如
public interface ICustomerDao {
  Customer GetById(int id);
}

public interface IProductDao {
  Product GetById(int id);
}
public interface IAutomaticWeaselDao {
  AutomaticWeasel GetById(int id);
}

他们的实现通常也很琐碎。这让我想,也许换一个方向会更简单,比如为简单的数据访问任务使用单个对象,而为那些需要更复杂的东西的创建专用的Data Access对象。
public interface SimpleObjectRepository {
      Customer GetCustomerById(int id);
      Product GetProductById(int id);
      AutomaticWeasel GetAutomaticWeaselById(int id);
      Transaction GetTransactioinById(int id);
}
public interface TransactionDao {
  Transaction[] GetAllCurrentlyOngoingTransactionsInitiatedByASweatyGuyNamedCarl();
}

有人有过这种架构的经验吗?总体而言,我对现在的设置非常满意,唯一的问题是管理所有这些小文件。不过,我仍然想知道其他关于数据访问层结构的方法。

5个回答

3
除非在简单系统中,否则我建议不要采用简单的方法,通常情况下,我认为最好为每个聚合创建一个自定义存储库,并尽可能多地封装适当的逻辑。
因此,我的方法是为每个需要它的聚合(例如CustomerRepository)创建一个存储库。这将具有Add(保存)方法和(如果适用于该聚合)Remove(删除)方法。它还将拥有任何其他适用的自定义方法,包括查询(GetActive),也许一些查询可以接受规范。
这听起来很费力,但除了自定义查询之外,大部分代码都很简单,至少如果您使用现代ORM,则可以实现继承(ReadWriteRepositoryBase where T:IAggregateRoot)和/或组合(调用RepositoryHelper类)。基类可能具有适用于所有情况的方法,例如GetById。
希望这可以帮助到您。

2
我使用PHP进行工作,但是我为我的数据访问层设置了类似的东西。我实现了一个接口,看起来像这样:
interface DataAccessObject
{
public static function get(array $filters = array(), array $order = array(), array $limit = array());
public function insert();
public function update();   
public function delete();
}

然后我的每个数据访问对象都是这样工作的:
class DataAccessObject implements DataAccessObject
{
    public function __construct($dao_id = null) // So you can construct an empty object
    {
        // Some Code that get the values from the database and assigns them as properties
    }
    public static function get(array $filters = array(), array $order = array(), array $limit = array()) {};  // Code to implement function
    public function insert() {};  // Code to implement function
    public function update() {};  // Code to implement function 
    public function delete() {};  // Code to implement function        
}

我目前正在手动构建每个数据访问对象类,因此当我添加一个表或修改数据库中现有的表时,显然必须手动编写新代码。在我的情况下,这仍然比我们的代码库要好得多。
但是,您还可以使用SQL元数据(假设您拥有相当可靠的数据库设计,利用外键约束等)来生成这些数据访问对象。然后理论上,您可以使用单个父DataAccessObject类来构建类的属性和方法,甚至自动构建与数据库中其他表的关系。这基本上会实现您所描述的相同功能,因为然后您可以扩展DataAccessObject类以提供自定义方法和属性,以处理某些需要手动构建代码的情况。
作为.NET开发的附注,您是否看过处理数据访问层底层结构的框架,例如Subsonic?如果没有,我建议您研究一下这样的框架:http://subsonicproject.com/
或者对于PHP开发,像Zend Framework这样的框架会提供类似的功能:http://framework.zend.com

以前从未听说过Subsonic,谢谢你的提示。看起来非常有趣。 - George Mauer

2

乔治,我知道你的感受。比利的架构对我来说很有意义,但创建容器、Imapper 和 mapper 文件都很痛苦。如果你使用 NHibernate,则需要相应的 .hbm 文件以及通常几个单元测试脚本来检查一切是否正常。

我假设即使你不使用 NHibernate,你仍然使用通用基类来加载/保存你的容器,例如:

public class BaseDAO<T> : IDAO<T> 
{
     public T Save(T entity)
     { 
       //etc......
     }
}
public class YourDAO : BaseDAO<YourEntity>
{
}

如果没有NHibernate,我猜你会使用反射或其他机制来确定要调用的SQL/SPROC?

无论哪种方式,我的想法是,如果DAO只需要执行在基类中定义的基本CRUD操作,则不需要编写自定义映射器和接口。我能想到的唯一实现这一点的方法是使用Reflection.Emit动态地在运行时创建DAO。


1

我也在我的DAO中使用仓储模式,并且我非常满意。是的,你最终会得到相当多的小类,但它非常易于维护。如果您使用IQueriable接口(LINQ),它会更加强大。如果您具有相当一致的数据库结构,还可以使用泛型(类似于T GetById<T>(int id))。


0
第二种方法的主要缺点在于单元测试方面--模拟大型工厂方法(如SimpleObjectRepository)需要比仅模拟ICustomerDao更多的工作。但是,对于高度相关的对象(其中大多数将在任何单元测试中使用),我的项目采用了第二种方法,因为它减轻了心理负担并使其更易于维护。
我会找出使您的系统更易于维护和理解的方法,并实施它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接