借助DDD思想,实践出质量检测系统

  张一帆   2021年09月12日


DDD 是 Domain-Driven Design 的缩写。其主要的思想是,我们在设计软件时,先从业务出发,理解真实的业务含义,将业务中的一些概念吸收到软件建模中来,避免造出“大而无用”软件。也避免软件设计没有内在联系,否则一团散沙,无法继续演进。

前言

  大屏项目借助链家门店中的终端设备(比如电视,pad等),构建智慧门店各场景的解决方案,助力门店业务发展。同时,协同交易签约场景打造居住服务中心智慧标杆,提供全新体验并优化签约流程。

  我们希望借助大屏深挖签约过程中更有价值的需求点,比如在签约过程中会录音录像(经过客业双方授权)。我们借助录制的视频可以保存下来作为客业(客户业主)双方斡旋时的证据,也能联合内部AI能力借助ASR转化来检测签约经理在服务环节中的关键讲解进而提升签约经理的服务质量。于是,我们就对此需求进行了立项,立项通过后进入了开发环节。

项目背景

  首先,简单介绍下项目交互和情况。实际发生签约时,客业及经纪人落座签约室进行签约过程。签约过程包括但不限于接待、斡旋、风险视频播放、起草合同等环节。每个环节的切换都是由签约经理通过电视进行操控的,所以每个环节的打点时间是有的,就可以和录音录像连动起来了。环节如图所示:

架构图

  接着简要描述一下项目需求:

  • 一个签约场景需要进行的环节有很多,每个环节内需要做的质量检测条目也很多。
  • 需要一个工作流系统,承接上游下发过来的质检时间点和质检规则。根据质检规则发送给AI侧进行ASR。
  • 需要一个工作流系统,转化上游质检环节的数据,下发给实际处理视频的系统进行ffmpeg处理。
  • 需要一个操作ffmpeg脚本的服务,负责制作音视频。
  • 未来可能会有更多的扩展业务需要进行质量检测。

架构思想

  然后,说下项目的架构设计思想。成熟的架构设计应该遵循简单,可复用的原则去设计。我们应该将整个项目优先进行切分,划分出不同的领域去设计。在这里我们可以简单的理解领域为专注的一件事情。在我们的这个项目中就划分出了两个领域,一个是工作流,一个是ffmpeg音视频制作系统。工作流系统专注于将上游业务下发的任务通过转化和编排等步骤切分出不同的底层可理解的任务,如拼接,转码等,再发送给视频处理系统去处理。ffmpeg音视频制作系统关注ffmpeg领域,只关心音视频制作等逻辑。ffmepg系统为底层系统,只对接工作流系统。如果将来接入方越来越多的话,因为只有工作流是对接系统,所以底层的ffmpeg可以与业务解耦,能够提供更灵活的支持。如下图所示:

项目的架构

领域设计实践

  从以上的需求描述来看,设计的概念较多,也有一定的业务复杂性,而且此次需求是质检系统从0到1的过程,我们决定使用领域驱动设计的方法论来解决业务复杂性问题,以及未来系统的可扩展性、可维护性。我在其中负责的是底层ffmpeg的服务。

构建统一领域语言

  由于需求涉及到的很多概念,为保证左后程序运行结果符合业务预期,需要对这些概念进行在限定语义下的无歧义的描述,就是构建统一的领域语言。这个步骤需要项目组全体成员达成共识,项目的正确性依赖于此。基于本次需求,我们规定了以下这些概念的具体含义:

  • 任务:指制作视频时的各种动作类型的任务;
  • 动作:指一个任务下需要进行的各种动作;
    • 下载:指从S3上进行下载
    • 上传:指上传至S3(批量同理)
    • 拼接:音视频文件拼接动作
    • 转码:音视频文件的转码动作
    • 剪切:通过业务指定的打点时间,剪切出音视频片段
    • 空源:指一个视频为黑屏,声音为无声的视频或者音频
  • 方案:由一个任务和一组动作组成的集合
  • 匹配:一个任务匹配动作的方案集
  • ffmpeg命令:通过不同的参数运行ffmpeg可以实现对音视频的操作

业务分析与建模

  以上的概念统一有助于接下来要进行的业务分析,在业务分析中主要的关注点在业务的相关概念、隐含概念以及这些概念之间的关系上,这些关系包括依赖、关联、聚合、组合等。

  对于业务分析,可以使用多种方式进行,比如用例分析法、UserStory、事件风暴等。这个具体看团队对于哪种方式比较熟悉。我们采用用例分析法,就是对于业务描述、梳理出各种情况下的用户用例。此次需求中有三个模块的用例:

  • 任务模块;
  • 动作匹配配置模块;
  • 动作组装命令策略模块;

  这里先来进行动作配置模块的用例分析:

  • 生成动作列表,校验任务状态;
  • 生成动作策略方案,从配置列表按照任务进行过滤;
  • 生成动作匹配集,每个任务根据传入的参数筛选匹配符合的动作;
  • ……

  具体更多的详细用例这里不再展示,业务用例的列举越全面详细,由它构建的模型就会越准确,在列举用例的过程中要依据产品的PRD(如果有),但不能照搬PRD,要对PRD的内容进行归纳总结抽象,发现其中隐含的模型概念,比如这里的动作匹配策略的概念在PRD中没有显示的表现,但是隐藏在业务执行的过程中。

  当用例列举完善后,需要在这些用例中总结抽象出业务模型以及模型之间的关系。对于分析用例,简单来说有以下三个基本方法论:

  • 提取实体,识别名词定位出实体;
  • 添加关联,识别动词添加实体和实体质检的关联;
  • 添加属性,识别形容词添加实体属性;

  对于上述过程,它不是一蹴而就的,而是一步一步的发现用例,画出相关的模型图,然后再次发现隐含的用例或模型,修改模型图,它是一个迭代而产生的模型图。在迭代的过程中,依然要对相关模型进行统一语言的构建。

匹配配置模型分析

  此项目的难点就是对于音视频处理任务的类型较多,而且每种类型的任务的场景不同,ffmpeg需要采用的命令行参数也不同,涉及到的规则很是复杂。如下面的规则示例:

  • 拼接 && A场景 && 兼容模式 && 电视摄像头采集 && ……
  • 拼接 && B场景 && 高画质模式 && 安防摄像头采集 && ……

  类似这样的规则需求里面有很多种,未来会频繁的修改添加,所以如何对其进行建模使其可配置可扩展至关重要。对与这样的规则,我们把它抽象为规则、条件表达式、ffmpeg命令参数这样的统一概念,一个方案对应一个表达式,一个表达式由一个或者多个ffmpeg命令参数组成。模型图如下所示:

模型图1

  这样的模型可以使ffmpeg命令和方案配置解耦,方便未来扩展更多的ffmpeg命令以及进行方案的配置,条件表达式可以使用方案引擎进行解析。

组装ffmpeg命令策略模型分析

  对于ffmpeg命令策略模型的设计不但依赖上面的匹配配置模型,还要考虑到不同命令参数对最终音视频的影响。目前项目涉及的任务类型只涉及到质检场景的视频拼接、剪切及转码。在未来规划中,还会对其他场景及任务类型的支持,所以这里需要对其进行可扩展的设计:抽象出场景的概念。

  基于以上的模型构建,主要分为三个模块:场景、方案、ffmpeg命令参数。想要完成ffmpeg命令策略模型的设计就需要对这三个模块的信息和算法进行编写,我们还需要在设计的同时让他们保持相互解耦,方便未来对算法进行迭代优化。基于此,整个业务模型图如下所示:

模型图2

领域建模

  基于以上的业务分析与建模、接下来要进行相关领域模型的建模。领域模型构建的关注点主要在于实体、值对象、聚合、领域服务和库的定义。

  1. 实体、值对象分析

    首先要对业务模型中的每个概念进行领域建模、领域模型中最主要的就是实体和值对象,他们的区别有两点:

    • 实体是有唯一ID的,而且在业务中是有生命周期的,就是它会有一些状态变化;
    • 值对象也可以有唯一的ID,但是它是无状态的,是在程序运行过程中是不可变的;

    根据以上的原则,可以对上述概念进行领域建模。

    • 对于任务这个概念,他是有唯一ID的,在任务处理过程中它是会改变状态的,所以它应该是实体。
    • 对于动作这个概念,它是由唯一ID的,在处理过程中它不会改变状态,所以他应该是值对象。
    • 对于场景这个概念,他是有唯一ID的,在任务处理过程中它是不会改变状态的,所以它应该是值对象。
    • 对于匹配来说,有唯一ID,在处理过程中一旦创建就不会改变,构建为值对象。
    • 对于方案来说,同上。
    • 对于方案集来说,在处理过程中它需要维护方案的ffmpeg参数,是否为最优方案等相关状态,所以他是实体。
    • 对于匹配策略来说,有唯一ID,在处理过程中不可变,构建为值对象。对于他所包含的参数规则,筛选规则,方案匹配规则都应该是值对象。
  2. 聚合的划分

    聚合这个概念是一个抽象的概念,它在代码中没有具体的类模型去承载,但是聚合的划分对于代码逻辑的内聚性至关重要。聚合可以理解为领域模型之间的一种代码约束,比如不同聚合之间的模型不可以直接调用,需要通过聚合根进行聚合间的交互。

    对于上述模型来说,可以看出来有任务、场景、ffmpeg命令策略这三个聚合。其他的模型与这三个聚合一起构成这个ffmpeg模块聚合。对于ffmpeg命令策略这个聚合,它的内部有匹配规则这个聚合,规则由于有多种类型,所以它是一个抽象类;规则聚合包括表达式这个小聚合,表达式聚合又包含了ffmpeg命令聚合

  3. 工厂、库、领域服务分析

​ 实体和值对象是领域模型中比较重要的两个模型,剩下的工厂、库、领域服务这些对象是为了维护以上两个模型的生命周期而产生的模型。

  • 工厂:正如它的名字,主要维护模型的创建工作。有两种形式来创建工厂,一个是通过构造方法充当模型的工厂方法;如果模型构建比较复杂,就需要创建一个工厂类来承载创建模型的工作。对于上述模型,需要为任务、场景、ffmpeg命令这三个聚合创建工厂类,因为他们够在相对来说比较复杂。
  • 库:库的概念主要是用来调用基础设施层来获取持久化数据以及进行数据持久化。这里服务者需要从第三方接口获取数据,需要构建一个库对象;测录需要从数据库获取,构建一个库对象;
  • 领域服务:对于领域服务,它主要承载聚合之间的交互逻辑,如果发现有一个逻辑设计多个聚合,它没有一个主体,不适合放在其中的摸一个聚合中,这时需要构建一个领域服务来承载这部分逻辑。比如上面木星中的方案生成逻辑,它涉及任务、场景、ffmpeg这三个聚合,所以应为其创建一个领域服务来承载方案生成以及最优化方案的成圣逻辑。对于领域服务,不能随便滥用,不然会变成一个业务逻辑的打你团。
  1. 领域模型

  基于以上的分析,我们可以构建出如下的领域模型图:

领域模型

  以上模型基本和业务分析出的模型类似,我们为其确立了具体模型归属,划分了相关的聚合,添加了相应的辅助领域模型。在构建这个模型时,依然要随时丰富相关的用例case,不断完善领域模型。