数据访问层应该如何构建?
Posted
技术标签:
【中文标题】数据访问层应该如何构建?【英文标题】:How should the Data Access Layer be structured? 【发布时间】:2010-09-13 18:09:33 【问题描述】:我最初按照 s# 架构示例 outlined in this codeproject article 设计我的系统(不幸的是,我没有使用 NHibernate)。基本思想是,对于需要与持久层通信的每个域对象,您将在不同的库中拥有相应的数据访问对象。每个数据访问对象都实现一个接口,当域对象需要访问数据访问方法时,它总是针对接口进行编码,而不是针对 DAO 本身进行编码。
当时,我仍然认为这种设计非常灵活。然而,随着我的领域模型中对象数量的增加,我发现自己在质疑这里是否存在组织问题。例如,几乎域中的每个对象都以相应的数据访问对象和数据访问对象接口结束。不仅如此,如果我想做一些简单的事情,比如在一些命名空间周围移动,每一个都在不同的地方,这更难以维护。
有趣的是,这些 DAO(及其相应的接口)中的许多都是非常简单的生物——最常见的只有一个 GetById() 方法。我最终得到了一大堆对象,例如
public interface ICustomerDao
Customer GetById(int id);
public interface IProductDao
Product GetById(int id);
public interface IAutomaticWeaselDao
AutomaticWeasel GetById(int id);
他们的实现者通常也很琐碎。这让我想知道朝不同的方向前进是否会更简单,也许通过为简单的数据访问任务设置一个对象来改变我的策略,并为那些需要更多东西的人保留专用数据访问对象的创建复杂。
public interface SimpleObjectRepository
Customer GetCustomerById(int id);
Product GetProductById(int id);
AutomaticWeasel GetAutomaticWeaselById(int id);
Transaction GetTransactioinById(int id);
public interface TransactionDao
Transaction[] GetAllCurrentlyOngoingTransactionsInitiatedByASweatyGuyNamedCarl();
有人对这样的架构有任何经验吗?总的来说,我对设置非常满意,因为现在我唯一关心的是管理所有这些小文件。然而,我仍然想知道还有哪些其他构建数据访问层的方法。
【问题讨论】:
【参考方案1】:我还在为我的 DAO 使用repository pattern,我对此非常满意。是的,你最终得到了很多小类,但它非常易于维护。如果您使用IQueriable
接口 (LINQ),它会更强大一些。如果您有一个非常一致的数据库结构,您还可以使用泛型(类似于T GetById<T>(int id)
)。
【讨论】:
【参考方案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
【讨论】:
以前从未听说过亚音速,感谢您的提示。看起来很有趣【参考方案3】:乔治,我很清楚你的感受。 Billy 的架构对我来说很有意义,但是创建容器、Imapper 和映射器文件的需求很痛苦。然后,如果您使用 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。
【讨论】:
【参考方案4】:第二种方法的主要缺点在于单元测试——模拟像 SimpleObjectRepository 这样的大型工厂方法比只模拟 ICustomerDao 需要更多的工作。但是,我的项目采用第二种方法来处理高度相关的对象(大多数将用于任何单元测试),因为它减轻了精神负担并使其更易于维护。
我会弄清楚是什么让您的系统更易于维护和易于理解并做到这一点。
【讨论】:
【参考方案5】:我建议不要使用简单系统以外的简单方法,通常我认为您最好为每个聚合创建自定义存储库并在其中尽可能多地封装合适的逻辑。
所以我的方法是为每个需要它的聚合创建一个存储库,例如 CustomerRepository。这将有一个 Add(保存)方法,如果适用于该聚合,还有一个 Remove(删除)方法。它还会有任何其他适用的自定义方法,包括查询 (GetActive),也许其中一些查询可以接受规范。
这听起来需要付出很多努力,但除了自定义查询之外,大多数代码至少在使用现代 ORM 时非常容易实现,因此我使用继承 (ReadWriteRepositoryBase where T: IAggregateRoot) 和/或组合(调用 RepositoryHelper 类)。基类可能具有适用于所有情况的方法,例如 GetById。
希望这会有所帮助。
【讨论】:
以上是关于数据访问层应该如何构建?的主要内容,如果未能解决你的问题,请参考以下文章
我应该对数据访问层进行单元测试吗?这是一个好习惯以及如何去做?