在 N 层架构中实现数据库功能对象?

发布于 2024-10-28 07:35:26 字数 996 浏览 2 评论 0原文

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

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

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

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

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

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

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

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

是否有其他人处理过旨在与数据库交互的业务对象?您如何将其放置在 N 层架构中?

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

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

I'm adding functionality to our website which performs long-running processes asynchronously using MSMQ. Doing this ansynch, however means we need to notify users when their requests are completed. Using the command pattern, I created an interface* called INotify and composed that into the message class, so the message processing class can simply call GiveNotice() on the message's INotify object. The first implementation, EmailNotify, was more difficult than expected, as I was surprised to discover MailMessage isn't serializable, but got it going.

Now I'm working on a new concrete notifier, DBNotify, which will call a SP of some sort and update a status in the main transactional database. I'm tripped up in that I would like to reuse the DAL architecture we've already created, but INotify is a member of the Model project, which is more fundamental than the DAL.

Our hierarchy looks like this:
Common > Model > DAL > BAL

Here's more detail about the tiers. Bear in mind, I inherited this from :
Common is responsible for all "utility" functions which are used many places in the application, things like accessing configuration settings, parsing strings, non-business related functionality.

Model are business objects, what some folks call data transfer objects, collections of getters and setters. I've added some "smarts" at this layer, but only business rules internal to that object, such as "An item's name must begin with an alphanumeric character."

DAL is the data access layer, in theory, all that happens here is model objects are moved into and out of the database.

BAL is the Business layer; in theory, business rules that govern the interaction of objects are enforced (i.e. "A form must have at least two items.").

So the INotify interface is defined an abstraction to allow the method of notification to vary independently (i.e. email, TXT, twitter, etc). It's fundamental to the system, so I have created it at the Model tier, which is independent of the DAL tier. However, I am creating a new concrete implementation of INotify whose notification method is to call a SP in a database.

Has anyone else dealt with a business object whose purpose is to interact with a database, and how do you situate that in your N-tier architecture?

Before you tell me to use Linq to Sql, great thanks. This is not a technical question (how do I do this), it's a design question (how should I do this).

I think there is a StackExchange site more focused on these sorts of language-independant design questions, so I'm going to copy this there.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

江湖正好 2024-11-04 07:35:26

也许这并不是你问题的真正答案,但仍然值得思考。

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

尽管我开始编写诸如 TClient.SaveTClient.Load 之类的代码,但我现在得出的结论是,不是客户端决定需要保存它,但用户交互决定何时需要域实例的数据并因此应加载,以及何时应保留客户端的数据(如果有的话)。因此,我现在是诸如 DataStore.Load(ClientInstance)DataStore.Save(ClientInstance) 之类的编码(在 GUI 中,更具体地说是 GUI 中的控制器)的支持者。然后由数据访问层来决定如何做到这一点。它可以使用 C# 中的反射或 Delphi 中的新 RTTI 来迭代所有客户端的属性,以便将它们发送到数据库。

虽然分层是一个非常好的概念,可以通过简单地遵循“可以向下调用但不能向上调用”来分离关注点并防止您将东西放得到处都是,但在解决日志记录、异常处理、通知等问题时,它并没有多大帮助。以及所有其他组件/层都需要的其他有趣的横切关注点。

此外,公共层,因为它是一个实用层,应该真正可供所有其他层访问。

总而言之(我保留了简单域类、模型和跨类业务规则、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 |   |     |
|   |                     |                |   |     |
|   |<--------------------|                |   |     |
|   |                     +----------------+   |     |
|   |                                          |     |
|   |<-----------------------------------------|     |
+---+                                          +-----+

Maybe not really an answer to your question, but something to think about nonetheless.

I am at odds with where you put the data access in your component hierarchy. I would not put it between two functional domain layers. Not even "above" the single domain model classes. Data access, or persistency, is not a concern for any domain class. It should only be something that can be done to them, not something they do.

Even though I started out coding things like TClient.Save and TClient.Load I have now come to the conclusion that it is not the Client that decides it needs to be saved, but user interactions that dictate when a domain instance's data is needed and therefore should be loaded, and when an Client's data should be persisted, if at all. I am therefore now a proponent of coding (in the GUI, more specifically the controller's in the GUI) things like DataStore.Load(ClientInstance) and DataStore.Save(ClientInstance). It is then up to the Data Access layer to figure out how to do that. It could use reflection in C#, or the new RTTI in Delphi to iterate over all the Client's properties so it can send them to a database.

While layering is a very good concept to separate concerns and keep you from putting stuff all over the place by simply adhering to "you can call down but not up", it does not help as much when addressing things like logging, exception handling, notifications and all those other interesting cross cutting concerns that every other component/layer needs.

In addition, the Common layer, as it is a utility layer should really be accessible to all other layers.

To put it all in a picture (where I have kept the distinction you make between simple domain classes, your model, and cross class business rules, your BAL):

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

The INotify implementation that calls into the database is currently in the model, which in the picture above, does not call into the data access layer itself, it is only called, or rather interrogated, by the data access layer.

The question really is whether INotify should be in the "model", a part of the domain layer, or whether it should be a Common interface and there should be a separate "Notification" layer/component that is accessible from the domain and the GUI. This new component could not only concern itself with notifications, but many other cross cutting concerns, for example logging. It has access to the common (of course) and data access components and to the GUI in at least in some sort of call back fashion.

In the picture below I have tried to visualize this, but I am not much good at visualization and always have problems with those pesky cross cutters. Thats why there are no call-arrows from the domain layer to the cross cutting concerns, though of course the domain layer should be able to access for example a "Logger" interface. Maybe I am trying to hard to distinguish between the common and the cross cutting components and an argument could be made to put these together, and visualize them as separate blocks within a "Utility" layer/component.

        +--------------------------------------------+
  +-----| Cross cutting concerns                     |
  |     +--------------------------------------------+
  v           v^                                    ^
+---+   +-------------+                             |
| C |<--| Data Access |<--------------------------+ |
| o |   +-------------+                           | |
| m |         |                                   | |
| m |         |                                   | |
| o |         v                                   | v
| n |   +-------------+   +----------------+   +-----+
|   |<--| Model       +<--| Cross class    |<--| GUI |
|   |   +-------------+   | business rules |   |     |
|   |                     |                |   |     |
|   |<--------------------|                |   |     |
|   |                     +----------------+   |     |
|   |                                          |     |
|   |<-----------------------------------------|     |
+---+                                          +-----+
塔塔猫 2024-11-04 07:35:26

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

是否有其他人处理过旨在与数据库交互的业务对象?您如何将其放置在 N 层架构中?

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

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

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

Just because your INotify interface is in your Model layer doesn't mean all the concrete implementations need to be there. It SHOULD be an interface--the purpose of an interface is to achieve abstraction--not a base class--the purpose of a base class is to achieve shared functionality. So anywhere you have a property or parameter of this type throughout any of your layers, it should be declared as INotify. In your BAL (did you mean BLL, Business Logic Layer?) you would decide the concrete type to use for those instances of INotify. Depending on how complex your notifications are, you could define your concrete implementation in your BLL and have it use a helper class in the DAL to actually perform the call to your sproc, or you could define it as a class directly in your DAL since it interacts with the database; that's really a judgement call based on how much the class is responsible for; either way it should be accessible in the top layer.

Has anyone else dealt with a business object whose purpose is to interact with a database, and how do you situate that in your N-tier architecture?

The way your projects are structured, it sounds like the logical responsibilities of each layer would be:

Common:
Shared utility methods with no dependencies on anything else in the project
Model:
Defines the structure of the entities in the system, also called DTO's or Data Transfer Objects, which means they can be transferred between layers. All they do is store your data and perform basic validation.
DAL:
Responsible for creating instances of classes from your Model layer and setting the properties based on values stored in a repository, such as a database. Also responsible for tracking changes to Model entities, and saving (persisting) those changes back to the repository.
BAL/BLL:
Uses the classes defined in the other layers to achieve something useful, and validates that the business requirements are followed.

You can achieve this with a variety of technologies and even using the same technology your exact implementation will vary depending on how you work. Something like Linq2Sql, or Entity Framework out of the box would blur the lines between your Model and your DAL; they want to define both in the same project. However, Entity Framework is more flexible and with some work you can split the definition of the entity model and the "context" (to use the Entity Framework term), which is ultimately responsible for the DAL component, into separate projects. You can edit the T4 templates or find ones online that will generate the entity and context class definitions from the entity model to support the Repository and Unit of Work design patterns, where you never reference an entity context directly but instead have it implement an IRepository interface, which makes your code more testable. I've never personally worked with NHibernate, but my understanding is that it's capable of doing the same thing (and arguably may currently do a better job).

噩梦成真你也成魔 2024-11-04 07:35:26

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

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

If your Model classes are your DTOs (what some might call data structures or data types), they should (probably) lay "across" your other layers and be known to all of them.

Based on what you say, you might have a MessageProcessing class that sits in the BAL and receives messages from other parts of the BAL or the DAL, then notifies anyone who is listening (the UI or other interested members of the BAL).

蹲在坟头点根烟 2024-11-04 07:35:26

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

恕我直言,人们过度使用层。大多数应用程序不需要很多层。我当前的客户的所有应用程序都采用了与您类似的架构。问题是只有数据访问层和表示层有逻辑,所有其他层只是从下层获取数据,转换它,然后将其发送到上层。

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

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

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

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

You could use your data entities in your project if they are POCOs. Otherwise I would create separate models as you have done. But do keep them in a separate assembly (not in the DataAccess project)

imho people overuse layers. Most applications do not need a lot of layers. My current client had a architecture like yours for all their applications. The problem was that only the data access layer and the presentation layer had logic in them, all other layers just took data from the lower layer, transformed it, and sent it to the layer above.

The first thing I did was to tell them to scrap all layers and instead use something like this (requires a IoC container):

  • Core (Contains business rules and dataaccess through an orm)
  • Specification (Seperated interface pattern. Contains service interfaces and models)
  • User interface (might be a webservice, winforms, webapp)

That works for most application. If you find that Core grows and becomes too large too handle you can split it up without affecting any of the user interfaces.

You are already using an ORM and have you thought about using a validation block (FluentValidation or DataAnnotations) for validation? Makes it easy to validate your models in all layers.

浪菊怪哟 2024-11-04 07:35:26

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

特别是当它们也与数据模型/基础/层相关联时。

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

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

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

我从这个关于 DCI(数据、协作)的简洁讲座中学到了很多东西和交互)

Classes that are used in many layers get me worried.

Especially when they are also tied to the data- model/base/layer.

As soon as there is a change in these classes you could run into re-coding in all layers. In other words you are missing the helpful effect of abstraction.

That said, maintaining transformation code (from layer to layer) is not much fun either but in general less work.

An in between solution might be the use of interfaces/roles: Define for each layer the interface/role that an object should play and use that interface to be passed to the layer. A (shared) class should then implement a role (or many of them). This will provide a more loosely coupled system.

I learned a lot from this neat lecture about DCI (Data, Collaborations, and Interactions)

椒妓 2024-11-04 07:35:26

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

我将其交叉发布给程序员,我认为此类问题可能真正属于该问题,并得到了一些有用的想法。如果您有兴趣,该主题在这里:
程序员线程关于这个问题。诚然,我在发帖时根据自己的研究添加了依赖注入的“提示”,所以问题可能更清楚了。

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

Thank you everyone for your input, there are several ideas here for improvements I plan to implement, although none directly answer the question I was asking.

I cross-posted this over to Programmers, where I think this sort of question may truly belong, and got some helpful ideas. If you're interested, the thread is here:
Programmers thread on this issue. Admittedly, I added the "hint" of dependency injection based on my own research when I posted there, so the problem may have been clearer.

This is a great and helpful community, which I am so proud to participate in.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文