由于各种原因,我们正在编写一个新的业务对象/数据存储库。该层的要求之一是分离业务规则的逻辑和实际的数据存储层。
可以有多个数据存储层实现对同一对象的访问 - 例如,实现大多数对象的主“数据库”数据存储源,以及实现User对象的另一个“ldap”源。在这种情况下,用户可以选择来自LDAP源,可能具有稍微不同的功能(例如,不可能保存/更新User对象),但是否则应用程序以相同的方式使用它。另一种数据存储类型可能是Web服务或外部数据库。
我们正在考虑实施这两种主要方式,而我和同事在基本层面上是不正确的。我想就哪一个最好用的建议。我会尽量保持对每个人的描述尽可能保持中立,因为我在这里寻找一些客观的观点。
业务对象是基类,数据存储对象继承业务对象。客户端代码处理数据存储对象。
在这种情况下,每个数据存储对象都会继承常见的业务规则,并且它是客户端代码直接使用的数据存储对象。
这暗示客户端代码确定要为给定对象使用哪种数据存储方法,因为它必须显式地向该类型的对象声明实例。客户端代码需要明确知道它正在使用的每种数据存储类型的连接信息。
如果数据存储层为给定对象实现不同的功能,则客户端代码在编译时明确地知道它,因为该对象看起来不同。如果更改了数据存储方法,则必须更新客户端代码。
业务对象封装数据存储对象。
在这种情况下,业务对象由客户端应用程序直接使用。客户端应用程序将基本连接信息传递给业务层。关于给定对象使用哪种数据存储方法的决定是由业务对象代码做出的。连接信息将是从配置文件中获取的一大块数据(客户端应用程序并不真正知道/关心它的详细信息),它可以是数据库的单个连接字符串,也可以是各种数据存储类型的多个连接字符串。还可以从另一个地点读取附加数据存储连接类型 - 例如,数据库中的配置表,其指定各种web服务的URL。
这样做的好处是,如果将新数据存储方法添加到现有对象,可以在运行时设置配置设置以确定使用哪种方法,并且它对客户端应用程序完全透明。如果给定对象的数据存储方法发生更改,则无需修改客户端应用程序。
业务对象是基类,数据源对象是从业务对象继承的。客户端代码主要处理基类。
这与第一种方法类似,但客户端代码声明了基本业务对象类型的变量,而业务对象上的Load()/ Create()/ etc静态方法返回了相应的数据源类型对象。
此解决方案的体系结构与第一种方法类似,但主要区别在于决定哪个数据存储对象用于给定的业务对象是由业务层而不是客户端代码。
我知道现有的ORM库已经提供了一些这样的功能,但是请暂时折扣(有可能用这些ORM库之一实现数据存储层) - 另请注意我是故意的没有告诉你这里使用的语言,除了它是强类型的。
我正在寻找一些关于哪种方法更好用(或随意提出其他建议)的一般建议,以及为什么。
答案 0 :(得分:11)
我可能会提出另一种选择,可能会有更好的解耦:业务对象使用数据对象,数据对象实现存储对象。这应该将业务规则保留在业务对象中,但不依赖于存储源或格式,同时允许数据对象支持所需的任何操作,包括动态更改存储对象(例如,用于在线/离线操作)
这属于上面的第二类(业务对象封装数据存储对象),但更清楚地将数据语义与存储机制分开
答案 1 :(得分:1)
您还可以拥有一个外观,以防止您的客户直接致电业务。此外,它还为您的业务创建了共同的入口点。
如上所述,除了您的DTO和Facade之外,您的业务不应该接触到任何其他内容。
是。您的客户可以处理DTO。这是通过您的应用程序传递数据的理想方式。
答案 2 :(得分: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;
}
}
答案 3 :(得分:0)
客户端不应直接处理存储对象。他们可以直接处理DTO,但是任何没有包含在业务对象中的存储逻辑的对象都不应该由客户端直接调用。
答案 4 :(得分:0)
查看Rocky Lhotka的CSLA.net。
答案 5 :(得分:0)
答案 6 :(得分:0)
好吧,我在这里,同事格雷格提到了。
格雷格描述了我们一直在考虑的替代方案。我只想在情境描述中添加一些额外的考虑因素。
客户端代码可能不了解存储业务对象的数据存储,但是如果只有一个数据存储,或者存在多个同一业务对象类型的数据存储(存储在本地数据库和外部的用户),则可能LDAP)但客户端不会创建这些业务对象。在系统分析方面,这意味着不应存在两个相同类型对象的数据存储可能影响用例流的用例。
一旦需要区分在不同数据存储中创建的对象,客户端组件就必须了解其Universe中的多种数据存储,并且它将不可避免地成为决定当前使用哪种数据存储的原因。对象创建(并且,我认为,从数据存储加载对象)。业务层可以假装它正在做出这个决定,但决策算法将基于来自客户端组件的信息的类型和内容,使客户有效地负责决策。
这个职责可以通过多种方式实现:它可以是每个数据存储的特定类型的连接对象;它可以是segregared方法来调用以创建新的BO实例等。
此致
迈克尔