mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-17 14:43:43 +08:00
fix img
This commit is contained in:
@@ -546,16 +546,16 @@ function hide_canvas() {
|
||||
<li>子功能:功能层次的场景划分,每个子功能都对应于业务功能,体现为领域驱动设计的领域模型对象。</li>
|
||||
</ul>
|
||||
<p>Corkburn 给出了如下案例来表现各个目标层次在一个电子商务系统中所处的位置:</p>
|
||||
<p><img src="assets/c555b960-e0db-11e9-903a-93e3c79f3247" alt="71013275.png" /></p>
|
||||
<p><img src="assets/c555b960-e0db-11e9-903a-93e3c79f3247" alt="png" /></p>
|
||||
<p>位于中间一层的用户目标被 Corkburn 形象地比喻为“海平面”,它是最重要的目标,可以认为是业务需求与系统需求的分界线。只有满足用户目标的场景才体现了业务价值,因此,位于这一层的场景才可以认为是“领域场景”。**在事件风暴中,那些参与者参与的决策命令可以视为一个领域场景。**这并非巧合,因为参与者(Actor)本身来自用例的概念,一个用例只有与参与者存在“使用(use)”关系时,才被认为有业务价值,换言之,才满足了用户目标。</p>
|
||||
<p>在设计阶段,采用面向对象范式实现领域场景时,需要多个扮演不同角色的对象履行各自的职责进行协作。职责同样具有层次,由外自内分为:业务价值、业务功能与业务实现。业务价值体现了<strong>领域场景</strong>要满足的用户目标。为了实现该业务价值,领域场景需要被分解为多个子任务,这些子任务就是支撑业务价值的业务功能。当子任务不可再分时,就对应于业务功能的具体业务实现。不可再分的子任务可称为<strong>原子任务</strong>,位于原子任务之上的子任务则称为<strong>组合任务</strong>。下图体现了职责层次与领域场景层次之间的映射关系:</p>
|
||||
<p><img src="assets/4e22cb30-e0e0-11e9-bf38-c5b6f97c8850" alt="80710848.png" /></p>
|
||||
<p><img src="assets/4e22cb30-e0e0-11e9-bf38-c5b6f97c8850" alt="png" /></p>
|
||||
<p>无论职责的层次,还是领域场景的层次,皆非固定的三层结构,对于业务功能而言,只要还没有到具体业务实现的层次,就可以继续分解,组合任务同样如此。设计者需要把握任务的粒度,对场景进行合理的任务分解是设计的关键。</p>
|
||||
<h3>场景驱动设计</h3>
|
||||
<p>领域场景是各个对象一起表演的舞台,站在这个舞台上,每个对象代表了不同的角色,在不同的层次履行不同的职责。由参与者开启一个初始状态,开始执行具有时序性的连续任务,角色之间采用行为协作来共同满足业务价值,这就是<strong>场景驱动设计(Scenario Driven Design)</strong>。</p>
|
||||
<p>场景驱动设计之得名,盖因为该方法将<strong>领域场景</strong>作为了设计的起点。一方面,这强调了任何设计决策皆不能脱离具体的场景;另一方面,领域场景与领域逻辑有关,这一设计驱动力是与领域驱动设计一脉相承的。</p>
|
||||
<p>场景驱动设计通过结合角色、职责与协作三要素与场景的 <strong>6W 模型</strong>,即描写场景的过程必须包含的 <strong>W</strong>ho、<strong>W</strong>hat、<strong>W</strong>hy、<strong>W</strong>here、<strong>W</strong>hen 与 ho<strong>W</strong> 这六个要素,形成了动静结合、相辅相成的完整设计方法:</p>
|
||||
<p><img src="assets/f1a31fd0-e0db-11e9-bf38-c5b6f97c8850" alt="81447508.png" /></p>
|
||||
<p><img src="assets/f1a31fd0-e0db-11e9-bf38-c5b6f97c8850" alt="png" /></p>
|
||||
<p>如上图所示,场景驱动设计的关键要素为角色、职责与协作。<strong>角色</strong>即对象的角色构造型,参与领域场景活动的主要<strong>角色</strong>包括应用服务、领域服务、聚合与抽象的网关。<strong>职责</strong>的层次与任务分解相对应,而任务分解的层次又与角色构造型相对应。在完成一个领域场景时,不同角色履行不同层次的职责:</p>
|
||||
<ul>
|
||||
<li>应用服务:匹配领域场景,提供满足业务价值的服务接口</li>
|
||||
@@ -566,7 +566,7 @@ function hide_canvas() {
|
||||
<p>在当前领域场景的背景下,各个对象角色履行不同层次和粒度的职责。由于场景是由参与者触发的按照时序排列的一系列连续执行的任务过程,因此可以通过时序图表达它们彼此之间的<strong>协作</strong>方式。把场景与角色、职责、协作结合起来,恰好对应于 6W 模型。以场景作为设计起点,利用任务分解细化场景的业务需求,明确不同层次的职责,并分配给不同角色构造型的对象,结合职责层次通过时序图表现这些对象之间的行为协作。这就是场景驱动设计的全景图。</p>
|
||||
<h3>场景驱动设计的过程</h3>
|
||||
<p>为了简化场景驱动设计,可以将该设计方法固化为一个可按部就班执行的动态设计过程。整个设计过程如下所示:</p>
|
||||
<p><img src="assets/7e25bf90-e0e0-11e9-baf7-f97ccb573723" alt="84131198.png" /></p>
|
||||
<p><img src="assets/7e25bf90-e0e0-11e9-baf7-f97ccb573723" alt="png" /></p>
|
||||
<p>场景驱动设计的过程分为三个步骤:</p>
|
||||
<ol>
|
||||
<li>识别场景:从需求中识别出独立的具有业务价值的领域场景</li>
|
||||
@@ -578,9 +578,9 @@ function hide_canvas() {
|
||||
<p>我们在利用事件风暴识别业务全景时,会判断事件之起因,由此确定事件的参与者:用户角色、策略和外部系统。除外部系统发布的事件,其余事件皆由决策命令触发,故而事件的参与者实质就是决策命令的参与者。若决策命令没有参与者,则说明它对应的事件是前置事件的直接结果,不由外部参与者触发。例如支付完成事件(PaymentProcessed)导致订单完成事件(OrderCompleted)。这时的“订单完成事件”就没有参与者,对应的决策命令“完成订单”自然也没有参与者了。既然有参与者的决策命令可以视为一个领域场景,那么,没有参与者的决策命令就应属于该领域场景下的子任务,属于在一个时序中被连续执行的任务。</p>
|
||||
<p><strong>说明:<strong>这种识别领域场景的方法并非绝对正确,在确定了连续执行的任务时,还要明确这些任务是否都是为了同一个</strong>业务价值</strong>。如果不是,就需要对领域场景做进一步拆分。</p>
|
||||
<p>以信用卡申请开卡为例,为事件识别了参与者,其中有两个事件的参与者为外部系统,有两个事件的参与者为不同的用户角色,还有两个事件没有任何参与者,即下图所示的“卡号已生成”事件与“审批结果已通知”事件,它们都是“开卡申请已审批”事件的直接后果:</p>
|
||||
<p><img src="assets/9523ac70-e0e0-11e9-baf7-f97ccb573723" alt="28133553.png" /></p>
|
||||
<p><img src="assets/9523ac70-e0e0-11e9-baf7-f97ccb573723" alt="png" /></p>
|
||||
<p>事件风暴以事件为驱动力可以推导出对应的领域分析模型。在分析模型中,决策命令的参与者应与事件应保持一致。这时,就可通过参与者为分界线,划定领域场景,如下图所示:</p>
|
||||
<p><img src="assets/a0144590-e0e0-11e9-903a-93e3c79f3247" alt="32124922.png" /></p>
|
||||
<p><img src="assets/a0144590-e0e0-11e9-903a-93e3c79f3247" alt="png" /></p>
|
||||
<p>“提交开卡申请”决策命令和“审批开卡申请”决策命令分别由申请人与审批人参与,意味着这两个命令并非连续执行,应分属两个不同的领域场景。“生成卡号”决策命令与“通知审批结果”决策命令没有任何参与者,因此考虑将它们与“审批开卡申请”决策命令一起放到同一个领域场景中。至于“征信预检已完成”事件和“信用卡制作完毕”事件的参与者皆为外部系统,故而不纳入这两个领域场景。</p>
|
||||
<p>在寻找到领域场景之后,我们需要根据其<strong>业务价值</strong>为领域场景命名。第一个领域场景只有一个决策命令,故而该决策命令就是领域场景的业务价值。第二个领域场景分别执行了审批、生成卡号、通知这三件事情,但从用户目标这一层次来看,其核心价值就是“审批开卡申请”。如果无法为领域场景寻找到合适的体现业务价值的名称,说明识别出来的领域场景可能需要进一步拆分。</p>
|
||||
<h4>分解任务</h4>
|
||||
@@ -608,7 +608,7 @@ function hide_canvas() {
|
||||
<li>如果访问了外部资源,则判断是否访问了数据库,如果是,则由抽象的资源库承担该原则任务,否则交给对应的网关对象。</li>
|
||||
</ul>
|
||||
<p>分配职责的过程是多个对象角色在一定时序下进行协作的过程,因此可考虑引入时序图来可视化彼此间的协作关系。时序图可以直观地体现设计质量,确保对象之间的职责是合理分治的。一些设计坏味道可以很容易在时序图中呈现出来:</p>
|
||||
<p><img src="assets/208a43f0-e0dc-11e9-baf7-f97ccb573723" alt="77751078.png" /></p>
|
||||
<p><img src="assets/208a43f0-e0dc-11e9-baf7-f97ccb573723" alt="png" /></p>
|
||||
<p>如上图所示,时序图呈现的坏味道包括:</p>
|
||||
<ul>
|
||||
<li>红色五角星:表示对于一个领域场景而言,对外提供给参与者的方法应该只有一个。若存在多个红色五角星,说明对外的封装不够彻底,可能违背“最小知识法则”。</li>
|
||||
@@ -619,7 +619,7 @@ function hide_canvas() {
|
||||
</ul>
|
||||
<p>显然,对象之间的协作要点在于“<strong>平衡</strong>”,相比代码而言,时序图可以非常直观地呈现协作关系的平衡度。同时,由于时序图体现了从左到右消息传递的动态过程,这要比静态的领域设计模型更能让设计者发现可能缺失的领域对象。时序图中每个对象的调用时序是非常严谨的,只要消息的传递出现了断层,调用时序就无法继续往下执行,就说明这个协作过程中出现了缺失,启发我们去寻找这个缺失的领域模型对象。这是时序图无与伦比的驱动力。</p>
|
||||
<p>利用 <a href="https://www.zenuml.com/?utm_source=zhangyi">ZenUML</a> 绘图工具,我们还可以非常方便地将调用时序表现为一种伪代码形式的脚本。在对分解的任务分配职责时,直接用 ZenUML 脚本来展现类名与方法签名。在编写脚本时,工具会实时呈现可视化的时序图,通过所见即所得的方式帮助我们发现设计的坏味道。这些伪代码形式的脚本,亦可以作为领域实现模型有价值的参考。编写的 ZenUML 脚本以及对应的时序图如下所示:</p>
|
||||
<p><img src="assets/2fb97120-e0dc-11e9-bf38-c5b6f97c8850" alt="79060264.png" /></p>
|
||||
<p><img src="assets/2fb97120-e0dc-11e9-bf38-c5b6f97c8850" alt="png" /></p>
|
||||
<p>脚本形式的好处在于修改便利,随时可以调整类名与方法签名。脚本的语法接近于 Java 语言,通过大括号可以直观地体现类的层次关系,这种层次关系恰好和任务分解的层次相对应。一旦分解了任务,就可以打开工具,按照场景驱动设计中分配职责的过程,依次为场景、组合任务与原子任务编写脚本。</p>
|
||||
<p>由于应用服务与领域服务都有相对固定的命名形式,事件风暴的领域分析建模过程又帮我们识别出了聚合与读模型,其中,读模型往往会作为各个领域行为的输入参数。于是,在场景驱动设计的方法体系下,我们有效地融合了事件风暴、领域驱动设计、角色构造型与时序图。另外,不要忘了,分解任务的过程同样是测试驱动开发的重要前提,这就使得场景驱动设计还能搭配测试驱动开发,为下一阶段的领域实现建模奠定了良好的基础。</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user