业务逻辑层和数据访问层:循环依赖

24

我有一个小的架构问题。在我的项目中,我有一个业务逻辑层(BLL),其中包含所有的业务规则、模型和接口的面向对象API。每个对象都有像getById这样的静态方法,返回该对象的实例。每个对象还有像save和delete这样的方法。这是非常直观的面向对象代码。

现在我有一个数据访问层(DAL),包含在一个单独的命名空间中,对于每个BLL对象,我都有一个DataClass或“Repository”,执行getById和save命令。因此,在某种程度上,BLL保存和getById方法是DataClass方法的薄层封装。

public static NewsItem GetByID(int id)
{
       return DataFactory.GetNewsItemRepository().GetNewsItemById(id);
}
为了让DataClasses返回BLL对象,它们需要知道BLL。所以现在我们有:
GUI ---> BLL <---->DAL
DataFactory仅返回实现接口的对象,因此我可以隐藏实现细节,例如“OracleNewsItemRepository”。
但是自从我开始面向对象编程以来一直困扰着我的事情是,我的当前解决方案中,BLL和DAL都需要互相知道。这是循环依赖关系,并且最好避免循环依赖关系。而且我只想公开接口(和我的DataFactory),而不是我的类。这可以通过将DAL层放在单独的程序集中来完成。这是有意义的。然而,Visual Studio不允许两个程序集彼此引用。关于此的另一个问题:C#内部访问修饰符 不知何故,我觉得整个数据访问模式都错了。感觉像我混淆了ActiveRecord模式和其他东西,比如DataMappers。我在Martin Fowler的网站上花了很多时间,但那些模式描述得非常通用并且通过一个非常抽象的UML图表说明。
它们没有解决我的问题。也许我有点神经质,也许没有所谓的“完美的数据访问模式”。我现在做的事情似乎不是非常错误的。但是我现在的做法似乎有点问题……
有什么想法吗?
8个回答

13

我认为你的数据访问模式很好。你没有把BLL与OracleDAL耦合,而是与DAL接口进行了耦合。一定程度的耦合是必需的,否则你就无法完成任何事情。

我假设你的DataFactory和INewsItemRepository类存在于DAL层之外。以下是我的解决方案组织的示例。由于我不使用ActiveRecord,所以这可能不完全适合你。

核心(项目)
  领域
    业务实体
  数据
    存储库接口
    **你的DataFactory**
Oracle数据(项目) 数据 Oracle存储库实现
SQL数据(项目) 数据 SQL存储库实现
用户界面(项目)

希望这能有所帮助。


1
可以与 DI 结合使用。我的 BLL 现在只知道接口。 - user1144
Craig,我猜这意味着你没有使用接口将你的领域与用户界面解耦? - Bondolin

12

我的观点是:

数据访问层(DAL)应该使用基本的CLR对象(POCOs)来进行操作,比如使用以下操作:SaveNewsItem ( NewsItemDAO newsItemDAO )。这些POCOs就是你的数据访问对象(DAOs)。

业务逻辑层应该包含将数据访问对象(DAO)转换为富业务对象的逻辑,这个富业务对象可能只是DAO加上一些操作以及任何装饰/增强。

DAL不应该知道任何关于业务逻辑层的信息。理论上,它应该能够从任何客户端调用。例如,如果您想将DAL与应用程序分开并将其部署为通过WCF公开自身的单独服务,该怎么办?

正如提到的那样,DAL操作(例如SaveNewsItem)应该通过接口通过BO进行访问,可能通过依赖注入/IoC实现。


是的,我同意 - 最好DAL不要知道BLL的存在。相反,DAL应该有某种抽象层,以便BLL可以从“原始数据”(例如DAOs、XML或键/值对地图)构造其实体。这些工厂最好与域逻辑分开。 - Tom Carter

5
您可以使用接口/依赖注入来解决您的问题。
您的业务层(BL)包含数据访问(DA)接口,这些接口需要(可能不止一个)DAL实现。DAL项目具有对BL的项目引用,以便它们可以生成业务对象(BO)并实现DA接口。
您的BO可以调用DataFactory,通过依赖注入或反射实例化DA对象。
我在我们公司的许多应用程序中(基于Web和智能客户端),都使用了这种模式,效果非常好。

3

那是一篇很棒的文章。我不确定项目结构是从哪里来的,但我已经读了很多次埃里克·埃文斯的《领域驱动设计》了,所以... - Craig Wilson

3

虽然现在有点过时,但也许你应该考虑将pocos/interfaces放在另一个程序集中。

Project.Data references Project.Entities
Project.BL references Project.Entities and Project.Data
Project.UI references Project.Entities and Project.BL
在这里不存在循环引用。

2
只是为了明确,我认为商业模型和商业逻辑是两个不同的层。您的商业模型是您的POCOs(Plain old CLR对象)。您的业务逻辑层将负责使用业务模型和与DAL的接口执行验证、事务等操作,可以通过Spring、Castle或自己构建的IoC容器进行连接。
在您的DAL中实现零依赖于商业模型的良好方法是使用已构建的对象关系映射框架(ORM),如NHibernate(我最喜欢的ORM框架之一) 。

叛徒。每个人都知道EF比NHiberanate好上百万倍。[躲避攻击] - Jake
1
除了使用 EF,你无法拥有一个不依赖于 EF 的商业模型。 - Trent
1
业务层负责事务吗?不是,它们在数据访问层中进行。唯一的例外是分布式事务。 - Pascal

1
我会从你的BLL(领域模型)中删除任何Get()和Save()方法..这是我会做的:
GUI将请求Repository通过id获取领域对象。一旦GUI获得了领域对象,您可以导航它以达到其他对象。这样,领域层不需要知道任何关于repositories的信息。
在存储库内部,您可以返回懒惰加载或完全加载对象对象图的领域对象。这取决于您的需求。
这里有一篇关于同一主题的好文章... 重新构建对象 阅读Deyan Petrov在如何使用动态代理方面的评论。

0

DAL 必须是抽象的,因此它必须仅包含与后端数据库进行交互的普通 ADO.NET 对象,例如连接 DataAdapter、DataReader 等等。
有了这些,您可以在业务层中引用 DAL,在实体方面稍微抽象一下就可以解决所有问题。例如,如果您有一个客户类,可以创建一个实现与 DAL 交互的基本操作(如保存、更新和检索数据)的抽象化客户类,并在另一个继承该抽象类的类中重写基类方法实现以满足您的业务验证等需求。


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