Dino Esposito: 一个领域模型的设计
fmms
13年前
<p><span class="sentence"> 英文原文:<a href="/misc/goto?guid=4959500999426005564" target="_blank">Design of a Domain Model</a><br /> </span></p> <p><span id="xn4_0a69e92e97c8cd136dc23d26b76ab3ed" class="sentence"> 最新发布的 Entity Framework 4.1 和新的 Code First 开发模式打破了服务器程序开发的基本规则:如果数据库没有准备就绪,<span>不要轻举妄动(<span>Don’t take a single step)</span></span>。</span>Code First <span id="xn5_4ac7a471f79ad85495e2296caffa09ef" class="sentence">允许开发人员重点关注业务领域并根据“类”(class)来为该领域建模。</span><span id="xn6_2fc99412e092b0ede653cf2ffb3dde6b" class="sentence">在某种程度上, Code First 模式鼓励在 .NET 环境中应用“领域驱动设计 (DDD) ”原则。</span><span id="xn7_d335b9877db4a1f6ac28fb5e5e62749d" class="sentence">业务领域由相互关联的实体构成,这些实体通过属性对外公开自己的数据,通过方法和事件对外公开自己的行为。</span><span id="xn8_f9a74208235ccae8b546d6293ea1562c" class="sentence">更重要的是,每个实体都可能处于某一状态,并且与一组动态的验证规则相绑定。</span></p> <p><span id="xn9_59e19c820070e376df408e18b5da2855" class="sentence"> 为实际应用场景编写对象模型会面临一些在演示程序和教程中没有涉及的问题。</span><span id="xn10_ae0645071943ddacc9f7592ca2599599" class="sentence">在本文中,我将挑战这些问题,并讨论如何构建 Customer 类,我会就此简要介绍一些设计模式和设计实践,例如Party模式、聚合根(<span>aggregate roots)</span>、工厂(<span>factories)</span>以及代码协定(<span>Code Contracts)</span>和企业库验证应用程序块 (VAB) 等技术。</span></p> <p><span id="xn11_5b71aa15365cfb00c452e6f463c764fe" class="sentence"> 有一个开源项目可以作为参考,这里讨论的代码就是其中的一小部分。</span> 它就是<span class="sentence">由 Andrea Saltarello 创建的 </span>Northwind Starter Kit 项目 (<a id="ctl00_MTContentSelector1_mainContentContainer_ctl03" href="/misc/goto?guid=4959500999511060096">nsk.codeplex.com</a>) ,该项目旨在介绍构建多层解决方案的有效实践。</p> <h2><span id="xn13_36af6af70e8aa120fa1c092884645b78" class="sentence"> 对象模型(Object Model) vs. 领</span><span class="sentence">域模型(</span>Domain Model)</h2> <p><span id="xn15_ede6482618c5a37920eb0e589531f8ba" class="sentence"> 争论是使用对象模型还是领域模型似乎没有意义,在大多数情况下,这只是一个术语表述问题(<span>terminology)</span>。</span> <span id="xn16_882ebcdf05b4988c6f866e5b76f02e18" class="sentence">但准确地使用术语是确保团队所有成员在使用特定术语时始终遵循同一概念的重要因素。</span></p> <p><span id="xn17_81c9e9cea65ce464613438ffefffed99" class="sentence"> 对于软件行业的几乎每个人而言,对象模型是一个具有共性的并且可能相关的对象的集合。领</span><span id="xn18_05dad1b3018845f5e605450dbfe2c96a" class="sentence">域模型有何不同?</span> <span id="xn19_4a91f012fb30a03408e24e7a128f9364" class="sentence">域模型归根结底仍然是一个对象模型,因此,交替使用这两个术语可能不会产生严重的错误。</span><span id="xn20_f00ba7d16ba9fdb95c7714138588d9d7" class="sentence">但在专门强调使用“领域模型”一词时,它可能会使大家对所构建的对象的形态(shape)产生某些期望。</span></p> <p><span id="xn21_4a0e35edf61f4e10268659c25b81b4c1" class="sentence"> 领域模型的这种用法与 Martin Fowler 给出的以下定义相关:</span></p> <blockquote> <p><span id="xn21_4a0e35edf61f4e10268659c25b81b4c1" class="sentence">由行为和数据组合而成的领域的对象模型。</span><span id="xn22_3349e02d4e00e6dbf8a51df98d810ffa" class="sentence">相应地,这些行为用于表达业务规则和特定的业务逻辑(请参阅 <a href="/misc/goto?guid=4959500999597131383" target="_blank">P of EAA page 116</a>)。</span></p> <p><span class="sentence">An object model of the domain that incorporates both behavior and data. <span>In turn, the behavior expresses both rules and specific logic.</span></span></p> </blockquote> <p><span id="xn23_450d9a31c383e551795c419e8c5d3b8d" class="sentence"> DDD 向领域模型中添加了一些实用的规则。</span><span id="xn24_8f06efab486f7409a96ce13eb7b537ab" class="sentence">从这个角度看,领域模型不同于对象模型,它更多推荐使用值对象(value objects)而不是基元类型(primitive types)。</span><span id="xn25_4fa2844e2f45d531678c9b5094f0c29a" class="sentence">例如在对象模型中,一个整数可能具有多种含义,它可能表示温度、金额、大小或数量。</span>而在领<span id="xn26_f043e56eaa4614559c54d45c5901ce78" class="sentence">域模型中,针对各种不同的场景会使用特定的值对象类型。</span></p> <p><span id="xn27_dbc45d0bf4c44c587b79d07b37ede00c" class="sentence"> 此外,领域模型需要识别出聚合根。</span><span id="xn28_81bd1108521e881f8a1040cd9623c73d" class="sentence">聚合根是一个通过组合其他实体而得到的实体。</span><span id="xn29_e6927434f29a88eda924c5e71494ac80" class="sentence">聚合根中的对象与外部没有直接的关联,也就是不存在这样的用例——不经过根对象而直接使用这些对象。</span><span id="xn30_e566a4a926499a8235ce1ee775fdbd15" class="sentence">比如,Order 实体就是一个典型的聚合根。</span> <span id="xn31_dcd8b7e18575b42416df76199074b75c" class="sentence">Order 包含聚合的 OrderItem,而不包含 Product。</span> <span id="xn32_c6a4f4aa6e0d520961932e1e602404ed" class="sentence">难以想象您使用一个OrderItem 而它并不来自 Order(即使这只是由specs决定的,译者注:也就是通过规约查询直接得到相应的OderItem)。</span><span id="xn33_ba5b8ad452644999b09421e1d6fc7dd0" class="sentence">另一方面,您很可能具有这样一些用例,您在其中使用不涉及订单的 Product 实体。</span><span id="xn34_14bc74a8234254a8e2586713983c5d0e" class="sentence">聚合根负责维护处于有效状态的子对象并持久化这些对象。</span></p> <p><span id="xn35_505c14ea675b7d0c2e573fa1c1ce06c6" class="sentence"> 最后,某些领域模型类(class)可以提供用于创建新实例的公共工厂方法,而不是构造函数。</span><span id="xn36_f1be36d8a3ad13a728f64c2629c9e30a" class="sentence">如果模型类通常是独立的并且实际上不是层次结构的一部分,或者用于创建该类的步骤只是与客户端相关,则可以使用普通的构造函数。</span><span id="xn37_894d2836285c8ad8534cd055967be08e" class="sentence">但是,在使用聚合根这样的复杂对象时,您还需要实例化之外的其他抽象级别。</span> <span id="xn38_718128c37a4b91598de900999bcb01a1" class="sentence">DDD 引入了工厂对象(简单一些的话,可以使用类中的工厂方法)方式,这种方式可将客户端的需求与内部的对象及其关系和规则分离开来。</span><span id="xn39_903a711905820c676216e0e99f21258b" class="sentence">可以在<a href="/misc/goto?guid=4959500999686856386" target="_blank"> An Introduction to Domain Driven Design</a> 中找到有关 DDD 的清晰简要的介绍。</span></p> <h2><span id="xn40_533516916ac6776ac2c20ff5f03a5971" class="sentence"> Party模式</span></h2> <p><span id="xn41_996b8d5b323a4b32a4f6da58b0fd816a" class="sentence"> 让我们重点分析一下 Customer 类。</span> <span id="xn42_d48a262265c34a7f50a388f70925e68c" class="sentence">根据上文所述,此处是可能的签名:</span></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;">public</span> <span style="color:#0000ff;">class</span> Customer : Organization, IAggregateRoot { ... }</pre> </div> <p><span id="xn45_9b298a100f8e1a29182b1e59a20073d2" class="sentence"> 谁是您的客户?</span> <span id="xn46_8b680f01e027d338ca223076cac84f9f" class="sentence">它是个人和/或组织?</span> Party <span id="xn47_c37df6cfe4aadda54fa5a42738689eeb" class="sentence">模式建议您区别这两者,并清晰地定义哪些属性是公用的,哪些属性仅属于个人或组织。“代码1”中</span><span id="xn48_483af5f98b106cbe8a49466a6da93c19" class="sentence">的代码仅针对 Person 和 Organization。您可以根据业务领域的需要,将组织细分为非盈利组织和商业公司,从而细化代码内容。</span></p> <p><strong><span id="xn49_8f58707de5f3ea7d56c134f9ad720c48" class="sentence"> 代码1 基于Party模式的类</span> </strong></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;">public</span> <span style="color:#0000ff;">abstract</span> <span style="color:#0000ff;">class</span> Party { <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> String Name { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> PostalAddress MainPostalAddress { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } }<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">abstract</span> <span style="color:#0000ff;">class</span> Person : Party { <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> String Surname { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> DateTime BirthDate { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> String Ssn { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } }<span style="color:#0000ff;">public</span> <span style="color:#0000ff;">abstract</span> <span style="color:#0000ff;">class</span> Organization : Party { <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> String VatId { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } }</pre> </div> <p><span id="xn51_ac5760483fe865b5960675c6ac5cee2a" class="sentence"> 您必须始终记住,您的目标是构建一个可为您的实际业务领域精确建模的模型,而不是生成该业务的抽象表示。</span><span id="xn52_ffaee13a24ceb2ddb40ed630334913c8" class="sentence">如果您的需求只涉及作为个体的客户(Customer),那么 Party 模式不是必需的,即使该模式带来了后续可扩展性。</span></p> <h2><span id="xn53_8d4b6e5d58e4c91e0da1b547c097b5d5" class="sentence"> 作为聚合根的Customer类</span></h2> <p><span id="xn54_df1bce809ba3ec9d3ec3d1861e78025b" class="sentence"> 聚合根是模型中的一个类,它表示一个独立的实体——在与其他实体的关系中并不存在(one that doesn’t exist in relation to other entities,译者注:也就是与其他实体不存在关联)。</span><span id="xn55_215f1bb7362879986e9141e8952253a8" class="sentence">在大多数情况下,您的聚合根只是单独的类,这些类不管理任何子对象,或者只是指向其他聚合的根。</span> “<span id="xn56_b12c583ad52131b55a03ef29cc182ad1" class="sentence">代码2<strong>”</strong>显示了更详细的 Customer 类。</span></p> <p><strong><span id="xn57_5aca944fa52674a85c6add0639b4bb89" class="sentence"> 代码2 作为聚合根的 Customer 类</span> </strong></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;">public</span> <span style="color:#0000ff;">class</span> Customer : Organization, IAggregateRoot { <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> Customer CreateNewCustomer( String id, String companyName, String contactName) { ... } <span style="color:#0000ff;">protected</span> Customer() { } <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> String Id { <span style="color:#0000ff;">get</span>; <span style="color:#0000ff;">set</span>; } ... <span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> IEnumerable<Order> Orders { <span style="color:#0000ff;">get</span> { <span style="color:#0000ff;">return</span> _Orders; } } Boolean IAggregateRoot.CanBeSaved { <span style="color:#0000ff;">get</span> { <span style="color:#0000ff;">return</span> IsValidForRegistration; } } Boolean IAggregateRoot.CanBeDeleted { <span style="color:#0000ff;">get</span> { <span style="color:#0000ff;">return</span> <span style="color:#0000ff;">true</span>; } } }</pre> </div> <p><span id="xn61_c169bd73f84130ca0e0cace76bf21e7a" class="sentence"> 正如您所看到的,Customer 类实现了(自定义)IAggregateRoot 接口。</span> 代码如下<span id="xn62_7fb54f75795ec318e112dae78535581a" class="sentence">:</span></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;">public</span> <span style="color:#0000ff;">interface</span> IAggregateRoot { Boolean CanBeSaved { <span style="color:#0000ff;">get</span>; } Boolean CanBeDeleted { <span style="color:#0000ff;">get</span>; } }</pre> </div> <p><span id="xn64_364e527976317f1cdcea04952c1ac816" class="sentence"> 成为聚合根意味着什么?</span> <span id="xn65_12b02caec98211fb0c5defa2a7182b5d" class="sentence">聚合根处理所包含的子聚合对象的持久化,并负责强制实施与该组对象相关的不变条件(<span> invariant conditions)</span>。因此</span><span id="xn66_da2c8b96b3c2035819fd3f6122f45ab8" class="sentence">,聚合根应该能够检查整个聚合对象堆(stack)是否能被保存或删除。</span><span id="xn67_9eecc04d9501dc30e063936de792a664" class="sentence">独立聚合根只返回 True,而不进行任何进一步检查。</span></p> <h2><span id="xn68_e3c4cda056e5ad16d2b478e552f915d1" class="sentence"> 工厂和构造函数</span></h2> <p><span id="xn69_b43bdc2e58d0799e6539f80d87d1b421" class="sentence"> 构造函数是特定于类型的。</span><span id="xn70_083737be9f3f5d4412510f88711ef83b" class="sentence">如果对象只是一个类型(没有聚合并且没有复杂的初始化逻辑),那么使用普通的构造函数会更好。</span><span id="xn71_9b3701135b0c686e74a7d09c0981c10d" class="sentence">工厂通常是一个有用的额外抽象层。</span><span id="xn72_675f2c758c1e3627438fb24c560d96b3" class="sentence">工厂可以是实体类中的一个简单的静态方法,也可以是一个单独的组件。</span><span id="xn73_f42cd87ae9268c0ad6ae92c72654fcf6" class="sentence">使用工厂方法还可以让代码更具可读性,因为通过它你可以清楚地知道为何要这样实例化。</span><span id="xn74_15d6c78f856e89c9cda47a45a18bac81" class="sentence">如果使用构造函数,那么您在处理不同实例化场景时将受到更多的限制,因为构造函数的方法名不能随意更改(只能与类同名),只能通过签名来识别它。</span><span id="xn75_6e934d1712d1dfb3c3025169a6c8d9cc" class="sentence">特别是长签名(有很多参数的构造函数),在以后使用时会很难弄明白为什么要这样实例化。</span> “代码<span id="xn76_62804c000763a3f32ce814adbbf793e1" class="sentence">3”显示了 Customer 类中的工厂方法。</span></p> <p><strong><span id="xn77_d7861623b0c19c0c7323af9aa47458d6" class="sentence"> 代码3 Customer 类中的工厂方法</span> </strong></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;">public</span> <span style="color:#0000ff;">static</span> Customer CreateNewCustomer( String id, String companyName, String contactName) { Contract.Requires<ArgumentNullException>( id != <span style="color:#0000ff;">null</span>, <span style="color:#800000;">"</span><span style="color:#800000;">id</span><span style="color:#800000;">"</span>); Contract.Requires<ArgumentException>( !String.IsNullOrWhiteSpace(id), <span style="color:#800000;">"</span><span style="color:#800000;">id</span><span style="color:#800000;">"</span>); Contract.Requires<ArgumentNullException>( companyName != <span style="color:#0000ff;">null</span>, <span style="color:#800000;">"</span><span style="color:#800000;">companyName</span><span style="color:#800000;">"</span>); Contract.Requires<ArgumentException>( !String.IsNullOrWhiteSpace(companyName), <span style="color:#800000;">"</span><span style="color:#800000;">companyName</span><span style="color:#800000;">"</span>); Contract.Requires<ArgumentNullException>( contactName != <span style="color:#0000ff;">null</span>, <span style="color:#800000;">"</span><span style="color:#800000;">contactName</span><span style="color:#800000;">"</span>); Contract.Requires<ArgumentException>( !String.IsNullOrWhiteSpace(contactName), <span style="color:#800000;">"</span><span style="color:#800000;">contactName</span><span style="color:#800000;">"</span>); <span style="color:#0000ff;">var</span> c = <span style="color:#0000ff;">new</span> Customer { Id = id, Name = companyName, Orders = <span style="color:#0000ff;">new</span> List<Order>(), ContactInfo = <span style="color:#0000ff;">new</span> ContactInfo { ContactName = contactName } }; <span style="color:#0000ff;">return</span> c; }</pre> </div> <p><span id="xn79_b67d62dbec320e606e74ff7f620a8418" class="sentence"> 工厂方法是一个原子操作,可获取输入参数、执行其作业并返回指定类型的新实例。</span><span id="xn80_86ceec1a7088f7b346a3ba66cbcf2700" class="sentence">应确保返回的实例处于有效状态。</span><span id="xn81_15625465ba959c15e353fef984393a4d" class="sentence">工厂负责履行所有已定义的内部验证规则。</span></p> <p><span id="xn82_d09fa0cb0cc8ad59262d591811e8b919" class="sentence"> 工厂还需要验证输入数据。</span><span id="xn83_cd1bd42d8fc15ba4fc521e949c523c01" class="sentence">为此,可使用代码协定(Code Contracts)前提条件来保证代码的清晰和高可读性。</span><span id="xn84_9bf4f3c1e0b58010271cb2c46558581c" class="sentence">还可以使用后置条件来确保返回的实例处于有效状态,如下所示:</span></p> <div class="cnblogs_code"> <pre>Contract.Ensures(Contract.Result<Customer>().IsValid()); </pre> </div> <p><span id="xn86_27e82ec7d4955455ebce19f1a3180cb6" class="sentence"> 如果在整个类中使用不变式(invariants),经验表明,您无法始终提供这些不变式。</span>不变式<span id="xn87_095f67d02a8f837b6dbe5ba5908aa269" class="sentence">的侵入性可能太强,特别是在复杂的大型模型中。</span><span id="xn88_a73af2cbb2cc7645cea4ff2be8487e7b" class="sentence">代码协定(Code Contracts)不变式有时可能过于严格地遵循规则集,而在您的代码中,有时需要更多的灵活性。</span><span id="xn89_8210c90602fc5e555e62d2535e833fde" class="sentence">因此,最好对必须强制执行不变式的区域进行限制。</span></p> <h2><span id="xn90_a617908b172c473cb8e8cda059e55bf0" class="sentence"> 验证</span></h2> <p><span id="xn91_9fe345bf9b3ab06fb036e1c32c2e9a4e" class="sentence"> 可能需要验证领域类中的属性,以确保必填字段不为空,文本没有超出长度限制,并且相关数值处于规定的范围内等等。</span><span id="xn92_e7cece11193748220fdacaf080d84c5b" class="sentence">您还必须考虑进行跨属性验证以及复杂的业务规则。</span><span id="xn93_3467ee8f0f514fcd111de613fe30839b" class="sentence">如何进行代码验证?</span></p> <p><span id="xn94_a6ca05aa0099c7b936f20b281291afa9" class="sentence"> 验证涉及条件代码,最终涉及组合某些 if 语句,并返回布尔值。不借助任何框架或技术,</span><span id="xn95_7c5c099e03590c8e6f317a293db2d752" class="sentence">纯手工编写验证层也许可行,但实际上并不是一个好主意。这样编写出来的</span><span id="xn96_9ccf12c4a00f53e84970f7e8f76815df" class="sentence">代码的可读性和后续改进的方便性得不到保证,通过一些流畅的代码工具库(fluent libraries)可以改善这种情况。</span><span id="xn97_33f6e13717faa6575dec077a06c3342a" class="sentence">受实际业务规则的限制,验证规则可能会经常变化,您的实现必须考虑到这一点。</span><span id="xn98_9bc24768d6fdfefff794231ce4c6bf81" class="sentence">因此,您不能只编写针对当前验证规则的代码,而是应该编写能够适应验证规则变化的更灵活的代码。</span></p> <p><span id="xn99_7467f910141c78400b52c45fff1cc78a" class="sentence"> 在验证过程中,有时您希望传入无效数据时给出提示,有时您只希望收集相关错误并将其报告给其他代码层。</span><span id="xn100_7cf1789c69bc1687aed71cf5a54e0ef7" class="sentence">记住,代码协定不参与验证过程,它只检查各种条件,然后在条件不适用时引发异常。</span><span id="xn101_f80819420aa2d2f480391c5c84a7825d" class="sentence">通过集中式错误处理程序,您可以从异常中进行恢复并妥善降级。</span><span id="xn102_a1e84234e9ca3a703bba3f4ecb5693b2" class="sentence">通常建议仅在领域实体中使用代码协定,以便捕获可能导致出现不一致状态的潜在严重错误。</span><span id="xn103_e5d201c419771174560301d6b95d11ec" class="sentence">也可以在工厂中使用代码协定,在这种情况下,如果传入的数据无效,代码必须引发异常。</span><span id="xn104_0ca14620b3b094023fd3ad208c6cdd73" class="sentence">是否在属性的 setter 方法中使用代码协定由您自己决定。</span><span id="xn105_7be0a3de69a66c229a6fc75780ac295c" class="sentence">我更喜欢采用更舒适的方式,通过特性类(Attribute)进行验证。</span><span id="xn106_9fc00ab22b45978685f5673c643a7ec0" class="sentence">但使用哪些Attribute呢?</span></p> <h2><span id="xn107_049aefdb64cf5bd1827f2bdd8896758b" class="sentence"> Data Annotations(<strong>数据</strong><span>注解)</span>与企业库</span><span id="xn108_e0a1dc2ae6f24c18948ae9a33568483d" class="sentence">VAB(<span>Enterprise Library Validation Enterprise Block)</span></span></h2> <p><span id="xn109_670ab09b60984e0807cb3e5ab0bd929e" class="sentence"> Data Annotations 命名空间和企业库 VAB 非常类似。</span><span id="xn110_227be338ff10d0f6d79f38f0508c5baf" class="sentence">这两种框架均基于Attribute,可以使用表示自定义规则的自定义类对其进行扩展。</span><span id="xn111_7aaeeb549f95465786ce37639013522a" class="sentence">在这两种情况下,您都可以定义跨属性(property)验证。</span><span id="xn112_ce04d6e904a30c09d31b7b2ce48f621b" class="sentence">最后,这两种框架都提供了验证API,用于评估实例并返回错误列表。这</span><span id="xn113_9b209b2579a2f103e0ea6b61fc714890" class="sentence">两者有何区别?</span></p> <p><span id="xn114_9b09a97a7627c29af4a3b235cbcaaa6c" class="sentence"> Data Annotations 是 Microsoft .NET Framework 的一部分,不需要单独下载。</span><span id="xn115_ace617baaf60dd0a616b2ad433954fec" class="sentence">企业库需要单独下载,在大型项目中并不重要,但在企业应用中可能需要批准,因此仍会产生问题。</span><span id="xn116_aa7f7295148b5b226497c65e2649744c" class="sentence">可以通过 NuGet 轻松安装企业库(请参阅本期专栏中的“<a href="/misc/goto?guid=4959500999763874432" target="_blank">使用 NuGet 管理项目库</a>”一文)。</span></p> <p><span id="xn117_a9c53db5ab74a7257058b960a9ccbf62" class="sentence"> 企业库 VAB 在以下方面优于Data Annotations:可以通过 XML 规则集对其进行配置。</span><span id="xn118_0160ae049debaee02f4eff8848f1de18" class="sentence">XML 规则集是您用于描述所需验证的配置文件中的条目。</span><span id="xn119_1d2c229531a1ea1c2a37fa55347b1b83" class="sentence">不用说,您能够以声明方式更改某些内容,甚至无需改动代码。</span> “代码4”<span id="xn120_7509e157ea7bbc1bba0561d3d7a90c76" class="sentence">显示了一个示例规则集。</span></p> <p><strong><span id="xn121_50e20020110c1951c18d9c5e10db1024" class="sentence"> 代码4 企业库规则集</span> </strong></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;"><</span><span style="color:#800000;">validation</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">type </span><span style="color:#ff0000;">assemblyName</span><span style="color:#0000ff;">="..."</span><span style="color:#ff0000;"> name</span><span style="color:#0000ff;">="ValidModel1.Domain.Customer"</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">ruleset </span><span style="color:#ff0000;">name</span><span style="color:#0000ff;">="IsValidForRegistration"</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">properties</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">property </span><span style="color:#ff0000;">name</span><span style="color:#0000ff;">="CompanyName"</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> messageTemplate</span><span style="color:#0000ff;">="The company name cannot be null"</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="NotNullValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">lowerBound</span><span style="color:#0000ff;">="6"</span><span style="color:#ff0000;"> lowerBoundType</span><span style="color:#0000ff;">="Ignore"</span><span style="color:#ff0000;"> upperBound</span><span style="color:#0000ff;">="40"</span><span style="color:#ff0000;"> upperBoundType</span><span style="color:#0000ff;">="Inclusive"</span><span style="color:#ff0000;"> negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> messageTemplate</span><span style="color:#0000ff;">="Company name cannot be longer ..."</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="StringLengthValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">property</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">property </span><span style="color:#ff0000;">name</span><span style="color:#0000ff;">="Id"</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> messageTemplate</span><span style="color:#0000ff;">="The customer ID cannot be null"</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="NotNullValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">property</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">property </span><span style="color:#ff0000;">name</span><span style="color:#0000ff;">="PhoneNumber"</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="NotNullValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">lowerBound</span><span style="color:#0000ff;">="0"</span><span style="color:#ff0000;"> lowerBoundType</span><span style="color:#0000ff;">="Ignore"</span><span style="color:#ff0000;"> upperBound</span><span style="color:#0000ff;">="24"</span><span style="color:#ff0000;"> upperBoundType</span><span style="color:#0000ff;">="Inclusive"</span><span style="color:#ff0000;"> negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="StringLengthValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">property</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">property </span><span style="color:#ff0000;">name</span><span style="color:#0000ff;">="FaxNumber"</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="NotNullValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"><</span><span style="color:#800000;">validator </span><span style="color:#ff0000;">lowerBound</span><span style="color:#0000ff;">="0"</span><span style="color:#ff0000;"> lowerBoundType</span><span style="color:#0000ff;">="Ignore"</span><span style="color:#ff0000;"> upperBound</span><span style="color:#0000ff;">="24"</span><span style="color:#ff0000;"> upperBoundType</span><span style="color:#0000ff;">="Inclusive"</span><span style="color:#ff0000;"> negated</span><span style="color:#0000ff;">="false"</span><span style="color:#ff0000;"> type</span><span style="color:#0000ff;">="StringLengthValidator"</span> <span style="color:#0000ff;">/></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">property</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">properties</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">ruleset</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">type</span><span style="color:#0000ff;">></span> <span style="color:#0000ff;"></</span><span style="color:#800000;">validation</span><span style="color:#0000ff;">></span></pre> </div> <p><span id="xn123_f31f40b124e30151838768abbbf04fc6" class="sentence"> 规则集列出了您要应用于指定类型中的指定属性 (Property) 的Attribute。</span><span id="xn124_8df14a83e6d12080fbad3ee5b18be682" class="sentence">在代码中,您可以按如下所示验证规则集:</span></p> <div class="cnblogs_code"> <pre><span style="color:#0000ff;">public</span> <span style="color:#0000ff;">virtual</span> ValidationResults ValidateForRegistration() { <span style="color:#0000ff;">var</span> validator = ValidationFactory .CreateValidator<Customer>(<span style="color:#800000;">"</span><span style="color:#800000;">IsValidForRegistration</span><span style="color:#800000;">"</span>); <span style="color:#0000ff;">var</span> results = validator.Validate(<span style="color:#0000ff;">this</span>); <span style="color:#0000ff;">return</span> results; }</pre> </div> <p><span id="xn126_0e457a151e9a1ae3120b3df4636efc05" class="sentence"> 该方法将 IsValidForRegistration 规则集中列出的验证程序应用于指定实例。</span></p> <p><span id="xn127_d899d4569ace816e3f5d04fe9eefbe85" class="sentence"> 关于验证和库的最后一点说明。</span><span id="xn128_0868f98dbf1a2072fecdb0165992cff0" class="sentence">我在这里没有谈及每个常用的验证库,但它们之间并没有明显的区别。</span><span id="xn129_669bc1774cefce915058c3f8a600fe41" class="sentence">重要的是考虑您的业务规则是否发生了更改及更改频率如何。</span><span id="xn130_530911ae1aaad30087e0b0e51942b1c7" class="sentence">您可以在此基础上决定是Data Annotations、Enterprise Library VBA、代码约定(Code Contracts)还是其他某个库更合适。</span><span id="xn131_1f844111e3e07c2d78d7db5b606f51a2" class="sentence">根据我的经验,如果您确切知道所需实现的目标,则可以轻松地选择“正确”的验证库。</span></p> <h2><span id="xn132_8d209bddbf510fd33e5655bc186c5b0a" class="sentence"> 总结</span></h2> <p><span id="xn133_57207e8d18a8ee29c0424ce5e06ac7bb" class="sentence"> 用于真实业务领域的对象模型几乎不会仅仅是属性和类的简单集合。</span><span id="xn134_9b6406bdfe50b188ab53e633db2afd31" class="sentence">此外,在考虑技术问题之前应先考虑设计方面的事项。</span><span id="xn135_7f03c83cfac1b3a3a87e5ed214d9553f" class="sentence">一个设计完好的对象模型可以表达该领域所需的方方面面。</span><span id="xn136_f81560037b35e46a794dcb843114a34f" class="sentence">在大多数时候,这代表可以轻松地对“类”进行初始化和验证,可以方便地给“类”增加更多的属性与逻辑。</span><span id="xn137_31ef6c2b12947c1ec1d5f3b60d6b6203" class="sentence">不应该教条式地看待 DDD 实践,它应该成为你明确前进方向的指南。</span></p> <p><strong><span id="xn138_4dd148710d772e775d5ef94febbe7fc5" class="sentence"> Dino Esposito</span> </strong><em><span id="xn139_0e8eb4b138e1b123ad0ce8051975acaa" class="sentence">是《Programming Microsoft ASP.NET 4》(Microsoft Press,2011 年)和《Programming Microsoft ASP.NET MVC》(Microsoft Press,2011 年)的作者,同时也是《Microsoft .NET:Architecting Applications for the Enterprise》(Microsoft Press,2008 年)的合著者。</span> <span id="xn140_adab03894b304613c7eb9225f0b86ac8" class="sentence">Esposito 定居于意大利,经常在世界各地的业内活动中发表演讲。</span> <span id="xn141_da6ad7b422125268bbb9c3d7b6fba801" class="sentence">有关他的情况,请访问 推ter 上的 <a id="ctl00_MTContentSelector1_mainContentContainer_ctl14" href="/misc/goto?guid=4959500999849465320">推ter.com/despos</a>。</span> </em></p> 来自: <a href="/misc/goto?guid=4959500999938632829" target="_blank">http://msdn.microsoft.com/zh-cn/magazine/hh547108.aspx</a>