【问题标题】:implementing Database-functionality objects in an N-Tier architecture?在 N 层架构中实现数据库功能对象?
【发布时间】:2011-03-30 16:50:22
【问题描述】:

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

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

我们的层次结构如下所示: Common > 模型 > DAL > BAL

这里有更多关于层的详细信息。请记住,我继承了这个: Common 负责应用程序中许多地方使用的所有“实用”功能,例如访问配置设置、解析字符串、非业务相关功能。

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

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

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

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

是否有其他人处理过以与数据库交互为目的的业务对象,您如何将其置于 N 层架构中?

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

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

【问题讨论】:

  • * 只是名义上的接口,因为我真的想序列化这些对象,所以我不得不把它变成一个抽象类。
  • 我对你的层次结构感到困惑。模型和 BAL 负责什么?我用 BAL 代表业务访问层,但后来模型似乎不合适。在我看到的代码中,模型通常是最抽象的,位于(使用)之上或者是业务层的一部分......另外,如果模型比 DAL 更基础,那么 DAL 使用类有什么问题从和调用模型上的方法?
  • 哦,如果你不知道,你不需要在cmets中添加信息,你可以简单地编辑你的问题......

标签: c# database design-patterns


【解决方案1】:

也许不是您问题的真正答案,但仍然需要考虑。

我不同意您在组件层次结构中放置数据访问的位置。我不会把它放在两个功能域层之间。甚至没有“高于”单个域模型类。数据访问或持久性不是任何域类的问题。这应该只是可以对他们做的事情,而不是他们做的事情。

尽管我开始编写 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。

在下面的图片中,我尝试将其可视化,但我不太擅长可视化,并且总是对那些讨厌的横切机有问题。这就是为什么从领域层到横切关注点没有调用箭头的原因,尽管领域层当然应该能够访问例如“记录器”接口。也许我正在努力区分通用组件和横切组件,并且可以提出一个论点将它们放在一起,并将它们可视化为“实用程序”层/组件中的单独块。

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

【讨论】:

    【解决方案2】:

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

    是否有其他人处理过以与数据库交互为目的的业务对象,您如何将其置于 N 层架构中?

    您的项目的结构方式听起来好像每一层的逻辑职责是:

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

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

    【讨论】:

    • 不错的努力,但这里有一个建议:SO 中的答案往往简短而中肯。尝试缩短它或将其拆分为更多段落并添加标题。
    • INotify 在功能上是一个接口,但是你不能序列化一个接口,所以我把它做成了一个抽象类。它限制了灵活性(如果它是一个接口,我可以从其他类继承并仍然实现 INotify)。
    • 是的,BAL == BLL。就用我继承的名字。
    【解决方案3】:

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

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

    【讨论】:

    • 消息进入 MSMQ 并由作为服务运行的代码处理,因此在 Web 应用程序中创建 MessageProcessing 类没有帮助。但是你的回答给了我一个想法。
    【解决方案4】:

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

    恕我直言,人们过度使用图层。大多数应用程序不需要很多层。我当前的客户的所有应用程序都有一个像你这样的架构。问题是只有数据访问层和表示层有逻辑,其他所有层都只是从较低层获取数据,对其进行转换,然后将其发送到上面的层。

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

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

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

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

    【讨论】:

      【解决方案5】:

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

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

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

      也就是说,维护转换代码(从层到层)也不是很有趣,但通常工作量更少。

      一种介于两者之间的解决方案可能是使用接口/角色:为每一层定义一个对象应该扮演的接口/角色,并使用该接口传递给该层。一个(共享的)类应该实现一个角色(或其中的许多)。这将提供一个更松耦合的系统。

      我从this neat lecture about DCI (Data, Collaborations, and Interactions)学到了很多

      【讨论】:

        【解决方案6】:

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

        我将这篇文章交叉发布给程序员,我认为这类问题可能真正属于这里,并且得到了一些有用的想法。如果你有兴趣,线程在这里: Programmers thread on this issue。不可否认,我在发帖时根据自己的研究添加了依赖注入的“提示”,所以问题可能更清楚了。

        这是一个伟大而乐于助人的社区,我很自豪能够参与其中。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-09-12
          • 1970-01-01
          • 1970-01-01
          • 2011-07-06
          • 1970-01-01
          • 1970-01-01
          • 2012-03-04
          相关资源
          最近更新 更多