《领域驱动设计与模型驱动开发》由会员分享,可在线阅读,更多相关《领域驱动设计与模型驱动开发(142页珍藏版)》请在金锄头文库上搜索。
1、领域驱动设计与模型驱动开发钟玮军2015年3月致谢: 此培训材料借鉴了来自参考文献以及互联网的大量资料,部分资料的参考来源未能尽数列举,谨在此对那些在网络中无私分享自己知识的人表达我的衷心感谢!培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模型驱动开发领域驱动设计思想的发展2002年MartinFower在其出版企业应用架构模式中,归纳总结了40多种企业应用架构的设计模式。其中所提到的多种设计模式和概念,如事务脚本、活动记录和领域模型等,对业界产生了深远的影响。2004年著名建模专家EricEvans发表了他最具影响力的著名书籍:Domain-Driv
2、enDesignTacklingComplexityintheHeartofSoftware(中文译名:领域驱动设计软件核心复杂性应对之道),书中提出了“领域驱动设计(简称DDD)”的概念。2010年GregYoung在“CQRS,TaskBasedUIs,EventSourcingagh!”一文中对BetrandMeyer的CQS模式进行改造,提出CQRS模式。此后JimmyNilsson的ApplyingDomain-DrivenDesignandPatterns、AbelAvram和FloydMarinescu合作的Domain-DrivenDesignQuickly、DanHaywoo
3、d的Domain-DrivenDesignUsingNakedObjects、以及VaughnVernon的ImplementingDomain-DrivenDesign等书籍的出版,丰富了领域驱动设计的实践和指导。领域驱动设计是什么领域驱动设计事实上针对是OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术框架进行了分层规划,同时对每个类进行了策略和类型的划分。nItsasetofprovenmodelingtechniquesespeciallytargetedtocomplexapplications.nItsasetofprinciplesandpracticessupp
4、ortingthedevelopmentprocess.nItsasetofpatternsthatsupportacleanandcoherentviewofthedomainmodel.nItsasetofpragmaticstrategiesallowingapplicationstoscaleinsizeandcomplexitymaintainingtheirintegrity.领域驱动设计的特性成熟、清晰的分层架构领域对象与现实世界的业务映射明确的职责划分分层架构领域对象是核心领域对象复用:完整的业务对象描述设计复用:设计基于领域对象而非数据库复用具备复杂业务逻辑的软件开发对设计和
5、开发人员要求较高不适用普通CRUD的业务软件的维护性和扩展性良好(Testable)使用场景领域驱动设计分层规划(一)领域驱动设计分层规划用户界面用户界面/展现层展现层负责向用户展现信息以及解释用户命令。展示层的组件实现用户与应用交互的功能。一般建议用MVC,MVP或者MVVM模式来分隔这些组件为子层应用层应用层很薄的一层,用来协调应用的活动,实现协调应用的“通道”,例如事务、执行单位操作、调用应用程序的任务。它不包含业务逻辑。它不保留业务对象的状态,但它保有应用任务的进度状态。类似于Faade模式,调用领域层和基础设施层来完成应用的用例。领域层领域层本层包含关于领域的信息。这是业务软件的核心
6、所在。在这里保留业务对象的状态,对业务对象和它们状态的持久化被委托给了基础设施层。基础设施基础设施层层本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用。领域驱动设计分层规划(二)领域驱动设计是对传统N层架构模式的继承和发展领域驱动设计分层规划(三)领域驱动设计是对传统N层架构模式的继承和发展CoreJ2EEPatterns例:J2EE参考分层架构传统J2EE或Spring+Hibernate等事务性编程模型只关心数据,这些数据对象除了简单sette/getter方法外,没有任何业务方法,被比喻成“失血模型”。领域驱动设计分层规划(四)分布式
7、领域驱动设计领域驱动设计分层规划(五)分布式领域驱动设计与DotNET技术架构体系之间的关系映射面向对象分析与设计技术面向过程vs.面向对象事务脚本模式把业务逻辑组织成单个过程,在过程中直接调用数据库,业务逻辑在服务(Service)层处理。事务脚本模式的特点是简单容易理解,面向过程设计。对于少量逻辑的业务应用来说,事务脚本模式简单自然,性能良好,容易理解,而且一个事务的处理不会影响其他事务。不过缺点也很明显,对于复杂的业务逻辑处理力不从心,难以保持良好的设计,事务之间的冗余代码不断增多,通过复制粘贴方式进行复用。可维护性和扩展性变差。对类的策略和类型的划分对类进行StereoType(“构造
8、型”)划分的好处在于:(1)指导设计(2)帮助命令对象(3)辅助理解按照策略和类型对类进行划分六边形架构以领域模型为核心的六边形架构领域驱动设计中的设计模式有助于获得柔性设计的设计模式每个元素的名称都提供了一次揭示设计意图的机会。站在客户开发人员的角度上来思考它。人们为了使所有类和操作都具有相似的规模而寻找一种一致的力度。粒度的大小并不是唯一要考虑的问题,我们还要考虑粒度在哪种场合下使用。随着代码重构不断适合新理解的概念或需求,概念轮廓也就逐渐形成了。搞内聚低耦合原则既适用于代码,也适用于概念。领域驱动设计软件核心复杂性应对之道第10章任何对未来操作产生影响的系统状态的改变都可以成为副作用。把
9、命令和查询严格地放到不同操作中;创建并返回ValueObject。允许我们安全地对多个操作进行组合。使用断言把副作用明确表示出来,使它们更易于处理。寻找在概念上内聚的模型,更易推出预期ASSERTION,从而加快学习过程并避免代码矛盾。尽一切可能保持低耦合。把所有无关概念提取到对象之外,类就变成完全孤立的了,使得我们可以单独地研究和理解它。每个孤立类都极大减轻了因理解Module而带来的负担。操作闭合:在适当的情况下,在定义操作时让它的返回类型与其参数相同。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖性。培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程
10、实践CQRS架构模型驱动开发使用通用语言的重要性Talkingdifferentlanguagesmakesprojectsfail.nProgrammersspeakusingtechnicaljargon(designpatterns,acronyms,geekyin-jokes)nDomainexpertsuseterminologyspecifictotheirfieldofexpertisenComputersspeakprogramminglanguagesn大家必须妥协领域驱动设计的关键点关注核心领域(Core Domain)领域专家和软件从业者共同开发模型在一个明确的限界上下文
11、(Bounded Context)中使用领域通用语言(ubiquitous language)通用语言(一)通用语言(UBIQUITOUSLANGUAGE)是团队共享的语言。领域专家和开发者使用相同的通用语言进行交流。事实上,团队中每个人都使用相同的通用语言。不管你在团队中的角色如何,只要你是团队的一员,你都将使用通用语言。n通用语言是团队自己创建的公用语言。团队中同时包含领域专家和软件开发人员。n通用语言更多地是关于业务本身如何思考和运作的,领域专家对通用语言有很大影响。不同领域专家会在概念和术语上产生分歧,甚至也会犯错,当领域专家和开发者一起创建领域模型的时候,他们有时会达成一致,有时会做
12、一些妥协,但最终目的都是为了创造最适合项目的通用语言。团队成员们妥协的绝对不应是通用语言的质量,而是概念、术语和含义。最初的一致并不表示始终一致,通用语言也会随着时间推移而不断演化改变。n领域驱动设计的一个核心思想就是使用基于模型的共同语言。因为模型是软件满足领域的共同点,它很适合作为这种通用语言的构造基础。使用模型作为语言的核心骨架,要求团队在进行所有的交流都是使用一致的语言,在代码中也是这样。在共享知识和推敲模型时,团队会使用语言、文字和图形。这儿需要确保团队使用的语言在所有的交流形式中看上去都是一致的,这种语言被称为“通用语言(UbiquitousLanguage)”。n通用语言的词汇表
13、包括类名称和主要操作。语言中包含术语,有些术语用来讨论模型中已经明确的规则,还有一些术语则来自施加于模型上的高级组织原则。最后,团队一致应用于领域模型的模式名称使这种语言更为丰富。模型之间的关系成为所有语言都具有的组合规则,词和短语的意义反映了模型的语义。通用语言(二)在应用通用语言时,应注意:n将模型作为语言的中心。确保团队在所有交流活动和代码中坚持使用这种语言。在画图、写东西特别是讲话时也要使用这种语言。n通过尝试不同的表示方法(它们反映了不同模型)来消除难点。然后重构代码,并对类、方法和模块重新命名,以便与新模型相一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一个公认的理解一样。
14、n要认识到UBIQUITOUSLANGUAGE中的更改就是对模型的更改。n领域专家应该避免使用拗口或无法表达领域理解的术语或结构,开发人员应该密切监视那些将会妨碍设计的有歧义和不一致的地方有了通用语言,模型就不仅仅是一个设计工作了。它成为开发人员和领域专家共同完成的每项工作中的不可或缺的部分。语言以动态形式传递知识。使用这种语言进行讨论能够更清楚地表达图和代码背后的真实含义。通用语言是那些不以代码形式出现的设计方面的主要载体,这些方面包括把整个系统组织在一起的比例结构、定义了不同系统和模型之间关系的BoundedContext,以及在模型和设计中使用的其他模式。通用语言的应用通用语言贯穿于项目
15、的各个环节nUserStoriesnProjectMeetingsnTeamEmailsnInstantMessagesnSchedulePlannSoftwareDocuments在限界上下文中,保持语言的一致性(如口语、图形(如UML图等)、文字、代码等)。通用语言的应用示例(一)UserStoriesNOWhenUserlogsonwithvalidcredentials,anemptypanelisdisplayed.YESWhenPlayerlogsonwithvalidcredentials,anemptyboardgameisdisplayed.(fromaTicTacToeGa
16、mesoftwareexample)通用语言的应用示例(二)CodeExampleNO.Integeri=newInteger();.Stringchar1=newString();.publicclassGameDAO().catch(Exceptione)YES.StringrealMeaningOfMyString=newString();.publicclassScoreDataLoader().catch(ExceptionNotLoggedInException)NO.Ambiguities.Inconsistencies.Synonyms.AbbreviationsYES.Cla
17、rity.Precision.Reuse.FullNamespackagetictactoe.client.userInterface;/*AddthestringOorXtoacellinthegrid.*/publicclassShowCellGridpublicstaticvoiddisplayUser(Gridgrid,Cellcell)if(!Initialization.flag&Initialization.gameStatus.getSequence() = null&isEmpty(grid, cell) Initialization.flag = true;Stringmk
18、=showString(Initialization.gameStatus.getCurrentUser().getUserString();grid.setHTML(cell.getRowIndex(),cell.getCellIndex(),mk);Initialization.gameStatus.getStatus()cell.getRowIndex()cell.getCellIndex()=Initialization.gameStatus.getCurrentUser();GameEnd.checkEnd(Initialization.gameStatus,cell.getRowI
19、ndex(),cell.getCellIndex();(.)AclassBEFOREandAFTERUbiquitousLanguagepackagetictactoe.client.userInterface;/*Performsamoveinthegame.*/publicclassPlayerMove/*Whentheplayerclicksinacell,thegamedrawsanOoraXonthe*gamegriddependingonwhichplayersturnitis.*/publicstaticvoidmakeMove(GameGridgameGrid,Cellcell
20、)if(!GameInitialization.waitingMoveFlag&GameInitialization.currentGameStatus.getSequenceWinner() = null &isCellEmpty(gameGrid, cell) GameInitialization.waitingMoveFlag = true;Stringmarker=showPlayerIcon(GameInitialization.currentGameStatus.getCurrentPlayer().getPlayerIcon();gameGrid.setHTML(cell.get
21、RowIndex(),cell.getCellIndex(),marker);GameInitialization.currentGameStatus.getGameMoves()cell.getRowIndex()cell.getCellIndex()=GameInitialization.currentGameStatus.getCurrentPlayer();CheckWinner.checkForWinner(GameInitialization.currentGameStatus, cell.getRowIndex(),cell.getCellIndex();(.)(Excerpte
22、dfromaTicTacToeGamesourcecode)WhichonewouldaStakeholderbetterunderstand?PlayerMovePerformsamoveinthegame.MakeMoveWhentheplayerclicksinacell,thegamedrawsanOoraXonthegamegriddependingonwhichplayersturnitis.IsCellEmptyThePlayercanselectacellonlyifitwasntalreadyselected.ShowCellGridAddtheStringOorXtoace
23、llinthegrid.DisplayUserIsEmpty(ExcerptedfromaTicTacToeGamesourcecode)模型的统一模型的内部一致性又叫做“统一”,这样每个术语都不会有模棱两可的意义,也不会有规则冲突。除非模型在逻辑上是一致的,否则它就没有意义。识别限界上下文中的不一致:重复的概念和假同源n重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生改变时,都必须要更新两个地方。每次由于新的知识导致一个对象被修改时,也必须重新分析和修改另一个对象。如果不进行实际的重新分析,结果就会出现同一个概念的两个版本,它们遵守不同的规则,甚至不同
24、的数据。更重要的是,团队成员必须学习同一操作的两种方法,以及保持这两种方法同步的各种方式。n假同源是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。但是,当两个定义都与同一个领域方面相关,而只是在概念上稍有区别时,这种冲突更难以发现。假同源会导致开发团队互相干扰对方的代码,也可能导致数据库中含有奇怪的矛盾,还会引起团队沟通的混淆。注意用词词汇n注意正确用词,不要歪曲词义n开发人员经常习惯于使用增/删/改/查(CRUD)此类动词词汇,也许有时候它们也确实属于通用语言,但大多数情况下,它们并不能正确反映业务,用词上混淆了业务概念。模型的分裂在理想的世界中,我
25、们可以有一种把整个企业领域包含进来的单一模型;这个模型将是统一的,没有任何相互矛盾或相互重叠的术语定义;每个有关领域的逻辑声明都将是一致的。但大型系统开发并不是这样理想。大型系统领域模型的完全统一是不可行的,也不是一种经济有效的做法。我们可以采用限界上下文(BoundedContext)定义每个模型的应用范围,采用上下文映射(ContextMap)给出项目上下文以及它们之间关系的总体视图。n任何一个大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。n明确地定义模
26、型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。在Context中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他Context中,会使用其他的模型,这些模型具有不同的术语、概念、规则和UBIQUITOUSLANGUAGE的技术行话。n定义BoundedContext:视察项目的现状,而不是它的理想状态。领域、子域和限界上下文核心域、支撑域和通用域ACore Domain isapartofthebusinessDomainthatisof
27、primaryimportancetothesuccessoftheorganization.Itisofutmostimportancetotheongoingsuccessofthebusiness.Ifadomainmodelssomeaspectofthebusinessthatisessential,yetnotCore,itisaSupporting Subdomain.ifadomaincapturesnothingspecialtothebusiness,yetisrequiredfortheoverallbusinesssolution,itisaGeneric Subdom
28、ain.Focusonthecoredomain战术建模与战略建模领域驱动设计的综合应用共享内核(Shared Kernel)当不同团队开发一些紧密相关的应用程序时,如果团队之间不进行协调,即使短时间内能够取得快速进展,他们开发出的产品也可能互相不适合,最后可能不得不在转换层上花费大量时间,而且得到的产品也五花八门。n从领域模型中选出两个团队都同意共享的一个子集。当然,除了模型的这个子集以外,这还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的状态,而且一个团队在没与另一个团队商量的情况下不应擅自更改它。n功能系统要经常进行集成,但集成的频率应该比团队中Con
29、tinuousIntegration的频率低一些。在进行这些集成的时候,两个团队都要运行测试。nSharedKernel通常是CoreDomain,或是一组GenericSubdomain(通用子领域),也可能二者兼有。企业架构方法与领域驱动设计3.架构架构内容内容框架框架 4.企业连续系列企业连续系列1.架构开发架构开发方法方法2.架构开发指引和技术架构开发指引和技术5.参考模型参考模型6.架构架构能力能力框架框架 两者都强调Business和IT的高度统一,很多企业架构方法对于领域驱动设计“战略设计”的具体实施办法具有详实的指导意义。如TOGAFV9构件:eTOM业务建模Level0Pro
30、cessesLevel1ProcessesLevel2Processes业务流程解耦/分解eTOM业务建模BSS业务流程框架领域解决特定问题领域解决特定问题eTOM信息数据模型eTOM0级视图SID1级视图ABE:AggregateBusinessEntity,ABE是SID中一组定义良好的实体,具有高内聚、低耦合的特征。共享内核共享内核eTOM信息数据模型参考读物领域驱动设计软件核心复杂性应对之道及实现领域驱动设计中的相关章节软件方法-业务建模和需求第三章“业务建模”中的相关内容参考模型范例:nTMForum的eTOM模型:http:/培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块
31、领域驱动设计编程实践CQRS架构模型驱动开发领域驱动设计的构造块Entity(实体)实体是一个具有唯一身份标识的对象,并且可以在相当长的一段时间内持续地变化。我们可以对实体做多次修改,故一个实体对象可能和它先前的对象大不相同,但是由于它们拥有相同的身份标识(identity),它们依然是同一个实体。我们通过标识对对象进行区分,而不是属性,此时我们应该将标识作为主要的模型定义。同时我们需要保持简单的类定义,并且关注对象在其生命周期中的连续性和唯一标识性。随着对象的改变,我们可能会跟踪这样的改变,比如什么时候发生了改变,发生了什么改变,是谁做出的改变等。我们应该慎重对待在对象整个生命周期中所发生的
32、合法改变。唯一的身份标识和可变性(mutability)特征将实体对象和值对象(ValueObjects)区分开来。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(领域事件)n有时候应用需要记录跟踪事情的发生n领域事件经常被
36、建模为ValueObject,但这些ValueObject并不能被共享,因为领域事件本身是“唯一”的。n一个领域事件是指一个在领域中“有意义有意义”的事件HintsnUML四色原型中有一个相近概念,称为时刻-时段原型(Moment-interval),即表示事物在某个时刻或某一段时间内发生。参考:四色原型四色原型是诞生于90年代,现在被广泛使用的一种系统分析方法,如Borland的Together架构师版,准确地说,是由PeterCoad和MarkMayfield首先提出,然后由DavidNorth拓展。Repositories(资源库/仓储)客户需要以一种符合实际的方式来获取对以存在的领域对
37、象的引用。为每种需要全局访问的对象类型创建一个对象,这个对象就相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体标准来挑选对象的方法,并返回属性值满足查询标准的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的聚合提供Repository。让客户始终聚焦于模型,而将所有对象的存储和访问操作交给Repository来完成。Repository的接口应当采用领域通用语言。作为客户端,不应当知道数据库实现的细节。Re
38、pository和DAO的作用类似,二者的主要区别:nDAO是比Repository更低的一层,包含了如何从数据库中提取数据的代码。nRepository以“领域”为中心,所描述的是“领域语言”。Repository把ORM框架与领域模型隔离,对外隐藏封装了数据访问机制。Repositories(资源库/仓储)示例publicinterfaceAccountRepositoryAccountfindAccount(StringaccountId);voidaddAccount(Accountaccount);publicclassHibernateAccountRepositoryimplem
39、entsAccountRepositoryprivateHibernateTemplatehibernateTemplate;publicHibernateAccountRepository(HibernateTemplatetemplate)hibernateTemplate=template;publicvoidaddAccount(Accountaccount)hibernateTemplate.save(account);publicAccountfindAccount(finalStringaccountId)return(Account)DataAccessUtils.unique
40、Result(hibernateTemplate.findByNamedQueryAndNamedParam(“Account.findAccountByAccountId”,“accountId”,accountId);Services(领域服务)当领域中的某个操作过程或转换过程不是实体或值对象的职责时,我们便应该将该操作放在一个单独的接口中,即领域服务。如果勉强地把这些重要的领域功能归为Entity或ValueObject的职责,那么不是歪曲了基于模型的对象的定义,就是人为地增加了一些无意义的对象。应确保领域服务和通用语言是一致的,并且保证它是无状态的。正确区分领域服务(DomainSer
41、vice)和应用服务(ApplicationService):n我们不应把业务逻辑置于应用服务,但我们会把业务逻辑置于领域服务中。(应用)服务要做“薄”。n领域服务职责:跨聚合实例业务逻辑;没办法合理放到实体中的其它业务逻辑。n应用服务职责:跨限界上下文的业务逻辑;DTO转换;事务AOP、权限AOP、日志AOP、异常AOP;外部系统访问(邮件、消息队列)。n领域服务设计原则:用来组织业务逻辑,面向业务逻辑;细粒度;内部视图看系统;一个请求对应多个服务的多个方法;服务之间会存在依赖;n应用服务设计原则:用来封装业务逻辑;面向用例;粗粒度;外部视图看系统;一个请求对应一个方法;服务之间互不依赖。n
42、应用服务和领域服务区分非常敏感,有时候需要在快速性/方便性上做折衷。Services(领域服务)示例publicinterfaceMoneyTransferServiceBankingTransactiontransfer(StringfromAccountId,StringtoAccountId,doubleamount);publicclassMoneyTransferServiceImplimplementsMoneyTransferServiceprivatefinalAccountRepositoryaccountRepository;privatefinalBankingTrans
43、actionRepositorybankingTransactionRepository;publicMoneyTransferServiceImpl(AccountRepositoryaccountRepository,BankingTransactionRepositorybankingTransactionRepository)BankingTransactiontransfer(StringfromAccountId,StringtoAccountId,doubleamount)应用服务、领域服务和基础设施服务Factories(工厂)当创建一个对象或创建整个聚合时,如果创建工作很复杂
44、,或者暴露了过多的内部结构,则可以使用Factory进行封装。应该将创建复杂对象的实例和聚合的职责转移到一个单独的对象,这个对象本身在领域模型中可能没有职责,但它仍是领域设计的一部分。不同类型的工厂模式:n工厂类n工厂方法Modules(模块)Module为人们提供了两种观察模型的方式,一是可以在Module中查看细节,而不会被整个模型淹没,二是观察Module之间的关系,而不考虑其内部细节。模块之间应该是低耦合的,而在模块内部则是高内聚的。模块并不仅仅是代码的划分,而且也是概念的划分。一个人一次考虑的事情是有限的(因此才有低耦合);不连贯的思想和“一锅粥”似的思想同样难于理解(因此才有高内聚
45、)。选择能够描述系统的Module,并使之包含一个内聚的概念集合。这通常会实现Module之间的低耦合,但如果效果不理想,则应寻找一种更改模型的方式来消除概念之间的耦合,或者找到一个可作为Module基础的概念,基于这个概念组织的模型可以以一种有意义的方式将元素集中到一起。找到一种低耦合的概念组织方式,从而可以相互独立地理解和分析这些概念。对模型进行精化,直到可以根据高层领域概念对模型进行划分,同时相应的代码也不会产生耦合。Module的名称应该是领域通用语言中的术语。模块及其名称应反映出领域的深层知识。培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模
46、型驱动开发概念辨析-VO/DTO/DO/PO(一)ViewObject(视图对象):视图对象,用于展示层,其作用是把某个指定页面(或组件)的所有数据封装起来。DataTransferObject(数据传输对象):这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。DomainObject(领域对象):从现实世界中抽象出来的有形或无形的业务实体、值对象或领域服务。PersistentObject(持久化对象):跟持久层(通常是关系型数据库)的
47、数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。Ref:http:/ 的子类privateStringengine;/发动机Column(nullable=true,length=30)publicStringgetEngine()returnengine;publicvoidsetEngine(Stringengine)this.engine=engine;SuppressWarnings(serial)EntityTable(name=Camion)PrimaryKeyJoinColumn(name=Camio
48、nID)/把主键对应的列名更改为CamionIDpublicclassCamionextendsCar/Car的子类privateStringcontainer;Column(nullable=true,length=30)publicStringgetContainer()returncontainer;publicvoidsetContainer(Stringcontainer)this.container=container;Entity(五)继承关系:TableperClassn一旦使用这种策略,意味着你不能使用AUTOgenerator和IDENTITYgenerator,即主键值不
49、能采用数据库自动生成。SuppressWarnings(serial)Entity/或MappedSuperclassInheritance(strategy=InheritanceType.TABLE_PER_CLASS)Table(name=“Vehicle”)/当为MappedSuperclass时,不要Table标注publicclassVehicleimplementsSerializable/基类privateLongid;privateShortspeed;/速度IdColumn(columnDefinition=integer)publicLonggetId()returnid
50、;publicvoidsetId(Longid)this.id=id;publicShortgetSpeed()returnspeed;publicvoidsetSpeed(Shortspeed)this.speed=speed;SuppressWarnings(serial)EntityTable(name=Car)publicclassCarextendsVehicle/Vehicle 的子类privateStringengine;/发动机Column(nullable=true,length=30)publicStringgetEngine()returnengine;publicvoi
51、dsetEngine(Stringengine)this.engine=engine;SuppressWarnings(serial)EntityTable(name=Camion)publicclassCamionextendsCar/Car的子类privateStringcontainer;/集装箱Column(nullable=true,length=30)publicStringgetContainer()returncontainer;publicvoidsetContainer(Stringcontainer)this.container=container;Entity(六)审计
52、(Audit):最近一次修改时间,最近一次修改人。EntityEntityListeners(JodaAuditListener.class)publicclassCargoextendsAbstractDomainObjectimplementsJodaAuditable,IdentifiablepublicinterfaceJodaAuditablepublicvoidsetCreatedBy(StringcreatedBy);publicStringgetCreatedBy();publicvoidsetCreatedDate(DateTimecreatedDate);publicDat
53、eTimegetCreatedDate();publicvoidsetLastUpdatedBy(StringupdatedBy);publicStringgetLastUpdatedBy();publicvoidsetLastUpdated(DateTimeupdateDate);publicDateTimegetLastUpdated();publicclassJodaAuditListenerPreUpdatePrePersistprivatevoidchangeAuditInformation(JodaAuditableauditableEntity)DateTimelastUpdat
54、ed=newDateTime();auditableEntity.setLastUpdated(lastUpdated);StringlastUpdatedBy=ServiceContextStore.getCurrentUser();auditableEntity.setLastUpdatedBy(lastUpdatedBy);if(auditableEntity.getCreatedDate()=null)auditableEntity.setCreatedDate(lastUpdated);if(auditableEntity.getCreatedBy()=null)auditableE
55、ntity.setCreatedBy(lastUpdatedBy);Entity(七)审计(Audit):使用事件记录实体状态变更事件,操作人以及状态改变内容等。Service(bettingService)publicclassBettingServiceImplimplementsBettingServiceprivatestaticfinalLoggerLOG=LoggerFactory.getLogger(BettingServiceImpl.class);Publish(eventType=BettingInstruction.class,topic=bettingInstructi
56、onTopic,eventBus=commandBus)publicvoidplaceBet(Betbet)LOG.info(#Placingbet:,bet);/dosomeinitialvalidation./newBettingInstructionwillbepublishedSubscribe(topic=bettingInstructionTopic,eventBus=commandBus)publicclassBettingEngineImplimplementsBettingEnginepublicvoidreceive(Eventevent)DynamicMethodDisp
57、atcher.dispatch(this,event,handle);publicvoidhandle(BettingInstructionbetInstruction)LOG.info(#Handlingbet:,betInstruction);instructionRepository.save(betInstruction);Entity(八)并发冲突:乐观锁importjavax.persistence.Version;EntityEntityListeners(JodaAuditListener.class)publicclassCargoextendsAbstractDomainO
58、bjectimplementsJodaAuditable,IdentifiableVersionColumn(name=VERSION,nullable=false)privateLongversion;Value Object(一)值对象可以与其所在的实体对象保存在同一张表中,值对象的每一个属性保存为一列;值对象也可以独立于其所在的实体对象保存在另一张表中,值对象获得委派主键,该主键对客户端是不可见的。Value Object(二)值对象可以与其所在的实体对象保存在同一张表中:EntityEntityListeners(JodaAuditListener.class)publicclassC
59、argoextendsAbstractDomainObjectimplementsJodaAuditable,IdentifiableEmbeddedAttributeOverrides(AttributeOverride(name=identifier,column=Column(name=TRACKINGID,nullable=false,length=100)NotNullprivateTrackingIdtrackingId;publicTrackingIdgetTrackingId()returntrackingId;EmbeddablepublicclassTrackingIdex
60、tendsAbstractDomainObjectprivatestaticfinallongserialVersionUID=1L;Column(name=,nullable=false,length=100,unique=true)NotNullprivateStringidentifier;protectedTrackingId()publicTrackingId(Stringidentifier)super();Validate.notNull(identifier,TrackingId.identifiermustnotbenull);this.identifier=identifi
61、er;Value Object(三)使用数据库实体保存值对象EntityEntityListeners(JodaAuditListener.class)publicclassCargoextendsAbstractDomainObjectimplementsJodaAuditable,IdentifiableOneToOne(mappedBy=cargo,cascade=CascadeType.ALL,fetch=FetchType.EAGER)privateItineraryitinerary;Entity(name=Itinerary)Table(name=ITINERARY)public
62、classItineraryprivatestaticfinallongserialVersionUID=1L;staticfinalItineraryEMPTY_ITINERARY=newItinerary();publicItinerary(finalListlegs)Validate.notEmpty(legs);Validate.noNullElements(legs);super.getLegs().addAll(legs);Itinerary()OverridepublicListgetLegs()returnCollections.unmodifiableList(super.g
63、etLegs();/*Testifthegivenhandlingeventisexpectedwhenexecutingthis*itinerary.*paramevent*Eventtotest.*returntrueiftheeventisexpected*/publicbooleanisExpected(finalHandlingEventevent).注意区分数据库实体和领域实体两个概念的不同存储-对象关联关系(一)对象关联关系:应用担负了维护对象关联关系的职责存储-对象关联关系(二)维护对象关联关系示例:EntitypublicclassEmployeeManyToOne(fetc
64、h=LAZY)privateDepartmentdept;EntitypublicclassDepartmentOneToMany(mappedBy=“dept”,fetch=LAZY)privateCollectionemps=new;publicintaddNewEmployee()Employeee=newEmployee(.);Departmentd=departmentRepository.loadDepartment();e.setDepartment(d);/Reverserelationshipisnotsetem.persist(e);em.persist(d);return
65、d.getEmployees().size();publicintaddNewEmployee()Employeee=newEmployee(.);Departmentd=departmentRepository.loadDepartment();e.setDepartment(d);d.getEmployees().add(e);em.persist(e);em.persist(d);returnd.getEmployees().size();存储-对象关联关系(三)对象关联关系映射策略:n单向一对一是关联关系映射中最简单的一种,简单地或就是可以从关联的一方去查询另一方,却不能反向查询。单向
66、一对一关系的拥有端单向一对一关系的拥有端EntitypublicclassPersonimplementsSerializableprivatestaticfinallongserialVersionUID=1L;IdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateintage;OneToOneprivateAddressaddress;/Getters&Setters单向单向一对一关系的反端一对一关系的反端EntitypublicclassAddressimplementsS
67、erializableprivatestaticfinallongserialVersionUID=1L;IdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringstreet;privateStringcity;privateStringcountry;/Gettes&Setters单向的一对一关系在数据库中是以外键的形式被映射的。其中关系的发出端存储一个指向关系的接收端的一个外键,缺省情况下这个外键的字段名称,是以它指向的表的名称加下划线“_”加“ID”组成的,当然我们也可以根据我们的喜好来修改这个字段
68、,修改的办法就是使用JoinColumn这个注解存储-对象关联关系(四)关联关系映射策略:n双向一对一关系:双向一对一关系中的接收端双向一对一关系中的接收端EntitypublicclassAddressimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringstreet;privateStringcity;privateStringcountry;OneToOne(mappedBy=address)privatePersonperson;/Gettes&Set
69、ters双向关系有一方为关系的拥有端,另一方是关系的反端,也就是“Inverse”端。在这里例子中Person拥有这个关系,而Address就是关系的“Inverse”端。Address中我们定义了一个person属性,在这个属性上我们使用了OneToOne注解并且定义了他的“mappedBy”属性,这个在双向关系的“Inverse”端是必需的。存储-对象关联关系(五)对象关系维护的控制:n单向OneToMany关系:单向一对多关系的发出单向一对多关系的发出 端端EntitypublicclassPersonimplementsSerializableprivatestaticfinallon
70、gserialVersionUID=1L;IdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateintage;OneToManyprivateListcellPhones;/GettersandSetters单向一对多关系的接收端单向一对多关系的接收端EntitypublicclassCellPhoneimplementsSerializableprivatestaticfinallongserialVersionUID=1L;IdGeneratedValue(strategy=
71、GenerationType.AUTO)privateLongid;privateStringmanufacture;privateStringcolor;privateLongphoneNo;/GettersandSetters在一对多关联关系映射中,默认是以中间表的方式来映射这种关系的。如在本例中,中间表为person_cellphone,表的名称为关系的拥有端和Inverse端中间用下划线连接。中间表的字两个字段分别为两张表的得表名加下划线“_”加ID组成。当然我们也可以改表这种默认的中间表的映射方式,我们可以在关系的拥有端使用JoinClolum来使用外键的方式映射这个关系。存储-对象
72、关联关系(六)对象关系维护的控制:n双向OneToMany关系:双向一对多关系的接受端双向一对多关系的接受端EntitypublicclassPersonimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateintage;OneToMany(mappedBy=person)privateListcellPhones;/GettersandSetters双向双向一对多关系的发出端一对多关系的发出端EntitypublicclassCell
73、PhoneimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringmanufacture;privateStringcolor;privateLongphoneNo;ManyToOneprivatePersonperson;/GettersandSetters在OneToMany里加入mappedBy属性避免生成中间表。在当前例子中,cellPhones这一端是关系的拥有者,CellPhone一方的表中生成到关联类的外键。存储-对象关联关系(七)对象关系维护的控
74、制:n单向ManyToMany关系:单向多对多关系的发出端单向多对多关系的发出端EntitypublicclassTeacherimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateBooleangender;privateintage;privateintheight;ManyToManyprivateListstudents;/GettersandSetters单向单向多对多关系的反端多对多关系的反端Entitypublicclas
75、sStudentimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateBooleangender;privateintage;privateintheight;/GettersandSetters多对多关联关系中只能通过中间表的方式进行映射。我们使用了ManyToMany这个注解来对Teacher中的Students进行注释,其中Teacher就是关系的发出端。而在Student中我们并没有作任何定义,这是单向多对多的所要求的。存储-对
76、象关联关系(八)对象关系维护的控制:n双向ManyToMany关系:双向多对多关系的拥有端双向多对多关系的拥有端EntitypublicclassTeacherimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateBooleangender;privateintage;privateintheight;ManyToManyprivateListstudents;/GettersandSetters双向双向多对多关系的反端多对多关系的反端
77、EntitypublicclassStudentimplementsSerializableIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;privateStringname;privateBooleangender;privateintage;privateintheight;ManyToMany(mappedBy=students)privateListteachers;/GettersandSetters存储-对象关联关系(九)关联对象加载策略:nEAGERimmediatenLAZYloadedonlywhenne
78、edednLAZY适合对象数量多,或者嵌套层次深的关联对象nJPA中,1:m或m:n关系的默认加载策略是LAZY;n实际应用中,对于大数据库(egBLOB)的属性,以及不经常使用的关联对象,采用LAZYn对于Detached对象,使用LAZYload会报异常(参考DO/DTO区别)EntitypublicclassDepartmentIdprivateintid;OneToMany(mappedBy=“dept”)privateCollectiionemps;存储-对象关联关系(十)Cascade(级联):nCascadeType.PERSIST(级联保存):持久保存拥有方实体时,也会持久保存
79、该实体的所有相关数据nCascadeType.REMOVE(级联删除):删除一个实体时,也会删除该实体的所有相关数据nCascadeType.MERGE(级联更新):将Detached(游离)的实体重新合并到活动的持久性上下文时,也会合并该实体的所有相关数据nCascadeType.REFRESH(级联刷新):假如有一条数据(就有name值为B和sex值为male两个字段),A用户取出来在进行修改操作(修改name为A),正在A修改的过程中(未提交表单),B用户也对这条数据进行修改操作(修改sex为female),B先将性别修改后提交数据库.接着A用户也提交表单,但是,此时在entityMan
80、ager中的持久化实体的性别为male,没有更新为B用户修改成的female,所以此时执行一次Refresh操作,就会将该实体更新为数据库中的最新记录,然后再进行提交.做级联的时候就会将关联的实体的也获取最新的然后再更新,前提是要执行Refresh操作,CasCadeType.Refresh才会生效nCascadeType.ALL(包含以上全部级联操作)n是否应该使用级联应该是个业务问题而不是技术问题在一对多的级联中,如果对象之间是组合关系(UML术语),使用级联;如果是聚合关系(UML术语),不用级联。避免在深层嵌套关系中使用MERGE和ALL级联存储-对象关联关系(十一)Cascade(级
81、联)代码示例:EntityTable(uniqueConstraints=UniqueConstraint(columnNames=CLASSPACKAGE,NAME)publicclassSourceClassIdGeneratedValue(strategy=GenerationType.AUTO)privateLongid;/类的包路径privateStringclassPackage=;/类名称privateStringname=;/类描述Column(name=remark,length=1000)privateStringremark=;/类的字段OneToMany(mappedB
82、y=sourceClass,cascade=CascadeType.ALL)privateCollectionfields=newHashSet();/类的方法集合OneToMany(mappedBy=sourceClass,cascade=CascadeType.ALL)privateCollectionmethods=newHashSet();/存储-对象关联关系(十二)Cascade(级联)代码示例:EntitypublicclassEmployee/类的字段ManyToOne(cascade=CascadeType.PERSIST)privateAddressaddress;/值对象A
83、ddressaddress=newAddress();em.persist(address);employee.setAddress(address);Em.persist(employee);如果不用Cascade,则提交一个有address的Employee对象需要这样EntitypublicclassPlanetextendsAbstractDomainObjectimplementsAuditable,Identifiable/OneToMany(cascade=CascadeType.ALL,orphanRemoval=true,mappedBy=planet,fetch=Fetch
84、Type.EAGER)ForeignKey(name=FK_MOON_PLANET_PLANET,inverseName=FK_MOON_PLANET_MOON)NotNullprivateSetmoons=newHashSet();/另举一个聚合根的例子(Planet是聚合根,Moon是非聚合根):EntityTable(name=MOON)EntityListeners(AuditListener.class)publicclassMoonextendsAbstractDomainObjectimplementsAuditable,Identifiable/Aggregate(一)超大聚合
85、的问题:n并发访问时的数据一致性(consistency)以及事务失败问题(transactionalfailures);n性能和扩展性问题,如一次加载过多数据或内存不足等;Aggregate(二)设计小聚合:n探索业务中真正必须一致(强一致)的规则,依据一致性边界发现聚合;n不要轻易相信所有用例,有时候数据的最终一致是可以接受的;下例中, 我们重新划分了四个聚合,每个聚合都依赖一个公共的值对象ProductId(Product领域实体的标识)。采用领域服务协调publicclassProduct.publicBacklogItemplanBacklogItem(StringaSummary,
86、StringaCategory,BacklogItemTypeaType,StoryPointsaStoryPoints).publicReleasescheduleRelease(StringaName,StringaDescription,DateaBegins,DateanEnds).publicSprintscheduleSprint(StringaName,StringaGoals,DateaBegins,DateanEnds).publicclassProductBacklogItemService.TransactionalpublicvoidplanProductBacklog
87、Item(StringaTenantId,StringaProductId,StringaSummary,StringaCategory,StringaBacklogItemType,StringaStoryPoints)Productproduct=productRepository.productOfId(newTenantId(aTenantId),newProductId(aProductId);BacklogItemplannedBacklogItem=product.planBacklogItem(aSummary,aCategory,BacklogItemType.valueOf
88、(aBacklogItemType),StoryPoints.valueOf(aStoryPoints);backlogItemRepository.add(plannedBacklogItem);.Aggregate(三)设计小聚合:如何解决聚合间相互引用问题n在聚合内引用其他聚合对象一致性边界控制问题:人们可能在一个聚合内,修改其它聚合对象;模型加载与遍历的性能问题publicclassBacklogItemextendsConcurrencySafeEntity.privateProductproduct;Aggregate(四)设计小聚合:如何解决聚合间相互引用问题n在聚合内使用标识(
89、ValueObject)引用其他聚合;publicclassBacklogItemextendsConcurrencySafeEntity.privateProductIdproductId;publicclassProductBacklogItemServiceTransactionalpublicvoidassignTeamMemberToTask(StringaTenantId,StringaBacklogItemId,StringaTaskId,StringaTeamMemberId)BacklogItembacklogItem=backlogItemRepository.backlo
90、gItemOfId(newTenantId(aTenantId),newBacklogItemId(aBacklogItemId);TeamofTeam=teamRepository.teamOfId(backlogItem.tenantId(),backlogItem.teamId();backlogItem.assignTeamMemberToTask(newTeamMemberId(aTeamMemberId),ofTeam,newTaskId(aTaskId);publicclassBacklogItemextendsConcurrencySafeEntitypublicvoidcom
91、mitTo(SprintaSprint)DomainEventPublisher.instance().publish(newBacklogItemCommitted(this.tenantId(),this.backlogItemId(),this.sprintId();领域服务协调聚合间访问采用最终一致方式保证聚合间数据一致性Aggregate(五)应遵循把聚合作为数据一致性边界的原则,允许有以下例外(谨慎使用):n用户交互的便捷性:如批量导入/更新等用户操作行为;n全局事务,两阶段提交事务;n因为查询性能,需要直接在聚合中引用其他聚合对象;n技术框架不支持。Aggregate(六)是否需
92、要进一步划分聚合应衡量哪些因素?n“通用语言”,即业务的一致性规则要求;n衡量聚合的代价:一次加载对象数,内存消耗,以及通用的使用场景(出现频率)Aggregate(七)Aggregate ObjectnSometimestheresponsibilityofthewholeaggregatedoesntreallybelongtotheAggregateRootentity.nSometimespreservingintegrityoftheaggregateasawholeisaresponsibilitythatdeservesaroleofitsownnAnAggregateObjec
93、tmightcoordinateinvariantscheckingandstatemanagementbetweenthedifferententitiesoftheaggregate存储-事务控制(一)事务控制的边界位于服务层(ServiceLayer)publicclassProductBacklogItemServiceTransactionalpublicvoidassignTeamMemberToTask(StringaTenantId,StringaBacklogItemId,StringaTaskId,StringaTeamMemberId)BacklogItembacklog
94、Item=backlogItemRepository.backlogItemOfId(newTenantId(aTenantId),newBacklogItemId(aBacklogItemId);TeamofTeam=teamRepository.teamOfId(backlogItem.tenantId(),backlogItem.teamId();backlogItem.assignTeamMemberToTask(newTeamMemberId(aTeamMemberId),ofTeam,newTaskId(aTaskId);存储-事务控制(二)事务管理方式:nJTA:全局事务nRES
95、OURCE_LOCAL:本地事务JPAconfigurationforDDDSampleorg.hibernate.ejb.HibernatePersistencejava:comp/env/jdbc/applicationDSorg.sculptor.dddsample.cargo.domain.Cargoorg.sculptor.dddsample.carrier.domain.CarrierMovementorg.sculptor.dddsample.carrier.domain.CarrierMovementIdorg.sculptor.dddsample.cargo.domain.H
96、andlingEventorg.sculptor.dddsample.cargo.domain.Itineraryorg.sculptor.dddsample.cargo.domain.Legorg.sculptor.dddsample.location.domain.Locationorg.sculptor.dddsample.routing.domain.RtCarrierMovementorg.sculptor.dddsample.routing.domain.RtLocationorg.sculptor.dddsample.cargo.domain.TrackingIdorg.scul
97、ptor.dddsample.routing.domain.TransitEdgeorg.sculptor.dddsample.routing.domain.TransitPathorg.sculptor.dddsample.location.domain.UnLocodeENABLE_SELECTIVEAUTOpersistence.xml存储-事务控制(三)TransactionPropagation属性值nRequired:在有transaction状态下执行;如果当前没有transaction,则创建新的transaction。这是最常用的属性,也是默认属性。nMandatory:必须
98、在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException。通常在“ClientOrchestrationtransactionstrategy”中使用。nRequiresNew:创建新的transaction并执行,如果当前已有transaction,则将当前transaction挂起。通常在Auditing或Logging等数据操作情形下使用,因为这些操作与整个基础事务的成败没有关系。nSupports:如当前有transaction,则在transaction状态下执行,如果当前没有transact
99、ion,在无transaction状态下执行。主要用于数据库的只读操作,与NotSupported不同,Supports属性在有当前事务时,读取数据可以保持与当前事务的数据保持一致。nNotSupported:在无transactioin状态下执行;如果当前已有transaction,则将当前transaction挂起。通常在有数据库存储过程情形下使用(数据库不支持嵌套事务)。nNever:在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。唯一的使用场景可能就是测试了Ref:http:/ Con
100、text中的中的Entity和和DDD Context中的中的Entity的概念区别!的概念区别!存储-缓存(四)HibernateJPA中配置Ehcache二级缓存示例n(1)JPA的persistence.xml配置存储-缓存(五)HibernateJPA中配置Ehcache二级缓存示例n(2)对ehcache进行简单的配置(ehcache.xml)示例中启用了Ecache非分布式缓存,也可以结合Terracotta启用分布式缓存。存储-缓存(六)HibernateJPA中配置Ehcache二级缓存示例n(3)JPA的Entity类中生命缓存的隔离机制importorg.hibernate
101、.annotations.Cache;importorg.hibernate.annotations.CacheConcurrencyStrategy;Cache(usage=CacheConcurrencyStrategy.READ_WRITE)EntityTable(name=catagory)publicclassCatagoryEntityextendsBaseEntity.Event事件的两大类别:Command和Event两类事件的命名方式:nCommand:主动式,如PlaceOrder/PlaceOrderCmdnEvent:被动式,如OrderPlaced/OrderPlac
102、edEventServiceService间通信方式n本地服务通信n远程服务通信SynchronousHTTPasynchronousAMQPFormats:JSON,XML,ProtocolBuffers,Thrift,.Evenviathedatabase*Asynchronousispreferred*JSONisfashionablebutbinaryformatismoreefficientModule(一)模块在实际语言中的映射n模块在Java语言实现中用包(Package)表示n模块和模块之间不允许存在相互依赖。n一个模块中的服务(Service)不允许直接访问另一个模块的仓储(
103、Repository),它们之间必须通过服务接口进行访问。Module(二)模块设计的原则:低耦合,高内聚Module(三)示例:DDDwithoutmodularityModule(四)模块化数据库Schema设计要求n数据库结构不允许跨领域(模块)n查询不允许跨领域边界好处:l模块可以独立迁移到下一版本l在一个系统中,不同模块可以采用不同存储/数据库技术Module(五)跨边界查询两种方案对比:Module(六)使用ElasticSearch实现跨模块查询视图:测试(一)领域驱动设计软件开发/测试各阶段测试(二)测试的目的与相应方法ApplicationLevelsTestingTypeE
104、xpectationsUI/Presentation/Controls/Services/Classes/Models/CodeMeetsCustomersExpectationsCodeMeetsProgrammersExpectationsUnitTestingBDDSelenium测试(三)行为驱动开发(BehaviorDrivenDevelopment):BDD=TDD+DDDn测试驱动开发(TDD),是一种新型的开发方法,要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简介可用和高质量的代码,并加速开发过程。(Re
105、f:Test-DrivenDevelopmentByExample,KentBeck)n行为驱动开发(BDD):BDD的重点是通过与利益相关者的讨论取得对预期的软件行为的清醒认识。它通过用自然语言书写非程序员可读的测试用例扩展了测试驱动开发方法。行为驱动开发人员使用混合了领域中统一的语言的母语语言来描述他们的代码的目的,使得开发者得以把精力集中在代码如何实现商业价值商业价值上。nTDD和BDD都是Test-FirstDevelopment,都借助自动化测试工具。BDD是对TDD的拓展,把关注点从“测试”本身,转移到“商业价值”上来。测试(四)行为驱动开发:用举例来阐述行为VisionGoalG
106、oalGoalCapabilityCapabilityFeatureFeatureFeatureStoryStoryStoryScenarioScenarioCodeCodeCodeGiven a contextWhen an event happensThen an outcome should occurGiven Fred has bought a microwaveAnd the microwave cost 100When we refund the microwaveThen Fred should be refunded 100.Anexampleofhowthesystemmi
107、ghtbehavefromauserperspectiveStory TemplateAs a User/RoleI want Behaviourso that Ireceivebenefit测试(五)在项目中实施行为驱动开发的步骤1.Foreachscenariodescribingafeature2.Runthescenarioitfails(gored)3.Definethefirststepgored4.Writedowntheapplicationcodegettingthesteptopassgogreen5.Refactorthecodeandrepeatsteps4&5fore
108、achstepuntil66.Thescenariopassesgogreen7.Refactortheapplicationcode*测试驱动开发或者行为驱动开发在做法上的要求是一致的,即在编写代码的过程中,不要急于探索如何进行内部实现的细节或机制,而是首先考虑客户(调用方/使用者)使用软件(应用/模块/类/)的场景。满足客户的需求,不多也不少。测试(六)自动化测试常用工具n单元测试框架:Junit(http:/junit.org/)n行为驱动测试框架:Jbehave(Ref:http:/jbehave.org/)nMock框架:EasyMock(http:/www.easymock.org
109、/)、JMockit(http:/jmockit.github.io/)等n内存数据库:HSQLDB(http:/hsqldb.org/)培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块领域驱动设计编程实践CQRS架构模型驱动开发传统的CRUD方法传统的CRUD方法的问题:n使用同一个对象实体来进行数据库读写可能会太粗糙,大多数情况下,比如编辑的时候可能只需要更新个别字段,但是却需要将整个对象都穿进去,有些字段其实是不需要更新的。在查询的时候在表现层可能只需要个别字段,但是需要查询和返回整个实体对象。n使用同一实体对象对同一数据进行读写操作的时候,可能会遇到资源竞争的情况,经常要处理的
110、锁的问题,在写入数据的时候,需要加锁。读取数据的时候需要判断是否允许脏读。这样使得系统的逻辑性和复杂性增加,并且会对系统吞吐量的增长会产生影响。n同步的,直接与数据库进行交互在大数据量同时访问的情况下可能会影响性能和响应性,并且可能会产生性能瓶颈。n由于同一实体对象都会在读写操作中用到,所以对于安全和权限的管理会变得比较复杂。n这里面很重要的一个问题是,系统系统中的读写频率比中的读写频率比,是偏向读,还是偏向写,就如同一般的数据结构在查找和修改上时间复杂数据结构在查找和修改上时间复杂度度不一样,在设计系统的结构时也需要考虑这样的问题。解决方法就是我们经常用到的对数据库进行读写分离。CQRS简介
111、(一)CQRS由CregYound在CQRS,TaskBasedUIs,EventSourcingagh!这篇文章中提出。“CQRS只是简单的将之前只需要创建一个对象拆分成了两个对象,这种分离是基于方法是执行命令还是执行查询这一原则来定的”。nCQRS使用分离的接口将数据查询操作(Queries)和数据修改操作(Commands)分离开,这也意味着在查询和更新过程中使用的数据模型也是不一样的。这样读和写逻辑就隔离开了。n主数据库处理CUD,从库处理R,从库的的结构可以和主库的结构完全一样,也可以不一样,从库主要用来进行只读的查询操作。在数量上从库的个数也可以根据查询的规模进行扩展,在业务逻辑上
112、,也可以根据专题从主库中划分出不同的从库。CQRS简介(二)从库也可以实现成ReportingDatabase,根据查询的业务需求,从主库中抽取一些必要的数据生成一系列查询报表来存储。使用ReportingDatabase的一些优点通常可以使得查询变得更加简单高效:nReportingDatabase的结构和数据表会针对常用的查询请求进行设计。nReportingDatabase数据库通常会去正规化,存储一些冗余而减少必要的Join等联合查询操作,使得查询简化和高效,一些在主数据库中用不到的数据信息,在ReportingDatabase可以不用存储。n可以对ReportingDatabase重
113、构优化,而不用去改变操作数据库。n对ReportingDatabase数据库的查询不会给操作数据库带来任何压力。n可以针对不同的查询请求建立不同的ReportingDatabase库。CQRS架构CQRS架构示意图:ClientCommandsCommandBusSendsCommandHandlersModifyRepositoriesReadWriteDatastorePublishEventsEventBusCommandServicesEventHandlersEventsReadstoreQueryHandlersQueryResultsQueriesQueryServicesEve
114、ntsDomain事件源事件源(EventSourcing):AggregatestracktheirownDomainEventsandderivestatefromthemOrderCreatedProductAddedProductAddedProductRemovedProductAddedOrderShippedOctober5October6October6October7October7October9事件源实现CQRS(一)FULLCQRSwithEventSourcing:UIDomainEventStoreCommandsChangedataCommandsEventsSQ
115、LDBDocumentDBGraphDBUIDataQueriesAskfordataEventsQueryBuildOursinglesourceoftruth事件源实现CQRS(二)FULLCQRSwithEventSourcing代码风格示例:public class CustomerCommandHandler private Repository customerRepository; public CustomerCommandHandler(Repository customerRepository) this.customerRepository = customerRepos
116、itory; CommandHandler public void handle(UnsignCustomer cmd) Customer customer = repository.load(cmd.getCustomerId(); customer.unsign(); public class Customer private boolean signedUp; public void unsign() if (signedUp) apply(new CustomerUnsignedEvent(); EventHandler private void handle(CustomerUnsi
117、gnedEvent event) signedUp = false; CQRS应用示例(一)SalesProductSKUNamePriceQuantityOrderedInventory Service (SAP)ProductSKUQOHLocationCodePricing ServiceProductSKUUnitPricePromotionalPriceInventoryPricingSalesCustomersNewSKUEventNewSKUEventNewSKUEventOrderAcceptedEventMessageBusWhocoordinatesthesalesproc
118、ess?OnlineOrderingSystemWebShop(CompositeUI)CQRS应用示例(二)应用功能扩展:Sales ServiceOrderAcceptedBilling ServiceShipping Shipping Process ManagerProcess Manager(Saga)(Saga)Shipping ServiceOnlineOrderingSystemMessageBusOrderAcceptedOrderAcceptedCustomerBilledCustomerBilledShipOrderShipOrderCQRS模式的优点分工明确,可以负责不
119、同的部分将业务上的命令和查询的职责分离能够提高系统的性能、可扩展性和安全性。并且在系统的演化中能够保持高度的灵活性,能够防止出现CRUD模式中,对查询或者修改中的某一方进行改动,导致另一方出现问题的情况。逻辑清晰,能够看到系统中的那些行为或者操作导致了系统的状态变化。可以从数据驱动(Data-Driven)转到任务驱动(Task-Driven)以及事件驱动(Event-Driven)。CQRS的应用场景当在业务逻辑层有很多操作需要相同的实体或者对象进行操作的时候。CQRS使得我们可以对读和写定义不同的实体和方法,从而可以减少或者避免对某一方面的更改造成冲突对于一些基于任务的用户交互系统,通常这
120、类系统会引导用户通过一系列复杂的步骤和操作,通常会需要一些复杂的领域模型,并且整个团队已经熟悉领域驱动设计技术。写模型有很多和业务逻辑相关的命令操作的堆,输入验证,业务逻辑验证来保证数据的一致性。读模型没有业务逻辑以及验证堆,仅仅是返回DTO对象为视图模型提供数据。读模型最终和写模型相一致。适用于一些需要对查询性能和写入性能分开进行优化的系统,尤其是读/写比非常高的系统,横向扩展是必须的。比如,在很多系统中读操作的请求时远大于写操作。为适应这种场景,可以考虑将写模型抽离出来单独扩展,而将写模型运行在一个或者少数几个实例上。少量的写模型实例能够减少合并冲突发生的情况适用于一些团队中,一些有经验的
121、开发者可以关注复杂的领域模型,这些用到写操作,而另一些经验较少的开发者可以关注用户界面上的读模型。对于系统在将来会随着时间不段演化,有可能会包含不同版本的模型,或者业务规则经常变化的系统需要和其他系统整合,特别是需要和事件溯源EventSourcing进行整合的系统,这样子系统的临时异常不会影响整个系统的其他部分。CQRS不适用的场景领域模型或者业务逻辑比较简单,这种情况下使用CQRS会把系统搞复杂。对于简单的,CRUD模式的用户界面以及与之相关的数据访问操作已经足够的话,没必要使用CQRS,这些都是一个简单的对数据进行增删改查。不适合在整个系统中到处使用该模式。在整个数据管理场景中的特定模块
122、中CQRS可能比较有用。但是在有些地方使用CQRS会增加系统不必要的复杂性。CQRS与大数据技术CQRS示例:微软.NET框架组件提供的CQRS架构附:CQSBetrandMeyer(Eiffel语言之父,开-闭原则OCP提出者)在ObjectOrientedSoftwareConstruction一书中提到一种命令查询分离(CommandQuerySeparation,CQS)的概念。Separationoffunctionsthatwrite&functionsthatreadFunctionsthatwritearecalledCommandmethodsandmustnot retur
123、n a valueFunctionsthatreadarecalledQuerymethodsandmusthaveno side effectsCQSUIApplicationDomainDataCommandsChangedataUIApplicationDataQueriesAskfordata(nosideeffects)培训内容领域驱动设计简介领域通用语言领域驱动设计的构造块CQRS架构领域驱动设计编程实践模型驱动开发领域驱动设计研发生命周期模型Amodelisarepresentationofthedomainserving a specific purpose.Thereisno
124、“perfect”modelforagivendomain.Agoodmodelistailoredonthegivenpurpose.CopyrightAlbertoBrandolini2008模型的表达Modelandtheunderlyingdesignmustevolveinsync.Code istheultimatewaytoexpressthemodel.Intermediateartifacts,diagramsanddocsserveatemporarygoal.CopyrightAlbertoBrandolini2008DesignModel自动代码生成(一)自动代码生成工
125、具可以大量减少手工代码量,并且可以增强代码的规范性。自动代码生成(二)http:/sculptorgenerator.orgJava领域驱动设计自动化代码生成工具:SculptorGenerator介绍自动代码生成(三)Application Universe basePackage=org.sculptor.example.helloworldModule milkyway Service PlanetService String sayHello(String planetName) throws PlanetNotFoundException;protected findByKey =
126、PlanetRepository.findByKey;PlanetgetPlanet(String planetName) throws PlanetNotFoundException;Entity Planet gapscaffoldString name key;String message;Integer diameter nullable min=1;Integer population nullable min=0;- Set moons opposite planet;Repository PlanetRepository findByKeys;findByKey;save;findAll(PagingParameterpagingParameter);findAll;Entity Moon not aggregateRoot / belongs to Planet AggregateString name key;Integer diameter nullable;- Planet planet opposite moons;DSL建模示例:自动代码生成(四)系统演示系统演示