2008-09-23 41 views
6

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

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

我们正在考虑实施这个方法有两种主要方式,而我和一位同事在基本层面上的意见不一致,这是正确的。我想就一个最适合使用的建议提出建议。我会尽力保持各个尽可能中性的我的描述,因为我在寻找一些客观的观点在这里。

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

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

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

    如果数据存储层为给定对象实现了不同的功能,客户端代码在编译时显式知道它,因为对象看起来不同。如果数据存储方法更改,则必须更新客户端代码。

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

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

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

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

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

    该解决方案的体系结构与第一种方法类似,但主要区别在于决定哪个数据存储对象用于给定业务对象是由业务层制定的,而不是客户机代码。

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

我在这里寻找一些一般性的建议关于哪种方法更好地使用(或随时提出别的东西),以及为什么。

回答

10

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

这属于上述第二类(业务对象封装数据存储对象),但更清楚地将数据语义从存储机制中分离出来

0

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

1

你也可以有一个门面,从你的客户保持直接调用业务。它也为您的业务创造了共同的入口点。

至于说,你的业务不应该暴露在任何东西,但你的DTO和门面。

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

0

讨论好办法,我在这里的同事格雷格提及。

格雷格描述了我们一直非常精确地考虑的替代方案。我只是想添加一些额外的考虑情况描述。

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

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

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

问候,

迈克尔

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