作者 Kid 蚂蚁金服. 数据体验技术团队
随着我们解决的场景越来越专业化和复杂化, 大型 SPA 应用的流行, 前端承担的职责越来越多. 代码的质量和系统的完整性越来越难把握. 很容易导致迭代着迭代着发现代码改不动了. 最后只能新起炉灶, 重新开发. 归根到底在于复杂度的失控, 本文会尝试分析其中的问题以及从
前端如何应用领域模型
开发的角度给出一些建议.
为什么迭代越来越难
我们的系统架构精心设计过, 按照标准的系统分层来管理复杂度. 逻辑层, 展示层, 数据层. 每一层都精心设计. 我们抽象出独立的类来放通用逻辑. 对着代码不断地重构, 将有复用能力的点进行抽象. 为什么需求的变动还是能经常摧毁我们的设计呢.
原因在于:
问题域本身错综复杂
技术模型与领域模型不匹配
知识的丢失
问题域本身错综复杂
软件本身是为了管理复杂度, 我们现在面对的问题域错综复杂. 为了创建真正好用的软件, 开发者必须有一整套与之相关的知识体系. 为此要知道的知识的广度让人生畏. 一旦我们不能理解问题域, 我们就没法做到控制问题域的复杂性.
当复杂性失去控制的时候, 开发人员就无法理解软件. 当领域的复杂度没有得到解决时, 基础技术再好的构思也无济于事.
技术模型与领域模型不匹配
上面我所描述的设计都是技术层面的设计. 我们很容易抽象出一个独立的类来放通用逻辑, 可是很难给它业务上的定义! 这个通用类只有技术维度上的通用.
问题在于技术维度上的通用
很容易被业务摧毁
. 需求上的变动或者膨胀, 技术维度的通用很容易被摧毁. 举个例子, 页面变化了, 某个视图组件被复用了, 他可能就要被提取到上层的 common 目录. 也就是技术模型立刻需要重新设计, 然后就是重构, 重构成工程师喜欢的简洁的样子. 然后需求再变化, 再重构.... 陷入了怪圈.
并且这个阶段我们很难保证重构的高效进行, 有个理论叫破窗户理论. 一幢年老的大楼, 一旦第一扇窗户破了, 就会立刻给人一种年久失修, 腐败的迹象. 就像是一辆车, 一旦第一个车窗坏了, 里面很快就会遭到破坏.
里面的根本原因就是我们设计的技术模型与领域模型不匹配. 于是每次需求的改动, 映射到技术模型的改动可能就是极大的工作量. 甚至根本改不动, 在业务压力很大的时候, 我们只能告诉产品经理, 这个可以做, 但是我们需要 2 个月. 结局很可能就是需求方的妥协, 牺牲用户的利益. 导致产品越来越难用.
知识的丢失
任何项目都会丢失知识, 外包出去的系统可能只交回了代码, 知识没有传递回来. 离职了, 转岗了, 一旦出于某种原因人们没有口头传递知识, 知识就丢失了.
丢失的知识也会导致系统越来越难维护, 新同学不知道对于通用逻辑的改动会发生什么事情, 代码最终变成了 "石油坑", 越陷越深, 最终无法自拔.
总结
以上这三个问题归根到底, 就是我们没有在前端代码里把我们业务描述清楚. 我们很多情况下是视图驱动, 而不是业务驱动. 很多时候只关心页面长什么样子, 发了什么请求拿了什么数据. 于是在业务概念上每个人理解的深度都不同. 解这个问题可能采用新的领域驱动设计的开发方式会比较合适.
领域驱动设计
领域模型是跨前端 - 后端 - 产品 - 设计的统一的语言. 统一的语言既可以形成统一的理解, 也可以促进领域模型的不断精化. 也能迫使开发人员学习重要的业务原理, 而不是机械的功能开发. 产品经理也会不断提炼知识, 升华自身理解. 如果没有一个统一的, 有共识的结构化的模型, 一定会让项目很快的僵化, 最后变成维护代价极高的遗留系统.
前端应用领域模型
领域模型很多情况下都是由后端同学建立的, 前端同学如何指导开发呢? 我对于我们系统的演进过程进行了总结, 希望能给大家一些灵感:
理解后端领域模型
建立前端领域模型
分离领域层
主导接口约定
开发中注意业务含义
实时同步
理解后端领域模型
我们在进行前端设计之前要搞懂我们要开发的业务含义. 除了自己理解建立模型之外我们可以寻求后端同学的帮助.
拿到他们的领域模型
, 弄清他们的模块划分. 他们其实是业务逻辑的最终实现方, 我们可以直接借鉴他们的模型, 这样也可以保证前后端对于业务模型的理解一致.
建立前端领域模型
我们要绘制出前端的领域模型图, 这个图与后端的领域模型图一致程度很高, 但绝不是一样的. 通常比后端模型简单. 比如页面需要进行一项任务的配置, 这个配置在后端模型里可能会被解释的相当复杂 (会被拿去做一些同环比之类的复杂操作), 但是在我们前端模型里, 他的业务功能就是简单的任务配置而已~
分离出领域层
如图, 这一点是必须落到代码上的核心!! 一定要根据对应的前端领域模型在代码中分离出单独的领域层. 模型必须与实现紧密结合, 一定要指导设计, 并
落到代码上成为最终产品的一部分
.
还需要强调的是领域层的建设一定不是两个页面同时发了个请求, 于是把这个请求抽出来, 给与一个领域的名字. 他一定被提前建立好的. 在开始进行前端设计之前就被设计出来的一层.
我们要将所有页面组件与模块内的业务行为都抽离出来, 放在合适的领域模块中. 只要是业务行为,
一定有一个领域模块可以落
. 如果不行就是领域模型设计的不合理.
要明白, 驱动领域层分离的目的并不是页面被复用, 这一点在思想上一定要转化过来. 领域层并不是因为被多个地方复用而被抽离. 它被抽离的原因是:
领域层是稳定的 (页面以及与页面绑定的模块都是不稳定的)
领域层是解耦的 (页面是会耦合的, 页面的数据会来自多个接口, 多个领域)
领域层具有极高复杂度, 值得单独管理 (view 层处理页面渲染以及页面逻辑控制, 复杂度已经够高, 领域层解耦可以轻 view 层.
view 层尽可能轻量
是我们架构师 cnfi 主推的思路)
领域层以层为单位是可以被复用的 (你的代码可能会抛弃某个技术体系, 从 vue 转成 react, 或者可能会推出一个移动版, 在这些情况下, 领域层这一层都是可以直接复用)
为了领域模型的持续衍进 (模型存在的目的是让人们聚焦, 聚焦的好处是加强了前端团队对于业务的理解, 思考业务的过程才能让业务前进)
这里想引用下我们 leader 导演的话说, 我们的竞争力绝不仅仅只是前端, 我们的竞争力在于我们是数据部门的前端, 在于我们对于数据业务的理解. 只有对于业务有深层次的理解, 才能将系统带到正确的轨道上来.
主导接口约定阶段
接口约定尽量由前端主导, 毕竟接口是给前端使用, 前端来设计接口比较合理. 而且在约定的过程中, 前端同学又多了一次熟悉后端是如何分模块的机会. 必须通过看后端同学的数据库和整体的设计文档来约定接口路径和变量名称, 也能够让前后端同学对于系统的各部分的命名一致.
在开发中注意业务含义
我们在类, 方法, 模块命名时要直指业务核心, 保持与领域模型的一致. 比如一条员工数据记录可能会被翻译成 inputRec 或者 employeeData, inputRec 其实就是一个计算机思维的术语, 而 employeeData 才是直指问题领域. 这个错误其实很容易犯, 我们开发的
程序员思维根深蒂固
.
实时同步
确保团队内部所有同学都要熟悉系统的模型. 尤其是对于要熟悉并修改代码的新同学, 先向他们分享我们系统的领域模型之后再介绍技术架构. 工作开展的重点的不同会导致编程世界观的不同. 这样子会让新同学养成习惯, 在进行技术决断之前先判断是否符合现有的模型. 不断的思考模型, 才能够帮助我们业务成长.
总结
领域驱动设计对于降低项目的复杂度上是明显效果的, 而且将前端的代码业务逻辑和视图逻辑解耦. 可以做到业务逻辑层的复用. 加深了前端同学对于业务的理解和思考, 可以促进业务发展. 这种分层思想并不局限在某个框架下, 建议大家尝试下~
对我们团队感兴趣的可以关注专栏, 关注 https://github.com/ProtoTeam/blog 或者发送简历至'tao.qit#### http://alibaba-inc.com'.replace('####', '@'), 欢迎有志之士加入~
来源: https://juejin.im/post/5b1c71ad6fb9a01e5918398d