在N层体系结构中实现数据库功能对象?

时间:2011-03-30 16:50:22

标签: c# database design-patterns

我正在为我们的网站添加功能,该功能使用MSMQ异步执行长时间运行的进程。但是,执行此ansynch意味着我们需要在完成请求时通知用户。使用命令模式,我创建了一个名为INotify的接口*并将其组成到消息类中,因此消息处理类可以简单地在消息的INotify对象上调用GiveNotice()。第一个实现,EmailNotify,比预期更困难,因为我惊讶地发现MailMessage不可序列化,但它已经开始了。

现在我正在开发一个新的具体通知程序DBNotify,它将调用某种类型的SP并更新主事务数据库中的状态。我惹上了,我想重用我们已经创建的DAL架构,但是INotify是Model项目的成员,它比DAL更基础。

我们的层次结构如下所示: 常见>型号> DAL> BAL

以下是关于等级的更多细节。请记住,我从以下方面继承了: Common负责应用程序中许多地方使用的所有“实用程序”功能,例如访问配置设置,解析字符串,非业务相关功能。

模型是业务对象,有些人称之为数据传输对象,是getter和setter的集合。我在这一层添加了一些“智能”,但只有该对象内部的业务规则,例如“一个项目的名称必须以字母数字字符开头。”

DAL是数据访问层,理论上,这里发生的所有事情都是模型对象被移入和移出数据库。

BAL是业务层;理论上,强制执行管理对象交互的业务规则(即“表单必须至少有两个项目。”)。

因此,INotify接口被定义为抽象,以允许通知方法独立变化(即电子邮件,TXT,推特等)。它是系统的基础,所以我在Model层创建了它,它独立于DAL层。但是,我正在创建一个新的INotify具体实现,其通知方法是在数据库中调用SP。

是否还有其他人处理了一个业务对象,其目的是与数据库进行交互,以及如何将其置于您的N层架构中?

在你告诉我使用Linq to Sql之前,非常感谢。这不是一个技术问题(我该怎么做),这是一个设计问题(我该怎么做)。

我认为有一个StackExchange网站更专注于这些与语言无关的设计问题,所以我打算将其复制到那里。

6 个答案:

答案 0 :(得分:3)

也许不是你问题的真正答案,但是要考虑一些事情。

我与您在组件层次结构中放置数据访问权的地方不一致。我不会把它放在两个功能域层之间。甚至不在单个域模型类“之上”。数据访问或持久性不是任何域类的关注点。它应该只是可以对他们做的事情,而不是他们做的事情。

即使我开始编写TClient.SaveTClient.Load这样的内容,我现在得出的结论是,不是客户决定需要保存它,而是用户交互决定何时需要域实例的数据是必需的,因此应该加载,并且当客户的数据应该被持久化时(如果有的话)。因此,我现在支持编码(在GUI中,更具体地说是GUI中的控制器),如DataStore.Load(ClientInstance)DataStore.Save(ClientInstance)。然后由数据访问层决定如何执行此操作。它可以在C#中使用反射,或者在Delphi中使用新的RTTI迭代所有客户端的属性,以便将它们发送到数据库。

虽然分层是一个非常好的概念,可以通过简单地坚持“你可以调用而不是向上”来分离问题,并防止你把东西放在一起,但在解决诸如日志记录,异常之类的问题时,它并没有多大帮助处理,通知以及其他所有组件/层需要的其他有趣的横切关注点。

此外,Common层,因为它是一个实用程序层,所以其他所有层都应该可以访问它。

将它全部放在一张图片中(我在这里保留了您在简单域类,模型和跨类业务规则,BAL之间的区别):

+---+   +-------------+
| C |<--| Data Access |<--------------------------+
| o |   +-------------+                           |
| m |         |                                   |
| m |         |                                   |
| o |         v                                   |
| n |   +-------------+   +----------------+   +-----+
|   |<--| Model       +<--| Cross class    |<--| GUI |
|   |   +-------------+   | business rules |   |     |
|   |                     |                |   |     |
|   |<--------------------|                |   |     |
|   |                     +----------------+   |     |
|   |                                          |     |
|   |<-----------------------------------------|     |
+---+                                          +-----+

调用数据库的INotify实现当前位于模型中,如上图所示,它不会调用数据访问层本身,只是由数据访问层调用,或者更确切地询问它。

问题实际上是INotify是否应该在“模型”中,是域层的一部分,或者它是否应该是一个公共接口,并且应该有一个可以从域访问的单独的“通知”层/组件和GUI。这个新组件不仅可以关注通知,还可以关注许多其他交叉问题,例如日志记录。它至少可以以某种回调的方式访问公共(当然)和数据访问组件以及GUI。

在下面的图片中,我试图想象这一点,但我不太擅长可视化,并且总是遇到那些讨厌的十字刀。这就是为什么没有从域层到交叉问题的调用箭头,尽管领域层当然应该能够访问例如“Logger”接口。也许我正在努力区分常见组件和交叉组件,并且可以将这些组合在一起,并将它们视为“实用程序”层/组件中的单独块。

        +--------------------------------------------+
  +-----| Cross cutting concerns                     |
  |     +--------------------------------------------+
  v           v^                                    ^
+---+   +-------------+                             |
| C |<--| Data Access |<--------------------------+ |
| o |   +-------------+                           | |
| m |         |                                   | |
| m |         |                                   | |
| o |         v                                   | v
| n |   +-------------+   +----------------+   +-----+
|   |<--| Model       +<--| Cross class    |<--| GUI |
|   |   +-------------+   | business rules |   |     |
|   |                     |                |   |     |
|   |<--------------------|                |   |     |
|   |                     +----------------+   |     |
|   |                                          |     |
|   |<-----------------------------------------|     |
+---+                                          +-----+

答案 1 :(得分:2)

仅仅因为你的INotify接口在你的模型层中并不意味着所有具体的实现都需要在那里。它应该是一个接口 - 接口的目的是实现抽象 - 而不是基类 - 基类的目的是实现共享功能。因此,无论您在任何图层中拥有此类型的属性或参数,都应将其声明为INotify。在你的BAL(你的意思是BLL,业务逻辑层?)中,你将决定用于那些INotify实例的具体类型。根据通知的复杂程度,您可以在BLL中定义具体实现,并让它在DAL中使用辅助类来实际执行对您的sproc的调用,或者您可以直接在DAL中将其定义为类,因为它与数据库交互;这真的是一个基于班级负责程度的判断!无论哪种方式,它都应该可以在顶层访问。

  

是否还有其他人处理了一个业务对象,其目的是与数据库进行交互,以及如何将其置于您的N层架构中?

您的项目结构的方式,听起来每层的逻辑责任是:

<强>常见: 共享实用程序方法,不依赖于项目中的任何其他内容 的型号: 定义系统中实体的结构,也称为DTO或数据传输对象,这意味着它们可以在层之间传输。他们所做的只是存储您的数据并执行基本验证 的 DAL: 负责从Model层创建类的实例,并根据存储在存储库(例如数据库)中的值设置属性。还负责跟踪对模型实体的更改,并将这些更改保存(保存)回存储库 的 BAL / BLL: 使用其他层中定义的类来实现有用的功能,并验证是否遵循业务要求。

您可以通过各种技术实现这一目标,即使使用相同的技术,您的具体实施也会因您的工作方式而异。像Linq2Sql或开箱即用的实体框架之类的东西会模糊你的模型和你的DAL之间的界限;他们想在同一个项目中定义两者。但是,实体框架更灵活,通过一些工作,您可以将实体模型的定义和最终负责DAL组件的“上下文”(使用实体框架术语)拆分为单独的项目。您可以编辑T4模板或在线查找将从实体模型生成实体和上下文类定义的模板,以支持存储库和工作单元设计模式,您不会直接引用实体上下文,而是实现IRepository接口,这使您的代码更易于测试。我从来没有亲自与NHibernate合作,但我的理解是它能够做同样的事情(并且可以说可能目前做得更好)。

答案 2 :(得分:1)

如果你的模型类是你的DTO(有些人可能称之为数据结构或数据类型),它们应该(可能)“覆盖”你的其他层,并为所有这些层所知。

根据您的说法,您可能拥有一个位于BAL中的MessageProcessing类,并接收来自BAL或DAL其他部分的消息,然后通知正在收听的任何人(UI或其他感兴趣的BAL成员)

答案 3 :(得分:1)

如果项目是POCO,您可以在项目中使用您的数据实体。否则我会像你一样创建单独的模型。但是请将它们保存在单独的程序集中(而不是在DataAccess项目中)

imho人们过度使用图层。大多数应用程序不需要很多层。我现在的客户有一个类似你所有应用程序的架构。问题是只有数据访问层和表示层中有逻辑,所有其他层只从下层获取数据,对其进行转换,并将其发送到上面的层。

我做的第一件事是告诉他们废弃所有图层,而是使用这样的东西(需要一个IoC容器):

  • 核心(通过orm包含业务规则和数据访问)
  • 规范(分离的接口模式。包含服务接口和模型)
  • 用户界面(可能是网络服务,winforms,webapp)

适用于大多数应用程序。如果您发现 Core 增长并且变得太大,则可以将其拆分而不会影响任何用户界面。

您已经在使用ORM了,是否考虑过使用验证块(FluentValidation或DataAnnotations)进行验证?可以轻松验证所有图层中的模型。

答案 4 :(得分:0)

许多层中使用的类让我很担心。

特别是当它们也与数据模型/基础/层绑定时。

只要这些类发生了变化,您就可能会在所有层中重新编码。换句话说,你错过了抽象的有用效果。

也就是说,维护转换代码(从一层到另一层)也不是很有趣,但总的来说工作量较少。

解决方案之间可能是接口/角色的使用:为每个层定义对象应该播放的接口/角色,并使用该接口传递给层。然后,(共享)类应该实现一个角色(或许多角色)。这将提供一个更松散耦合的系统。

我从this neat lecture about DCI (Data, Collaborations, and Interactions)

学到了很多东西

答案 5 :(得分:0)

感谢大家的意见,这里有几个想法,我计划实施的改进,虽然没有一个直接回答我问的问题。

我把它交给了程序员,在那里我认为这类问题可能真正属于,并得到了一些有用的想法。如果您有兴趣,该主题就在这里: Programmers thread on this issue。不可否认,当我在那里发布时,我根据自己的研究添加了依赖注入的“暗示”,所以问题可能更清楚了。

这是一个伟大而有用的社区,我很自豪能够参与其中。