1、领域驱动设计与模型驱动开发致谢: 此培训材料借鉴了来自参考文献以及互联网的大量资料,部分资料的参考来源未能尽数列举,谨在此对那些在网络中无私分享自己知识的人表达我的衷心感谢!培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模型驱动开发领域驱动设计思想的发展 2002年Martin Fower在其出版企业应用架构模式中,归纳总结了40多种企业应用架构的设计模式。其中所提到的多种设计模式和概念,如事务脚本、活动记录和领域模型等,对业界产生了深远的影响。 2004年著名建模专家Eric Evans发表了他最具影响力的著名书籍:Domain-Driven Des
2、ign Tackling Complexity in the Heart of Software(中文译名:领域驱动设计软件核心复杂性应对之道),书中提出了“领域驱动设计(简称DDD)”的概念。 2010年Greg Young在“CQRS, Task Based UIs, Event Sourcing agh! ”一文中对Betrand Meyer的CQS模式进行改造,提出CQRS模式。 此后Jimmy Nilsson的Applying Domain-Driven Design and Patterns、Abel Avram和Floyd Marinescu合作的Domain-Driven De
3、sign Quickly、Dan Haywood的Domain-Driven Design Using Naked Objects、以及Vaughn Vernon的Implementing Domain-Driven Design等书籍的出版,丰富了领域驱动设计的实践和指导。领域驱动设计是什么 领域驱动设计事实上针对是OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术框架进行了分层规划,同时对每个类进行了策略和类型的划分。nIts a set of proven modeling techniques especially targeted to complex applica
4、tions.nIts a set of principles and practices supporting the development process.nIts a set of patterns that support a clean and coherent view of the domain model.nIts a set of pragmatic strategies allowing applications to scale in size and complexity maintaining their integrity.领域驱动设计的特性领域驱动设计分层规划(一
5、) 领域驱动设计分层规划领域驱动设计分层规划(二) 领域驱动设计是对传统N层架构模式的继承和发展领域驱动设计分层规划(三) 领域驱动设计是对传统N层架构模式的继承和发展Core J2EE Patterns例:J2EE参考分层架构传统J2EE或Spring+Hibernate等事务性编程模型只关心数据,这些数据对象除了简单sette/getter方法外,没有任何业务方法,被比喻成“失血模型”。领域驱动设计分层规划(四) 分布式领域驱动设计领域驱动设计分层规划(五) 分布式领域驱动设计与DotNET技术架构体系之间的关系映射面向对象分析与设计技术 面向过程vs.面向对象 事务脚本模式把业务逻辑组织
6、成单个过程,在过程中直接调用数据库,业务逻辑在服务(Service)层处理。 事务脚本模式的特点是简单容易理解,面向过程设计。对于少量逻辑的业务应用来说,事务脚本模式简单自然,性能良好,容易理解,而且一个事务的处理不会影响其他事务。 不过缺点也很明显,对于复杂的业务逻辑处理力不从心,难以保持良好的设计,事务之间的冗余代码不断增多,通过复制粘贴方式进行复用。可维护性和扩展性变差。对类的策略和类型的划分对类进行StereoType(“构造型”)划分的好处在于: (1)指导设计 (2)帮助命令对象 (3)辅助理解 按照策略和类型对类进行划分六边形架构 以领域模型为核心的六边形架构领域驱动设计中的设计
7、模式 有助于获得柔性设计的设计模式每个元素的名称都提供了一次揭示设计意图的机会。站在客户开发人员的角度上来思考它。人们为了使所有类和操作都具有相似的规模而寻找一种一致的力度。粒度的大小并不是唯一要考虑的问题,我们还要考虑粒度在哪种场合下使用。随着代码重构不断适合新理解的概念或需求,概念轮廓也就逐渐形成了。搞内聚低耦合原则既适用于代码,也适用于概念。领域驱动设计软件核心复杂性应对之道第10章任何对未来操作产生影响的系统状态的改变都可以成为副作用。把命令和查询严格地放到不同操作中;创建并返回Value Object。允许我们安全地对多个操作进行组合。使用断言把副作用明确表示出来,使它们更易于处理。
8、寻找在概念上内聚的模型,更易推出预期ASSERTION,从而加快学习过程并避免代码矛盾。尽一切可能保持低耦合。把所有无关概念提取到对象之外,类就变成完全孤立的了,使得我们可以单独地研究和理解它。每个孤立类都极大减轻了因理解Module而带来的负担。操作闭合:在适当的情况下,在定义操作时让它的返回类型与其参数相同。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖性。培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模型驱动开发使用通用语言的重要性Talking different languages makes projects fail.nP
9、rogrammers speak using technical jargon (design patterns, acronyms, geeky in-jokes)nDomain experts use terminology specific to their field of expertise nComputers speak programming languagesn大家必须妥协领域驱动设计的关键点 关注核心领域(Core Domain) 领域专家和软件从业者共同开发模型 在一个明确的限界上下文(Bounded Context)中使用领域通用语言(ubiquitous langua
10、ge)通用语言(一)通用语言(UBIQUITOUS LANGUAGE)是团队共享的语言。领域专家和开发者使用相同的通用语言进行交流。事实上,团队中每个人都使用相同的通用语言。不管你在团队中的角色如何,只要你是团队的一员,你都将使用通用语言。n通用语言是团队自己创建的公用语言。团队中同时包含领域专家和软件开发人员。n通用语言更多地是关于业务本身如何思考和运作的,领域专家对通用语言有很大影响。不同领域专家会在概念和术语上产生分歧,甚至也会犯错,当领域专家和开发者一起创建领域模型的时候,他们有时会达成一致,有时会做一些妥协,但最终目的都是为了创造最适合项目的通用语言。团队成员们妥协的绝对不应是通用语
11、言的质量,而是概念、术语和含义。最初的一致并不表示始终一致,通用语言也会随着时间推移而不断演化改变。n领域驱动设计的一个核心思想就是使用基于模型的共同语言。因为模型是软件满足领域的共同点,它很适合作为这种通用语言的构造基础。使用模型作为语言的核心骨架,要求团队在进行所有的交流都是使用一致的语言,在代码中也是这样。在共享知识和推敲模型时,团队会使用语言、文字和图形。这儿需要确保团队使用的语言在所有的交流形式中看上去都是一致的,这种语言被称为“通用语言(Ubiquitous Language)”。n通用语言的词汇表包括类名称和主要操作。语言中包含术语,有些术语用来讨论模型中已经明确的规则,还有一些
12、术语则来自施加于模型上的高级组织原则。最后,团队一致应用于领域模型的模式名称使这种语言更为丰富。模型之间的关系成为所有语言都具有的组合规则,词和短语的意义反映了模型的语义。通用语言(二) 在应用通用语言时,应注意:n将模型作为语言的中心。确保团队在所有交流活动和代码中坚持使用这种语言。在画图、写东西特别是讲话时也要使用这种语言。n通过尝试不同的表示方法(它们反映了不同模型)来消除难点。然后重构代码,并对类、方法和模块重新命名,以便与新模型相一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一个公认的理解一样。n要认识到UBIQUITOUS LANGUAGE中的更改就是对模型的更改。n领域专
13、家应该避免使用拗口或无法表达领域理解的术语或结构,开发人员应该密切监视那些将会妨碍设计的有歧义和不一致的地方 有了通用语言,模型就不仅仅是一个设计工作了。它成为开发人员和领域专家共同完成的每项工作中的不可或缺的部分。语言以动态形式传递知识。使用这种语言进行讨论能够更清楚地表达图和代码背后的真实含义。 通用语言是那些不以代码形式出现的设计方面的主要载体,这些方面包括把整个系统组织在一起的比例结构、定义了不同系统和模型之间关系的Bounded Context,以及在模型和设计中使用的其他模式。通用语言的应用 通用语言贯穿于项目的各个环节nUser StoriesnProject Meetingsn
14、Team EmailsnInstant MessagesnSchedule PlannSoftware Documents 在限界上下文中,保持语言的一致性(如口语、图形(如UML图等)、文字、代码等)。通用语言的应用示例(一) User StoriesNOWhen User logs on with valid credentials, an empty panel is displayed.YESWhen Player logs on with valid credentials, an empty board game is displayed. (from a Tic Tac Toe
15、Game software example)通用语言的应用示例(二) Code ExampleNO. Integer i = new Integer();. String char1 = new String();. public class GameDAO() . catch (Exception e)YES. String realMeaningOfMyString = new String();. public class ScoreDataLoader() . catch (Exception NotLoggedInException)NO. Ambiguities. Inconsis
16、tencies. Synonyms. AbbreviationsYES. Clarity. Precision. Reuse. Full Namespackage tictactoe.client.userInterface;/* Add the string O or X to a cell in the grid.*/public class ShowCellGridpublic static void displayUser (Grid grid, Cell cell) if (!Initialization.flag & Initialization.gameStatus.getSeq
17、uence() = null & isEmpty(grid, cell) Initialization.flag = true; String mk= showString(Initialization.gameStatus .getCurrentUser().getUserString(); grid.setHTML(cell.getRowIndex(), cell.getCellIndex(), mk); Initialization.gameStatus.getStatus()cell.getRowIndex()cell .getCellIndex() = Initialization.
18、gameStatus .getCurrentUser(); GameEnd.checkEnd(Initialization.gameStatus, cell.getRowIndex(), cell.getCellIndex(); (.)A class BEFORE and AFTER Ubiquitous Languagepackage tictactoe.client.userInterface;/* Performs a move in the game.*/public class PlayerMove /* When the player clicks in a cell, the g
19、ame draws an O or a X on the * game grid depending on which players turn it is.*/public static void makeMove (GameGrid gameGrid, Cell cell) if (!GameInitialization.waitingMoveFlag & GameInitialization.currentGameStatus.getSequenceWinner() = null & isCellEmpty(gameGrid, cell) GameInitialization.waiti
20、ngMoveFlag = true; String marker = showPlayerIcon(GameInitialization.currentGameStatus .getCurrentPlayer().getPlayerIcon(); gameGrid.setHTML(cell.getRowIndex(), cell.getCellIndex(), marker); GameInitialization.currentGameStatus.getGameMoves()cell.getRowIndex()cell.getCellIndex() = GameInitialization
21、.currentGameStatus.getCurrentPlayer(); CheckWinner.checkForWinner(GameInitialization.currentGameStatus, cell.getRowIndex(), cell.getCellIndex(); (.)(Excerpted from a Tic Tac Toe Game source code)Which one would a Stakeholder better understand?Player Move Performs a move in the game. Make Move When t
22、he player clicks in a cell, the game draws an O or a X on the game grid depending on which players turn it is. Is Cell Empty The Player can select a cell only if it wasnt already selected.Show Cell Grid Add the String O or X to a cell in the grid. Display User Is Empty(Excerpted from a Tic Tac Toe G
23、ame source code)模型的统一模型的内部一致性又叫做“统一”,这样每个术语都不会有模棱两可的意义,也不会有规则冲突。除非模型在逻辑上是一致的,否则它就没有意义。 识别限界上下文中的不一致:重复的概念和假同源n重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生改变时,都必须要更新两个地方。每次由于新的知识导致一个对象被修改时,也必须重新分析和修改另一个对象。如果不进行实际的重新分析,结果就会出现同一个概念的两个版本,它们遵守不同的规则,甚至不同的数据。更重要的是,团队成员必须学习同一操作的两种方法,以及保持这两种方法同步的各种方式。n假同源是指使
24、用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。但是,当两个定义都与同一个领域方面相关,而只是在概念上稍有区别时,这种冲突更难以发现。假同源会导致开发团队互相干扰对方的代码,也可能导致数据库中含有奇怪的矛盾,还会引起团队沟通的混淆。注意用词词汇n注意正确用词,不要歪曲词义n开发人员经常习惯于使用增/删/改/查(CRUD)此类动词词汇,也许有时候它们也确实属于通用语言,但大多数情况下,它们并不能正确反映业务,用词上混淆了业务概念。模型的分裂在理想的世界中,我们可以有一种把整个企业领域包含进来的单一模型;这个模型将是统一的,没有任何相互矛盾或相互重叠的术语定义;
25、每个有关领域的逻辑声明都将是一致的。但大型系统开发并不是这样理想。大型系统领域模型的完全统一是不可行的,也不是一种经济有效的做法。我们可以采用限界上下文(Bounded Context)定义每个模型的应用范围,采用上下文映射(Context Map)给出项目上下文以及它们之间关系的总体视图。n任何一个大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。n明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模
26、型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。在Context中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他Context中,会使用其他的模型,这些模型具有不同的术语、概念、规则和UBIQUITOUS LANGUAGE的技术行话。n定义Bounded Context:视察项目的现状,而不是它的理想状态。领域、子域和限界上下文核心域、支撑域和通用域A Core Domain is a part of the business Domain that is of primary importance to the success of
27、the organization. It is of utmost importance to the ongoing success of the business.If a domain models some aspect of the business that is essential, yet not Core, it is a Supporting Subdomain.if a domain captures nothing special to the business, yet is required for the overall business solution, it
28、 is a Generic Subdomain.Focus on the core domain战术建模与战略建模领域驱动设计的综合应用共享内核(Shared Kernel)当不同团队开发一些紧密相关的应用程序时,如果团队之间不进行协调,即使短时间内能够取得快速进展,他们开发出的产品也可能互相不适合,最后可能不得不在转换层上花费大量时间,而且得到的产品也五花八门。n从领域模型中选出两个团队都同意共享的一个子集。当然,除了模型的这个子集以外,这还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的状态,而且一个团队在没与另一个团队商量的情况下不应擅自更改它。n功能系
29、统要经常进行集成,但集成的频率应该比团队中Continuous Integration的频率低一些。在进行这些集成的时候,两个团队都要运行测试。nShared Kernel通常是Core Domain,或是一组Generic Subdomain(通用子领域),也可能二者兼有。企业架构方法与领域驱动设计架构架构内容内容框架框架 企业连续系列企业连续系列架构开发架构开发方法方法架构开发指引和技术架构开发指引和技术参考模型参考模型架构架构能力能力框架框架 两者都强调Business和IT的高度统一,很多企业架构方法对于领域驱动设计“战略设计”的具体实施办法具有详实的指导意义。如TOGAF V9构件:
30、eTOM业务建模Level 0 ProcessesLevel 1 ProcessesLevel 2 Processes业务流程解耦/分解eTOM业务建模BSS业务流程框架领域解决特定问题领域解决特定问题eTOM信息数据模型eTOM 0级视图SID 1级视图ABE:Aggregate Business Entity,ABE是SID中一组定义良好的实体,具有高内聚、低耦合的特征。共享内核共享内核eTOM信息数据模型参考读物 领域驱动设计软件核心复杂性应对之道及实现领域驱动设计中的相关章节 软件方法-业务建模和需求第三章“业务建模”中的相关内容 参考模型范例:nTMForum的eTOM模型: 培训内
31、容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模型驱动开发领域驱动设计的构造块Entity(实体) 实体是一个具有唯一身份标识的对象,并且可以在相当长的一段时间内持续地变化。我们可以对实体做多次修改,故一个实体对象可能和它先前的对象大不相同,但是由于它们拥有相同的身份标识(identity),它们依然是同一个实体。 我们通过标识对对象进行区分,而不是属性,此时我们应该将标识作为主要的模型定义。同时我们需要保持简单的类定义,并且关注对象在其生命周期中的连续性和唯一标识性。 随着对象的改变,我们可能会跟踪这样的改变,比如什么时候发生了改变,发生了什么改变,是谁做
32、出的改变等。我们应该慎重对待在对象整个生命周期中所发生的合法改变。 唯一的身份标识和可变性(mutability)特征将实体对象和值对象(Value Objects)区分开来。n很多时候,一个领域概念应该建模成值对象,而不是实体对象。n实体和值对象是领域模型概念,而不是数据存储模型概念。Value Objects(值对象)值对象的特征n它度量或者描述了领域中的一件东西。n它可以作为不变量。n它将不同的相关的属性组合成一个概念整体n当度量和描述改变时,可以用另一个值对象予以替换n它可以和其他值对象进行相等性比较n它不会对协作对象造成副作用。当我们只关心一个模型元素的属性时,应把它归类为值对象。我
33、们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。值对象应该是不可变的。不要为它分配任何标识,而且不要把它设计成Entity那么复杂。应该尽量使用值对象来建模而不是实体对象,即便一个领域概念必须建模成实体,在设计时也应该更偏向于将其作为值对象容器,而不是子实体容器。实体对象与值对象是领域概念,而不是数据存储模型概念n值对象可以与其所在的实体对象保存在同一张表中,值对象的每一个属性保存为一列;值对象也可以独立于其所在的实体对象保存在另一张表中,值对象获得委派主键,该主键对客户端是不可见的。Entity和Value Object示例Aggregates(聚合)在具有复杂关联的模型中,
34、要想保证对象更改的一致性是很困难的。不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。然而,过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相关绕,从而使系统不可用。在任何具有持久化数据存储的系统中,对数据进行修改的事务必须要有一个范围,而且要有一种保持数据一致性的方式。聚合(Aggregate)是一组相关对象的集合,我们把它作为数据修改的单元。每个聚合都有一个根和一个边界,边界定义了聚合的内部都有什么,根则是聚合中所包含的一个特定实体。在聚合中,根是唯一允许外部对象保持对它的引用的元素,而边界内部的对象之间则可以互相引用。除根以外的其他Entity都有本地
35、表示,但这些标识只有在聚合内部才需要加以区别,因为外部对象除了根Entity之外看不到其他对象。聚合行为视为是一个整体,在每个事务完成时,必须要满足聚合内所应用的固定规则的要求,即保证数据变化的一致性。根实体最终检查固定规则;删除操作必须一次删除聚合边界之内的所有对象;当提交对聚合边界内部的任何对象的修改时,整个聚合中的所有固定规则都必须被满足。原则:在一致性边界之内建模真正的不变条件;设计小聚合;通过唯一标识引用其他聚合;在边界之外使用最终一致性尽量将根实体所包含的其他聚合建模成值对象,而不是实体。Aggregates(聚合)示例Domain Event(领域事件) Domain Event
36、(领域事件)n有时候应用需要记录跟踪事情的发生n领域事件经常被建模为Value Object,但这些Value Object并不能被共享,因为领域事件本身是“唯一”的。n一个领域事件是指一个在领域中“有意义有意义”的事件 HintsnUML四色原型中有一个相近概念,称为时刻-时段原型(Moment-interval),即表示事物在某个时刻或某一段时间内发生。参考:四色原型 四色原型是诞生于90年代,现在被广泛使用的一种系统分析方法,如Borland的Together架构师版,准确地说,是由Peter Coad 和 Mark Mayfield首先提出,然后由David North拓展。Repos
37、itories(资源库/仓储)客户需要以一种符合实际的方式来获取对以存在的领域对象的引用。为每种需要全局访问的对象类型创建一个对象,这个对象就相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体标准来挑选对象的方法,并返回属性值满足查询标准的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的聚合提供Repository。让客户始终聚焦于模型,而将所有对象的存储和访问操作交给Repository来完成。Reposi
38、tory的接口应当采用领域通用语言。作为客户端,不应当知道数据库实现的细节。Repository和DAO的作用类似,二者的主要区别:nDAO是比Repository更低的一层,包含了如何从数据库中提取数据的代码。nRepository以“领域”为中心,所描述的是“领域语言”。Repository把ORM框架与领域模型隔离,对外隐藏封装了数据访问机制。Repositories(资源库/仓储)示例public interface AccountRepository Account findAccount(String accountId); void addAccount(Account acco
39、unt);public class HibernateAccountRepository implements AccountRepository private HibernateTemplate hibernateTemplate; public HibernateAccountRepository(HibernateTemplate template) hibernateTemplate = template; public void addAccount(Account account) hibernateTemplate.save(account); public Account f
40、indAccount(final String accountId) return (Account) DataAccessUtils.uniqueResult(hibernateTemplate. findByNamedQueryAndNamedParam( “Account.findAccountByAccountId”, “accountId”, accountId); Services(领域服务)当领域中的某个操作过程或转换过程不是实体或值对象的职责时,我们便应该将该操作放在一个单独的接口中,即领域服务。如果勉强地把这些重要的领域功能归为Entity或ValueObject的职责,那么
41、不是歪曲了基于模型的对象的定义,就是人为地增加了一些无意义的对象。应确保领域服务和通用语言是一致的,并且保证它是无状态的。正确区分领域服务(Domain Service)和应用服务(Application Service):n我们不应把业务逻辑置于应用服务,但我们会把业务逻辑置于领域服务中。(应用)服务要做“薄”。n领域服务职责:跨聚合实例业务逻辑;没办法合理放到实体中的其它业务逻辑。n应用服务职责:跨限界上下文的业务逻辑;DTO转换;事务AOP、权限AOP、日志AOP、异常AOP;外部系统访问(邮件、消息队列)。n领域服务设计原则:用来组织业务逻辑,面向业务逻辑;细粒度;内部视图看系统;一个
42、请求对应多个服务的多个方法;服务之间会存在依赖;n应用服务设计原则:用来封装业务逻辑;面向用例;粗粒度;外部视图看系统;一个请求对应一个方法;服务之间互不依赖。n应用服务和领域服务区分非常敏感,有时候需要在快速性/方便性上做折衷。Services(领域服务)示例public interface MoneyTransferService BankingTransaction transfer(String fromAccountId, String toAccountId, double amount);public class MoneyTransferServiceImpl implemen
43、ts MoneyTransferService private final AccountRepository accountRepository; private final BankingTransactionRepository bankingTransactionRepository; public MoneyTransferServiceImpl(AccountRepository accountRepository, BankingTransactionRepository bankingTransactionRepository) BankingTransaction trans
44、fer(String fromAccountId, String toAccountId, double amount) 应用服务、领域服务和基础设施服务Factories(工厂) 当创建一个对象或创建整个聚合时,如果创建工作很复杂,或者暴露了过多的内部结构,则可以使用Factory进行封装。应该将创建复杂对象的实例和聚合的职责转移到一个单独的对象,这个对象本身在领域模型中可能没有职责,但它仍是领域设计的一部分。 不同类型的工厂模式:n工厂类n工厂方法Modules(模块) Module为人们提供了两种观察模型的方式,一是可以在Module中查看细节,而不会被整个模型淹没,二是观察Module
45、之间的关系,而不考虑其内部细节。 模块之间应该是低耦合的,而在模块内部则是高内聚的。模块并不仅仅是代码的划分,而且也是概念的划分。一个人一次考虑的事情是有限的(因此才有低耦合);不连贯的思想和“一锅粥”似的思想同样难于理解(因此才有高内聚)。 选择能够描述系统的Module,并使之包含一个内聚的概念集合。这通常会实现Module之间的低耦合,但如果效果不理想,则应寻找一种更改模型的方式来消除概念之间的耦合,或者找到一个可作为Module基础的概念,基于这个概念组织的模型可以以一种有意义的方式将元素集中到一起。找到一种低耦合的概念组织方式,从而可以相互独立地理解和分析这些概念。对模型进行精化,直
46、到可以根据高层领域概念对模型进行划分,同时相应的代码也不会产生耦合。 Module的名称应该是领域通用语言中的术语。模块及其名称应反映出领域的深层知识。培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模型驱动开发概念辨析-VO/DTO/DO/PO(一) View Object(视图对象):视图对象,用于展示层,其作用是把某个指定页面(或组件)的所有数据封装起来。 Data Transfer Object(数据传输对象):这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性
47、能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。 Domain Object(领域对象):从现实世界中抽象出来的有形或无形的业务实体、值对象或领域服务。 Persistent Object(持久化对象):跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。Ref: 概念辨析-VO/DTO/DO/PO(二) VO与DTO:绝大多数应用场景下,VO与DTO的属性值基本一致,但对于设计层面来说,概念上还是存在VO和DTO的区别,DTO代表服务层需要接收的数据和返回的数据
48、,而VO代表展示层需要显示的数据。n示例:服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能你还会反驳,在服务层直接就返回“帅哥美女”不就行了吗?对于大部分应用来说,这不是问题,但设想一下,如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,
49、从职责单一原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的DTO,不应该出现与表现形式的耦合。n实现层面是否需要区分二者概念?具体问题具体分析概念辨析-VO/DTO/DO/PO(三)DTO与DO:DTO是展示层和服务层之间的数据传输对象(可以认为是两者之间的协议),而DO是对现实世界各种业务角色的抽象,这就引出了两者在数据上的区别,例如UserInfo和User,对于一个getUser方法来说,本质上它永远不应该返回用户的密码,因此UserInfo至少比User少一个password的数据。而在领域驱动设计中,DO不是简单的POJO,它具有领域业务逻辑。n在设计层面,展示层向服
50、务层传递的DTO与服务层返回给展示层的DTO在概念上是不同的(如返回UserInfo应该不包含password,但创建User传入的参数需要包含password),但在实现层面,我们通常很少会这样做(定义两个UserInfo,甚至更多),因为这样做并不见得很明智,我们完全可以设计一个完全兼容的DTO,在服务层接收数据的时候,不该由展示层设置的属性(如订单的总价应该由其单价、数量、折扣等决定),无论展示层是否设置,服务层都一概忽略,而在服务层返回数据时,不该返回的数据(如用户密码),就不设置对应的属性。n为什么不在服务层中直接返回DO:DO具有一些不应该让展示层知道的数据;DO具有业务方法,如果