UML软件工程组织

 

 

使用 NUnit, NUnitAsp, Rhino Mocks 和 Fit 创建BasicSample.Tests
 
2007-11-16 来源: 网络转载
 

我估计你已经能猜到这个项目有什么用途。在文章的第一版本,只是随便讨论了单元测试。行业和个人经验都证明:在生产高质量产品(这些产品更能适应做改变)测试驱动开发是一个关键的因素。而且,使用测试驱动方法往往会生产更好的设计,不好的方面就是会创建许多完美的有效的技术文件。看测试发展中一天的介绍对初学者非常有好处,例如,阅读Kent Beck's的《Test-Driven Development: By Example》。检查示例应用程序的单元测试给人们提供了:应用程序是怎样构建的,有什么可用的性能。当看了单元测试后,我们将进一步研究它们测试的代码。

单元测试的性能

单元测试必须运行得非常快。如果单元测试组运行得太久,开发者就会停止运行它们,然后我们需要它们一直在运行。事实上,如果测试运行的时间超过了0.1秒,测试可能太慢了。现在,如果你在过去运行过单元测试,你知道任何单元测试(要求访问一个现场数据库)运行的时间会更久。有NUnit时,你可以把测试放在categories里,每次运行测试的不同组时就会更加容易,同时大部分时间就会用在排除测试(此测试与数据库连接)。但是至少,这些“慢”测试应该在Continuous Integration环境里每晚运行。这里有一个已经分类的单元测试的例子。


[TestFixture] [Category("Database Tests")] public class SomeTests { [Test] public void TestSomethingThatDependsOnDb() { ... } }

领域层单元测试

为了简单化,这个应用程序的领域层很简单,至少可以这样说。但是即使在最简单的领域层,在最小程度上,拥有每一个非异常途径(被领域层覆盖)。在命名空间BasicSample.Tests.Domain里有领域层测试。(一方面,CustomerTests.CanCreateCustomer测试每一个属性的getter/setter时,它是不是非常具有杀伤力?在这个过程中你抓住了几个琐碎的bug)。在阅读这篇文章时,你可能会注意到类型DomainObjectIdSetter.cs;下面将讨论建立这个类型的动机。
为了运行单元测试,打开NUnit,转到File/Open Project,打开BasicSample.Tests/bin/Debug/BasicSample.Tests.dll.为了阻止消耗时间的测试继续运行,转到NUnit的Categories t键,双击“数据库测试”和"Web Smoke Tests."此外,点击底部的“删除这些类型”。现在,当你运行单元测试时,只有域名逻辑测试将运行,而且不会被HTTP和数据库访问测试减速。对于这样一个小的应用程序,补充的顶端的“慢”测试是可以忽略的,但是要增加时间来运行更大的应用程序的单元测试。
为数据访问层使用Test Doubles 。

在进入模拟数据库层之前,要注意这里有一个命名法来描述模拟服务的不同类型。Dummies,fakes, stubs and mocks都用来描述模拟行为的不同类型。对这些区别的总看法引起了一些东西,这些东西将包含在Gerard Meszaros' 即将出版的XUnit Test Patterns里。Meszaros提供了“双重测试”,一般用来描述任何这些行为。Stubs和mocks就是例子代码里显示的这样两个双重测试。

除非你明确测试DAO类别,通常你不需要运行单元测试,这些单元测试依靠现场数据库。本质上,它们非常慢,而且不稳定。例如:如果数据改变了,测试就崩溃了。当测试域名逻辑时,如果数据库改变,单元测试就不会崩溃。但是主要的障碍就是域名对象它们自己可能依靠DAOs。使用抽象的工厂模式(在例子中存在(以后将讨论))和连接的DAO接口,我们能把DAO双重测试注入到域名对象中,以此来模拟与数据库通信。在CustomerTests.CanGetOrdersOrderedOnDateUsingStubbedDao中包括了一个例子。下面的片段,从单元测试创建了DAO stub,并且通过公共的安装员,把它注入到customer中。因为安装员仅仅希望执行IOrderDao接口,stub DAO就会简单地代替所有现场数据库行为。

Customer customer = new Customer("Acme Anvils"); customer.ID = "ACME"; customer.OrderDao = new OrderDaoStub();

编写stub DAO 的另一个选择,可以静态地大量生成“无需实现接口”的代码,而这些可以通过如Rhino Mocks 或NMock工具模拟DAO。无论哪个都是非常好的选择,但是Rhino Mocks 以一种强而有力的方式调用方法,而不是使用数字符,而NMock 就是使用数字符。这使得能检查它的使用编译时间,协助重命名属性和方法。演示示例CustomerTests.CanGetOrdersOrderedOnDateUsingMockedDao 告诉人们使用Rhino Mocks 3.0来创建模拟的IOrderDao 。尽管看起来建立一个模拟的对象比建立一个stub更复杂。补充的灵活性和很大程度上减少的“没有执行”代码是有力的好处。下面的代码,在类型MockOrderDaoFactory.cs中找到的,展示了IOrderDao是怎样和Rhino Mocks一起模拟的。本质上,它建立了一个“静态”模拟,或者一个stub,事实上,在变元中经过了什么并不重要:它总是返回同一个例子命令(由TestOrdersFactory创建)。但是和Rhino Mocks模拟并没有限制在dumb reflexes上,例如:这个可以像要求的一样有响应。
public IOrderDao CreateMockOrderDao() { MockRepository mocks = new MockRepository(); IOrderDao mockedOrderDao = mocks.CreateMock<IOrderDao>(); Expect.Call(mockedOrderDao.GetByExample(null)).IgnoreArguments() .Return(new TestOrdersFactory().CreateOrders()); mocks.Replay(mockedOrderDao); return mockedOrderDao; }

很不幸地,而且通常大都是不幸的,你正在维护一个legacy 代码,这个legacy code没有完美的"代码-接口"形式,双重测试注入允许存在完美的"代码-接口"。通常,这里在具体对象上有许多详细的依赖项,很难用test doubles代替数据访问对象,以此来模拟现场数据库。在这些情况中,你的选择要么在测试中,refactor legacy code来安装,要么使用一个对象模拟工具(例如:TypeMock)。有了TypeMock,就可能模拟密封的和单一的类型-没有这样一个工具却来实现一个感人的宴会。

Albeit很有力,除非非得需要,不然TypeMock就被放在旁边;过早地使用TypeMock使得不编译接口非常诱人。当操作遗留代码-时间和预算允许时,更正确的方法就是refactor the code,取得更大的灵活性。Michael Feathers的《有效地与遗留代码工作》是包含了很多关于如何在测试工作中重构遗留代码的好办法的书。

NHibernateDAO的单元测试

在这篇文章的前一版本,Nhibernate的ISession通过System.Runtime.Remoting.Messaging.CallContext单例保存和读取。尽管非常适合WinForms 和单元测试,对ASP.NET 来说,却是一个不好的方法,因为ISession 可能在载入时会丢失(这两篇文章给予了进一步的解释:为什么在ASP.NET应用程序里使用CallContext是一个坏的方法)。为了正确地与ASP.NET 应用程序连接,应该将ISession 储存在HttpContext.Current.Items里,但是这样做,当运行单元测试时,会强迫你模拟一个HTTP文本。它同样阻止将结构简单地传递到WinForms。一个更好的方法就是在它正确的时候,使用正确的repository。因此如果一个网页文本可用,然后使用HttpContext;然而,使用CallContext. 将在后面讨论这个结合方式的执行细节。(要感谢许多对这篇文章给予评论,从而引起了这个关注)。阅读与怎样管理ISession有关的

BasicSample.Tests/Data/CustomerDaoTests.cs这篇文章,因为单元测试HTTP-agnostic。一方面,你将在单元测试看到,除非你像在你的测试中改变数据,来服从于数据库,反转处理才是一个好的方法。

正如在示例中显示的,可能创建一个通用的DAO,这个一般的DAO为任何持久稳固的对象工作。(后面将详细讨论)这就引起了应该测试什么以及怎样测试它的争论。每一个具体的DAO都要全面测试吗?怎样维护测试数据?个人经验给出了这些建议:

  • 保证每个泛型DAO中的每一个方法都有一个单元测试,例如,如果你有10个实现了泛型Dao的Dao,只有他们其中的一个需要被完全测试。单元测试中的实现了泛型DAO其他9个Dao只会提供很少的附加值。
  • 确保每个从泛型Dao扩展的每个方法都有一个单元测试。例如,如果你有一个从泛型DAO继承的CustomerDao 类并且增加了一个方法。如GetActiveCustomers(),那么这个单元测试应该测试这个扩展方法。
  • 保证存在一个单元测试来完完全全测试每一个“专业” DAO。例如,DAO BasicSample.Data/HistoricalOrderSummaryDao.cs不是从一般DAO 继承来的,将被看成是一个专业DAO。因此,存在一个单元测试来测试每一个方法。
  • 在DAO单元测试被运行之前或者以后,都使用一个工具,例如NDbUnit,把测试数据库放到一个已知的状态。
  • 使用如NDbUnit的工具测试数据库在单元测试运行请后的状态。 永远记着,单元测试永远不要依赖另外一个单元测试!他们应该是独立并且可以独立运行。例如一个删除测试不应该依赖上一个插入测试的成功。注意TestFixtureSetUp和其他setup、teardown的方法,保证包含了这个能够独立运行的测试也能够正常运行。

为表现层使用Fit Test Doubles

当测试领域层时,用双重测试来模拟NHibernate 与数据库的通信。同样,有些时候,当测试表现层时,使用同样的方法很方便。假设你和一个极其投入的具有创造性的队伍正在研究ASP.NET 项目。这些具有创造性的人们,穿着黑色的套领毛衣,管理着应用程序的外表和感觉。但是当他们研究图像配置时,不能阻止你开发一个表现层 ,以此来查看领域-逻辑和数据访问结果,并以此来得到客户回馈,但是仍然能推迟决定,例如:模板页建立、强制安全,和其他特殊表现层的决议的时间。在其它方案中,假设你正在研究一些复杂的商业规则,你希望在没必要写一些单元测试来压缩每一个细小的变化的情况下,你的客户也能证明。 FIT (Framework for Integrated Test)是开发者的工具。开发者利用此工具迅速地伪造表现层 ,并且在开发者和项目赌金保管者之间提供一个更合作的努力。正如Fit 站点所显示的,完成这个就知道软件应该做什么以及它做了什么。它自动地把顾客的期望比喻成实际结果。可以说,是一个工具,例如:这个。不是基本的,也不要求这个工具来检测NHibernate;但是需要重视测试驱动发展的重要性,一个像Fit的工具,例如,如果使用得正确,它就会像NUnit一样,可应用到软件质量中。

首先查看Fit测试结果,你能使用WinFITRunnerLite,WinFITRunnerLite它在Windows客户里运转Fit测试,与NUnit, or FitNesse的方法相似,同样它提供一个基于网页的wiki,用来修改测试输入和检查Fit测试结果。尽管过程烦琐一些,FitNesse提供了一个灵活的结构,允许客户参与确认译码逻辑以及应用程序流程。下面的屏幕显示了一个简单的例子,例子中显示了你期望通过使用FitNesse从运转计算器测试中看到这个输出情况。

尽管这篇文章没有Fit测试的执行例子,我期望你能对学习更多的这种强有力的结构感兴趣。除了前面列出的网站,可能在Rick Mugridge 和Ward Cunningham共同编写的Fit for Developing Software这篇文章中,有与Fit的使用以及和Fit的扩展有关的广泛信息。

通过NUnitAsp运行ASPX "Smoke Tests"

在这一点上,我们单元测试了领域层、数据访问层,而且我们学到了通过使用Fit,检测粗糙表现层 ,为了把将客户连接得更紧密。现在是ASPX 页码检测它们自己得时候了。NUnitAsp是一个类库用于执行这些(Aspx)单元测试。通过使用NUnitAsp和你的WebForms测试,尽管你能熟练地在WebForms测试中使用NUnitAsp,但是我发现:对于从连续综合服务器中运转一个"冒烟测试"来证明没有页码被破坏,NUnitAsp是最好的。再进一步考虑往往会导致许多相关单元测试码的维护。本质上,因为这些HTTP很慢,它们很少运转,因此只是被轻轻地维护着,因此它们必须尽可能保持得越简单。

BasicSample.Tests/Web/WebSmokeTests.cs展示这些单元测试得样本。尽管极其简单,这些冒烟测试经过很长时间来证明了:你的表现层是可以作出响应的,数据库通信正确地工作,NHibernate HBMs,在极大程度上,是没有错误的。作为一个补充的红利,如果冒烟测试在被安置后,迅速地被对准在生产环境中,它将预载所有ASPX页码,为下一个访问者提供一个更能响应的经验。在你的应用程序里,你应该为每一个URL可访问的网页提供一个冒烟测试。为了帮助组织它们,在冒烟测试的每一个分组中,建立一个独立的测试类型。例如:网页的管理部分的冒烟测试应该能在一个被叫做AdminSmokeTests.cs. BasicSample.Tests/Web/WebSmokeTests.cs文件中找到。

BasicSample.Core for Defining the Domain Layer

BasicSample.Core项目包括领域模型以及NHibernate HBM文件。这个项目也包括接口,在BasicSample.Core.DataInterfaces命名空间里描述数据访问对象。(可论证的是:在逻辑上,HBM 文件属于BasicSample.Data组件,但是在物理上,它们描述的在封装中HBM文件与接近领域对象所带来的方便超过损害。

分离接口

你将注意到BasicSample.Core项目没有包括数据访问对象执行细节,仅仅端口描述了它需要的服务。操作这些接口的具体的DAO类型能在BasicSample.Data里找到。正如前面所描述的,这个技术被称为Separated Interface.如果你认为BasicSample.Core是一个"高级别",BasicSample.Data 是一个"低级别",然后,正如Robert Martin所描述的:每一个高级别为它需要的服务宣告了一个抽象的接口。然后,从这些抽象的接口中意识到低级别。因此高级别不依赖于低级别。相反,低级别依赖于抽象服务接口(在高级别中宣告的)。

来看运转中的这一现象,在命名空间BasicSample.Core.DataInterfaces.中描述了数据接口。IDao是一个泛型接口,提供典型数据访问功能。然后,IDaoFactory为一个或者多个DAO 工厂类型而作为接口而工作。IDaoFactory接口的译码允许你为生产代码创建一个具体的DAO工厂,另外一个具体的DAO 工厂为单元测试目的返回DAO 双重测试。(这是一个使用抽象工厂模式的例子)。正如前面在BasicSample.Tests中检测的,综合利用mock objects in unit tests为每次检测一个单一的责任提供了一个方法。

集合泛型

到现在为止,C# 2.0已经进入表,其中一个最重大的好处就是包括 泛型 。有了泛型 ,当仍然在强烈执行被输入的译码“合同”时,可以有效地完成更多的代码再使用。在这篇文章的上一部分,Ayende 非常有用,但是被反对。NHibernate.泛型 用于连接NHibernate 和.NET 泛型 。但是现在NHibernate 1.2天生支持泛型 ,没必要再需要这个类型库。如果你在过去使用过Ayende 库,首先你要先完完全全从它那移出来,特别是如果你曾使用了自动配线,以此来操作母/子关系。但是不要让它阻止你,即使你没有立即refactor out 自动配线时,你仍然可以更新NHibernate 1.2。在下面Migrating from NHibernate 1.0x to 1.2中能找到从Ayende's NHibernate.泛型refactor 出来所要求的步骤的更多信息。Ayende's NHibernate.泛型 的一起缺点就是需要将内部连接和取值函式 和设值函式一起显示出来。这个损坏封装,允许以在无意识的方法操作或者修改连接-把它看成是 集合 困扰.现在NHibernate生来就支持泛型 ,可能要采用更好的 集合 encapsulation技术。下面来自于Customer.cs 和 Customer.hbm.xml的代码展示了一个更好的泛型 集合s封装。
public IList<Order> Orders { get { return new List<Order>(orders).AsReadOnly(); } protected set { orders = value; } } public void AddOrder(Order order) { if (order != null && !orders.Contains(order)) { orders.Add(order); } } public void RemoveOrder(Order order) { if (orders.Contains(order)) { orders.Remove(order); } } private IList<Order> orders = new List<Order>(); <bag name="orders" table="Orders" inverse="true" cascade="all"> <key column="CustomerID" /> <one-to-many class="BasicSample.Core.Domain.Order, BasicSample.Core" /> </bag>
设置命令集合设值函式到被保护的区域,允许NHibernate 组装 集合 ,而且不需要直接依赖一个私人会员,或者不需要公用地展现设值函式。另外一个选择就是:NHibernate setting access="field"可以用于直接设置一个私人会员,但是应该仔细考虑使用这个,而且只有被批准的时候才能用。在上面的样本代码中,注意AddOrder 和RemoveOrder对Customer 类型所做的。通过增添集合管理到包含类型,它们厚颜无耻地污染了这个类型。如果Customer结束,仍有许多 集合以及管理每一个的方法,想象这样子会导致的麻烦。如果有这样的例子,最好采用定制 集合。

作为使用定制集合的简单例子,Supplier类型可能有一些Product 条目与它连接在一起。警告人们NHibernate需要映射到IList 集合,因此,就会保持两个集合。第一个是产品集合自己,这是NHibernate 所注意到的,第二个就是产品集合包装纸,此包装纸显示了定制集合。下面来自于Supplier.cs 和 Products.cs的代码就表示了这些。
// Within Supplier.cs... public Products Products { get { if (productsWrapper == null) { productsWrapper = new Products(products); } return productsWrapper; } } private Products productsWrapper; // NHibernate binds directly to this member by the access="field" setting private IList products = new List<Product>(); // Within Products.cs public Products(IList products) { this.products = products; } public void Add(Product product) { if (product != null && !products.Contains(product)) { products.Add(product); } } private IList products;

尽管很简单,示例代码应该满足你的定制集合的大部分要求。但是在一些情况中,一个成熟的泛型,要求有定制的集合。其中一个最常见的方案包括创建一个定制集合,此定制集合执行BindingList.在这些情况中,有必要综合利用NHibernate's IUser 集合Type.在这里将找到执行这个的好例子。

Generic IDs and Object Comparisons

BasicSample.Core,中,每一个可持续的领域对象都从DomainObject 继承而来。这个类型处理与比较两个领域对象以求平等有关的大部分工作。(在devlicio.us blog post中,将找到这个对象的详细讨论)。DomainObject 也是一个泛型,接受数据类型,此数据类型宣告领域对象的ID 类型。这个泛型属性提供了拥有一个领域对象的能力,此领域对象使用一个字符串作为ID ,例如:Customer, 且另一个领域对象拥有long 作为ID,例如:Order. 。

应该适时地注意到:在这篇文章的前面部分,ID 属性有一个公用的取值函式 和设值函式。尽管取值函式 很重要,公用的设值函式污染现存的数据,以此打开了罪恶之源。假设你从数据库重新找到一个顾客,偶然地,把它的ID 设置到了另一个客户的ID 。当客户返回到数据库时,它的数据比另一个客户的数据多。举另一个更微妙的例子,假设Customer 被当作伪造的DTO 使用,此伪造的 DTO 正从一个编辑屏幕返回。有一个公用的ID 取值函式 ,开发者设置了ID 以及可以应用的属性,把它返回到另一个对象,此对象处理将DTO 信息传输到“真”客户(此客户是从数据库得到的)。

要提醒你的是:DTO 和“真”客户都是作为Customer 实例被传递的。 因此如果这样,在保持状态中,一个开发者没有意识到从现场返回的Customer 应该当作DTO 对待,而不是一个真的客户,继续在它上面直接调用保存 ,然后因为它使用稀有的DTO数据,写得过多,它很可能丢失许多现场客户数据。这一现象后面的主要缺点就是ID 有公用设值函式这一事实。因此,当从数据库下载对象时,领域对象的ID只被NHibernate设置,并且不在公用设配里出现,这样是最好的。有一些这样的情况:领域对象有一个被指定的ID.(我没有找到指派的IDs的空间,尽可能的避免它们,但是应该知道要建立一个变元供它们使用。在BasicSample.Web/AddCustomer.aspx里你能找到进一步的信息。)当要求这个被指定的ID时,包括一个叫做IHasAssignedId的接口。因此即使它为设置ID提供了初步方法,但是它需要更多的想法,此想法与它的使用有关,并且提供一个好场地,以此来包含ID-委派商业逻辑。下面就是snippet 例证了Customer类型里的这个。

public class Customer : DomainObject<string>, IHasAssignedId<string> { public void SetAssignedIdTo(string assignedId) { Check.Require(!string.IsNullOrEmpty(assignedId), "assignedId may not be null or empty"); // As an alternative to Check.Require, the Validation Application Block could be used for the following Check.Require(assignedId.Trim().Length == 5, "assignedId must be exactly 5 characters"); ID = assignedId.Trim().ToUpper(); } ... }

不显示一个公用设值函式的明显缺陷就是:这个属性不能用于单元测试结构,除非把NHibernate用于下载项目。为了绕过这个问题,类型BasicSample.Tests/Domain/DomainObjectIdSetter.cs使你能够建立领域对象ID,即使它们不能执行IHasAssignedId.这一能力在没有给ID属性提供公用设值函式时,也能打开许多测试可能性。在flipside上,使用反射设置私人会员,当绝对必要时,应该被看成是非标准惯例,,对单元测试有好处。因为它建立在字符串的基础上,它能增加复杂性,而且它很脆弱。但是对于设置领域对象得ID性能,它十分适合单元测试层。

映射领域对象到数据库

NHibernate给数据库提供了两种映射领域对象的方法:使用XML的HBMs 以及绘图属性。HBM-XML 文件的主要优点就是:在物理上,它们与它们描述的领域对象分离开来。这使得领域对象仍然是POCOs (清晰陈旧的C#对象),相对地,POCOs 遗忘了它们是怎样与数据库连接起来的。但是,保持映射信息与领域对象分离开来,也可以看成是HBMs 的缺点,因为它要求额外的努力,以此来保持HBMs 和它们映射类型之间的转换。(一些人讨厌用XML)映射属性,另一方面,与领域对象紧密地联系在一起,而且没有HBM 等同体那么冗长。使用映射属性使得领域对象比POCOs 更像Active Records(对于真的Active Records支持,考虑使用Castle Project's ActiveRecord)。

除了污染领域对象以外,映射属性要求与NHibernate.Mapping.Attributes 相关的参考,NHibernate.Mapping.Attributes 使领域层没有数据访问提供者。另一方面,你经常发现自己完全转换数据访问层吗?但是作为一个基本规则,领域层应该保持作为数据访问提供者,因为它对应用程序的设计目标很实在。当涉及到它时,决定使用HBMs 或者映射属性是个人爱好问题。当开始创建一个新的项目时,应该考虑使用哪种技术;混合技术可能会导致混淆,因为它可能不清楚哪个对象要映射,哪个对象不需要映射.在样本应用程序里没有找到下面的片断,展示了使用映射属性,而不是HBMs的例子。在NHibernate docs中能找到额外的与映射属性有关的信息。
[NHibernate.Mapping.Attributes.Class] public class Customer { [NHibernate.Mapping.Attributes.Property] public string FirstName { ... } ... }
NHibernate支持空类型

NHibernate 1.2 为表所带来的新性能支持nullable类型。以前,Nullables.NHibernate 的引用支持nullable类型,但是,不在需要这一类型。在HBMs里,没必要对待Nullables。

属性映射不同于其它属性映射;简单地映射nullable的属性,就像对待其它任何属性一样。NHibernate 足够聪明,能在数据库和nullable的属性(它映射的)之间传输null 。 看一看BasicSample.Core/Domain/Order.cs ,它一个使用了nullable DateTime.的示例。

尽管能很简单地操作对nullables的属性支持,只有通过仔细的考虑,才能使用。我自己的经验导致我不能在数据库(允许零)内创建列。依我看来,在数据库或者领域层内,映射零到原始的类型没有什么意义。nullables的DateTime属性是我能想起的唯一异常现象。很明显,这里有大量的有效形式,来和非原始关系一起使用null,但是,即使在那个时候,使用Null Object pattern是检查null(被代码分散的)的一个好选择。

NHibernate支持存储过程

NHibernate 1.2支持存储程序,是这个构架性能的关键补充。但是,如果NHibernate处理了所有的CRUD,与存储proc连接到底有什么好处?有两个原因:和遗留数据库一起运转,以及执行报告查询。创建一个存储程序,返回集中查询的报告报告数据是轮流检测数据库的另一个选择。为了解释,Northwind数据库维护由每一个顾客发出的订单。

假设你需要返回每一个产品的数量,数量是由特定的客户要求的。类型BasicSample.Core/Domain/HistoricalOrderSummary.cs 是一个值对象,此值对象压缩这个总结信息。注意这个类型没有从DomainObject继承,也没有响应的HBM文件。从NHibernate的角度来看,这是一个"非托管"类型,不能与非托管 C#相混淆。文件HistoricalOrderSummary.hbm.xml 定义一个指定的查询,这个指定的查询建立了一个到存储程序CustOrderHist的呼叫,为了返回需要的报告数据。(当解剖 BasicSample.Data项目时,将在下面讨论这个数据是怎样被转换成领域对象的)

提供性能,与存储程序相交流导致了这一问题:存储程序应该执行什么,领域层应该执行什么。作为存储proc 的另一个选择,要满足前面提出的要求,C# 代码要能恢复特定客户发出的所有命令,把它们以环连接起来,总结每一个产品的数量。但是很明显,拥有SQL Server 来执行这一总结,将会有效得多;想象如果客户已经发送了成千上万个命令,将会是怎样?。If,如果,另一方面, 在允许领域层做这份工作和让存储程序做这份工作之间,性能分析在效率方面没有什么差别,然后让领域层做这份工作。你将发现这将带给它一个更完整的领域驱动设计以及更好的结果代码的可再用性。正如经常在发展阶段碰到的,在更好的领域驱动设计和优化了的性能之间有一个综合利用…… 直到瓶颈发现后,总是偏向领域。正如在介绍中提到的,阅读Peter Weissbrod's article中的介绍,此介绍证明与NHibernate相关的瓶颈。意外的是,与我相牵连的第一个NHibernate是50,000 LOC,总共要求6个存储程序,来帮助优化数据查询性能。这应该告诉你:在没有一个客观的性能提高时,你可以放在领域层放多少。

 

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号