业务对象/数据库访问层的体系结构

时间:2020-03-06 14:34:44  来源:igfitidea点击:

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

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

我们正在通过两种主要方式来实现这一目标,而我和一位同事在正确的基本层面上意见分歧。我想要一些最佳使用建议。我会尽可能保持中立,因为我在这里要寻找一些客观的观点。

  • 业务对象是基类,数据存储对象继承业务对象。客户端代码处理数据存储对象。在这种情况下,每个数据存储对象都继承了通用的业务规则,而客户端代码直接使用的是数据存储对象。这暗示着客户端代码确定用于给定对象的数据存储方法,因为它必须显式声明该对象类型的实例。客户端代码需要明确地知道其使用的每种数据存储类型的连接信息。如果数据存储层为给定对象实现了不同的功能,则客户端代码会在编译时明确地知道该对象,因为该对象看起来有所不同。如果更改了数据存储方法,则必须更新客户端代码。
  • 业务对象封装了数据存储对象。在这种情况下,客户端应用程序直接使用业务对象。客户端应用程序将基本连接信息传递到业务层。业务对象代码决定给定对象使用哪种数据存储方法。连接信息将是从配置文件中获取的数据块(客户端应用程序并不真正知道/不在乎其详细信息),该数据可能是数据库的单个连接字符串,也可能是各种数据存储类型的多个连接字符串。还可以从另一个位置读取其他数据存储连接类型-例如,数据库中的配置表,该表指定了各种Web服务的URL。这样做的好处是,如果将新的数据存储方法添加到现有对象,则可以在运行时设置配置设置以确定使用哪种方法,并且对客户端应用程序完全透明。如果给定对象的数据存储方法发生更改,则无需修改客户端应用。
  • 业务对象是基类,数据源对象是从业务对象继承的。客户代码主要处理基类。这类似于第一种方法,但是客户端代码声明基本业务对象类型的变量,并且业务对象上的Load()/ Create()/ etc静态方法返回适当的数据源类型的对象。此解决方案的体系结构与第一种方法相似,但是主要区别在于,由业务层(而不是客户端代码)决定用于给定业务对象的数据存储对象。

我知道已经存在提供某些此功能的ORM库,但请暂时将其打折(有可能使用其中一个ORM库实现数据存储层)也请注意,我故意不告诉我们除了强类型输入外,此处还使用其他语言。

我在这里寻求一些一般性建议,以了解哪种方法更适合使用(或者随意提出其他建议)以及原因。

解决方案

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

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

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

我们还可以选择不让客户直接致电公司的门面。它还会为业务创建通用的入口点。

如前所述,除了DTO和Facade外,公司不应该接触其他任何东西。

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

查看Rocky Lhotka的CSLA.net。

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

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

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

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

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

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

问候,

麦可

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

也就是说,我一直在尝试的一种方法是将架构的"实质"放入业务控制器中。与传统思想相比,将现代数据访问更多地视为一种资源,然后,控制器以URI或者其他形式的元数据接受这些信息,这些信息可用于了解它必须为业务对象管理哪些数据资源。然后,业务对象本身不会封装数据访问权限;而不是控制器。这使业务对象轻巧,具体,并使控制器可以提供优化,可组合性,事务氛围等。请注意,然后,控制器将"托管"业务对象集合,就像许多ORM的控制器一样。

此外,还应考虑业务规则管理。如果我们对UML(或者像我一样:D的头脑中的模型)hard着眼睛,我们会注意到业务规则模型实际上是另一个模型,有时甚至是持久性模型(例如,如果我们使用的是业务规则引擎) 。我会考虑让业务控制器也实际控制规则子系统,并让业务对象通过控制器引用规则。原因是,不可避免地,规则实现通常需要执行查找和交叉检查才能确定有效性。通常,它可能既需要混合业务对象查找,也需要后端数据库查找。考虑检测重复的实体,例如,只有"新"实体被水合的情况。然后让规则由业务控制器来管理,然后我们就可以执行所需的大多数操作,而无需牺牲"域模型"中的简洁抽象。

用伪代码:

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;
 }
}