7

由于各种原因,我们正在编写一个新的业务对象/数据存储库。该层的要求之一是将业务规则的逻辑与实际的数据存储层分开。

可以有多个数据存储层来实现对同一个对象的访问——例如,实现大多数对象的主“数据库”数据存储源,以及实现用户对象的另一个“ldap”源。在这种情况下,用户可以选择来自 LDAP 源,可能具有稍微不同的功能(例如,无法保存/更新用户对象),但应用程序以相同的方式使用它。另一种数据存储类型可能是 Web 服务或外部数据库。

我们正在考虑通过两种主要方式来实现这一点,我和一位同事在基本层面上存在分歧,这是正确的。我想就哪一个最好用一些建议。我会尽量保持对每一个的描述尽可能中立,因为我在这里寻找一些客观的观点。

  • 业务对象是基类,数据存储对象继承业务对象。客户端代码处理数据存储对象。

    在这种情况下,每个数据存储对象都继承了通用的业务规则,而客户端代码直接使用的是数据存储对象。

    这意味着客户端代码确定对给定对象使用哪种数据存储方法,因为它必须显式声明该类型对象的实例。客户端代码需要明确知道它使用的每种数据存储类型的连接信息。

    如果数据存储层为给定对象实现不同的功能,客户端代码在编译时明确知道它,因为对象看起来不同。如果数据存储方式发生变化,客户端代码也必须更新。

  • 业务对象封装了数据存储对象。

    在这种情况下,业务对象直接由客户端应用程序使用。客户端应用程序将基本连接信息传递给业务层。由业务对象代码决定给定对象使用哪种数据存储方法。连接信息将是从配置文件中获取的一大块数据(客户端应用程序并不真正了解/关心它的细节),它可能是数据库的单个连接字符串,也可能是各种数据存储类型的多个连接字符串。其他数据存储连接类型也可以从另一个位置读取 - 例如,数据库中的配置表指定各种 Web 服务的 URL。

    这样做的好处是,如果将新的数据存储方法添加到现有对象,则可以在运行时设置配置设置以确定使用哪种方法,并且对客户端应用程序完全透明。如果给定对象的数据存储方法发生更改,则无需修改客户端应用程序。

  • 业务对象是基类,数据源对象继承自业务对象。客户端代码主要处理基类。

    这与第一种方法类似,但客户端代码声明了基本业务对象类型的变量,并且业务对象上的 Load()/Create()/etc 静态方法返回适当的数据源类型对象。

    此解决方案的架构与第一种方法类似,但主要区别在于为给定业务对象使用哪个数据存储对象由业务层而非客户端代码决定。

我知道已经有现有的 ORM 库提供了一些这样的功能,但现在请打折扣(数据存储层有可能是用这些 ORM 库之一实现的) - 另请注意,我故意不告诉你这里使用的是什么语言,除了它是强类型的。

我在这里寻找一些关于哪种方法更好使用(或随意提出其他建议)以及原因的一般建议。

4

7 回答 7

11

我是否可以建议另一种选择,可能更好地解耦:业务对象使用数据对象,而数据对象实现存储对象。这应该将业务规则保留在业务对象中,但不依赖于存储源或格式,同时允许数据对象支持所需的任何操作,包括动态更改存储对象(例如在线/离线操作)

这属于上面的第二类(业务对象封装了数据存储对象),但是将数据语义与存储机制分离得更清晰

于 2008-09-23T03:36:56.500 回答
1

你也可以有一个门面来阻止你的客户直接打电话给公司。它还为您的业务创建了共同的切入点。

如前所述,您的业务不应该暴露于您的 DTO 和 Facade 之外的任何东西。

是的。您的客户可以处理 DTO。这是通过应用程序传递数据的理想方式。

于 2008-09-23T03:50:27.013 回答
1

我通常最喜欢“业务对象封装数据对象/存储”。但是,简而言之,您可能会发现您的数据对象和业务对象的高度冗余可能看起来不值得。如果您选择 ORM 作为数据访问层 (DAL) 的基础,则尤其如此。但是,从长远来看,真正的回报是:应用程序生命周期。如图所示,“数据”来自一个或多个存储子系统(不限于 RDBMS)的情况并不少见,尤其是随着云计算的出现,以及分布式系统中的常见情况。例如,您可能有一些来自 Restful 服务的数据、来自 RDBMS 的另一个块或对象、另一个来自 XML 文件、LDAP 等的数据。有了这个认识,这意味着很好地封装来自业务的数据访问的重要性。还要注意通过 c-tors 和属性公开 (DI) 的依赖项。

也就是说,我一直在玩弄的一种方法是将架构的“肉”放在业务控制器中。将现代数据访问更多地视为一种资源而不是传统思维,控制器然后接受 URI 或其他形式的元数据,这些元数据可用于了解它必须为业务对象管理哪些数据资源。然后,业务对象本身并不封装数据访问;而是控制器。这使您的业务对象保持轻量和具体,并允许您的控制器提供优化、可组合性、事务环境等。请注意,您的控制器将“托管”您的业务对象集合,就像许多 ORM 的控制器一样。

此外,还要考虑业务规则管理。如果你仔细观察你的 UML(或者像我一样在你脑海中的模型 :D),你会注意到你的业务规则模型实际上是另一个模型,有时甚至是持久的(例如,如果你使用的是业务规则引擎) . 我会考虑让业务控制器也实际控制您的规则子系统,并让您的业务对象通过控制器引用规则。原因是,不可避免地,规则实现通常需要执行查找和交叉检查,以确定有效性。通常,它可能需要混合业务对象查找以及后端数据库查找。考虑检测重复的实体,例如,只有“新”实体是水合的。让您的规则由您的​​业务控制器管理,

在伪代码中:

using(MyConcreteBusinessContext ctx = new MyConcreteBusinessContext("datares://model1?DataSource=myserver;Catalog=mydatabase;Trusted_Connection=True ruleres://someruleresource?type=StaticRules&handler=My.Org.Business.Model.RuleManager")) {

User user = ctx.GetUserById("SZE543");
user.IsLogonActive = false;
ctx.Save();
}

//a business object
class User : BusinessBase {
  public User(BusinessContext ctx) : base(ctx) {}

  public bool Validate() {
    IValidator v = ctx.GetValidator(this);
    return v.Validate();
  }
}

// a validator
class UserValidator : BaseValidator, IValidator {
 User userInstance;
 public UserValidator(User user) {
  userInstance = user;
 }

 public bool Validate() {
   // actual validation code here
   return true;
 }
}
于 2010-05-22T00:03:05.003 回答
0

客户端不应该直接处理存储对象。它们可以直接处理 DTO,但任何具有任何存储逻辑但未包装在业务对象中的对象不应由客户端直接调用。

于 2008-09-23T03:44:28.230 回答
0

查看 Rocky Lhotka 的 CSLA.net。

于 2008-09-23T03:58:31.657 回答
0

里昂证券已经存在了很长时间。但是我喜欢 Eric Evans 书中讨论的方法 http://dddcommunity.org/

于 2008-09-23T05:09:38.337 回答
0

好吧,我在这里,同事格雷格提到。

Greg 非常准确地描述了我们一直在考虑的替代方案。我只是想在情况描述中添加一些额外的注意事项。

客户端代码可能不知道存储业务对象的数据存储,但在只有一个数据存储或同一业务对象类型有多个数据存储(用户存储在本地数据库和外部 LDAP 中)的情况下,这是可能的,但是客户端不创建这些业务对象。在系统分析方面,这意味着不应该存在相同类型的对象的两个数据存储的存在会影响用例流的用例。

一旦需要区分在不同数据存储中创建的对象,客户端组件就必须意识到其域中数据存储的多样性,并且它不可避免地要负责在对象创建时决定使用哪个数据存储(而且,我认为,从数据存储中加载对象)。业务层可以假装它正在做出这个决定,但决策的算法将基于来自客户端组件的信息的类型和内容,使客户端有效地对决策负责。

这种责任可以通过多种方式实现:它可以是每个数据存储的特​​定类型的连接对象;它可以是调用以创建新的 BO 实例等的隔离方法。

问候,

迈克尔

于 2008-09-23T13:50:52.250 回答