我的场景驱动设计

简介: 我的场景驱动设计

我结合领域驱动设计、事件风暴、DCI模式等方法提出的通过领域场景来驱动设计的一种简明设计方法。


我并非要刻意创造一个方法体系,仅仅是在领域驱动设计的大旗下,发现以“场景”为起点,会有更为系统的设计过程。设计本身会有许多驱动力,场景驱动的方式并没有超出领域驱动的范畴,只是以场景来描述会更准确。


我对场景的定义为:具有业务价值的,由参与者触发的,按照时序排列的一系列连续执行的任务过程。场景的层次与Alistair Corkburn设定的用例层次一致,可以简单分为三个层次:概要目标、用户目标和子功能。


用户目标被Corkburn形象地比喻为“海平面”,它是最重要的目标,可以认为是业务需求与系统需求的分界线。只有满足用户目标的场景才体现了业务价值,因此,位于这一层的场景才可以认为是“领域场景”。准确地说,场景驱动设计其实是领域场景驱动设计,如此才能体现通过业务来驱动设计的事实。


下图体现了场景驱动设计的关键要素:


image.png


如上图所示,场景驱动设计的关键要素为角色、职责与协作。角色即对象的角色构造型,参与领域场景活动的主要角色包括应用服务、领域服务、聚合与抽象的网关。职责的层次与任务分解相对应,而任务分解的层次又与角色构造型相对应。在完成一个领域场景时,不同角色履行不同层次的职责:

  • 应用服务:匹配领域场景,提供满足业务价值的服务接口
  • 领域服务:匹配组合任务,协调多个聚合与网关之间的协作,履行提供业务功能的领域行为
  • 聚合:匹配原子任务,履行自给自足的领域行为,提供具体的业务实现
  • 网关:匹配原子任务,抽象对外部资源的访问,封装具体的技术实现


在当前领域场景的背景下,各个对象角色履行不同层次和粒度的职责。由于场景是由参与者触发的按照时序排列的一系列连续执行的任务过程,因此可以通过时序图表达它们彼此之间的协作方式。把场景与角色、职责、协作结合起来,恰好对应于6W模型。以场景作为设计起点,利用任务分解细化场景的业务需求,明确不同层次的职责,并分配给不同角色构造型的对象,结合职责层次通过时序图表现这些对象之间的行为协作。这就是场景驱动设计的全景图。


为了简化场景驱动设计,可以将该设计方法固化为一个可按部就班执行的动态设计过程。整个设计过程如下所示:


image.png


场景驱动设计的过程分为三个步骤:

  • 识别场景:从需求中识别出独立的具有业务价值的领域场景
  • 分解任务:根据职责的层次对领域场景进行任务分解
  • 分配职责:为领域驱动设计角色构造型分配不同层次的职责


场景驱动设计的这三个步骤糅合了几种方法。它的基础其实事件风暴的成果,即通过事件风暴得到的领域分析模型,其中包含了决策命令、读模型、聚合和事件。每个决策命令都是潜在的领域场景。


分解任务其实最符合设计者思维方式,这其实是一种自顶向下的设计方式,它同时也作为测试驱动开发的前置条件。我根据子任务的粒度,将这些任务分为“组合任务”和“原子任务”。任务的类别划分直接影响到后面的职责分配。


分配职责的基础是角色构造型。下图是我总结的主要角色构造型:


image.png


在场景驱动设计中,发挥重要的角色构造型包括:应用服务、领域服务、聚合和网关。它们与场景及任务存在以下对应关系:

  • 应用服务:场景,体现业务价值
  • 领域服务:组合任务,封装多个领域对象之间的协作
  • 聚合:代表领域行为的原子任务
  • 网关:访问外部资源的原子任务


分配职责时,可以借用时序图来表达多个对象角色之间的协作关系。


可以看出,分解任务是场景驱动设计中的关键。只要任务分解合理了,按照我固化的设计流程进行职责分配是水到渠成的过程。我们还可以借助一些工具来显化职责分配与对象协作。推荐我的朋友肖鹏的时序图工具ZenUml,该工具提供了程序员最容易理解和接受的伪代码形式绘制时序图。关键在于这种伪代码将任务与职责完美地融合起来了。


例如,针对信用卡开卡的领域场景,分解的任务如下所示:

  • 审核申请
  • 获得征信信息
  • 验证申请信息
  • 审核
  • 生成卡号
  • 通知申请人
  • 根据模板生成通知内容
  • 获取模板
  • 根据申请信息和模板生成通知内容
  • 发送短信


根据这些任务,将场景对应应用服务,然后将组合任务和原子任务分配给对应的角色构造型,就可以编写如下伪代码:


ApplicationAppService.approve() {
    ApprovingApplicationService.execute() {
       CreditInvestigationGatewary.retriveCredit();
      Application.validate();
      Application.approve();
      ApplicationRepository.save(application);
   }
   CreditCard.generateCardNo();
   NotifingService.execute() {
      GeneratingNotificationService.execute() {
          TemplateRepository.find();
          Notification.generate(template, application);
      }
      SmsGateway.send(notification);
   }
}

由此伪代码获得的时序图如下所示:


image.png


在得到这些伪代码之后,我们可以利用测试驱动开发由原子任务开始编写单元测试。编写时,仅针对代表领域行为的原子任务进行测试驱动。在这个过程中,需要严格遵循红-绿-重构的节奏进行,通过重构发现之前设计上的不足之处,可以让聚合内实体与值对象之间的协作能够更加的合理。


相关文章
|
3月前
2023驱动保护学习 -- 应用层与驱动层交互操作
2023驱动保护学习 -- 应用层与驱动层交互操作
15 0
|
7月前
|
芯片
到底什么是I/O的驱动能力?
到底什么是I/O的驱动能力?
|
1月前
驱动常用技巧
。。。未完,待续。。。
17 0
|
3月前
|
Unix Linux 编译器
Linux驱动设计(一):驱动是什么?
Linux驱动设计(一):驱动是什么?
23 0
|
3月前
|
Linux API
Linux驱动的软件架构(三):主机驱动与外设驱动分离的设计思想
Linux驱动的软件架构(三):主机驱动与外设驱动分离的设计思想
37 0
|
11月前
|
Linux API
Linux驱动分析之SPI驱动架构
Linux驱动分析之SPI驱动架构
|
敏捷开发 消息中间件 缓存
什么是领域驱动
领域驱动的概念
168 0
|
IDE 前端开发 数据可视化
ZenUML与服务驱动设计
ZenUML与服务驱动设计
ZenUML与服务驱动设计
|
Windows
驱动开发:驱动与应用的简单通信
驱动程序与应用程序的通信离不开派遣函数,派遣函数是Windows驱动编程中的重要概念,一般情况下驱动程序负责处理I/O特权请求,而大部分IO的处理请求是在派遣函数中处理的,当用户请求数据时,操作系统会提前处理好请求,并将其派遣到指定的内核函数中执行,接下来将详细说明派遣函数的使用并通过派遣函数读取Shadow SSDT中的内容。
203 0

热门文章

最新文章