为什么你的轮子很难用(二):从战略到抽象3

337 阅读13分钟

画外音:

一直都在收拾了别人的烂摊子,总结一下怎么做一个"人见人夸"的功能设计。

这个系列主要写给产品看,最佳实践不敢说,失败案例我有大把可以分享和吐槽的。

研发请随意,有不妥的地方欢迎指正。

前情提要:

# 为什么你的轮子很难用(一):别造,造的话先盘组织和业务

# 为什么你的轮子很难用(二): 从战略到抽象1

# 为什么你的轮子很难用(二):从战略到抽象2

当你不得不造一个轮子的时候,你需要先储备一些基础常识。内容很多,我们分期讲:

三、组件化

第一节:设计思想

在讲组件化之前,我们需要 梳理几组概念:

1. 可维护性/可复用性/可拓展性:

1)可维护性:指软件能够被理解、修正、适应及扩展的难易程度。

2)可复用性:指软件能够被重复使用的难易程度。

3)可拓展性:指软件适应未来需求变化的改造成本。

也就是说:可扩展性是可维护性的一部分,修正重,扩展重

大部分轮子的难用,无非就这么几种情况:

1)可维护性不强:

出问题不知道怎么查,故障定位不好定。

知道问题不好改,牵一发动全身,隔离没做好,引发各个子功能出问题。

2)可复用性不强:

无法多次重复创建或利用这个对象。或者 有个相同的需求,接入成本特别高。

得多出来一堆与接入方无强相关的东西,各种依赖/调试/配置步骤一大堆。

(注意:这是技术层面的复用性,不是业务层面的复用性。)

3)可拓展性不强:

功能架构/数据结构/接口封装 等等设计不合理,加个功能得到处改,甚至于大范围重构。

2.面向过程/面向对象/面向切面:

1)面向过程 POP:

描述的是功能逻辑执行步骤是如何组织的。

也就是:对象之间是怎么进行处理与计算的,以及 对象内部从输入到输出是怎么处理计算的。

比如

步骤1:A 打 B ,

步骤2:A 打 C 。

2)面向对象 OOP:

描述的是 功能逻辑中 所涉及的 对象及其行为。

也就是:我是谁,别人输入什么,我将输出什么。

同上例:

对象1:A,行为 打(人),

对象2:B,行为 被(人)打,

对象3:C,行为 被(人)打。

3) 面向切面 AOP:

描述的是 对共同行为的抽象封装,实现调用方与被调用方解耦 乃至 实现功能逻辑的分层治理。

比如 区分业务逻辑与公共逻辑,核心逻辑与支撑逻辑,将可重用的部分剥离出来固化。

同上例:

调用方:A ,

切面:打(人),

被调用方:B / C。

当你大概get到这几组概念,那我们就可以开始来讲组件了。

第二节:什么是组件

我们来看一下某些学术定义:

是一个组装单元,它具有约定式规范的接口,以及明确的依赖环境。 构建可以被独立的部署,由第三方组装。

看着很绕口,让我们大白话翻译一下

1)有约定规范的接口: 有固定的输入与输出内容,且输入到输出执行逻辑是稳定的。

2)明确的依赖环境:在特定工程范围下才能使用,脱离了这个环境用不了。

3)构建时可独立部署:可以多次创建与利用,每次创建或利用是隔离开的,不会互相影响。

4)由第三方组装:这是一句有歧义的话,因为第三方不代表所有的第三方都可以。

问题就来了,什么是一个组装单元?

这个问题是不是很眼熟?跟# 为什么你的轮子很难用(二):从战略到抽象2 我曾经问的一样,什么是一个实例?

让我们再次回到这张图: image.png

基于前面的认知,我们给出一个新的定义,

组件:在特定工程范围内,具备 可复用性的 一个 实例/模块/页面/应用/系统。

组件化:某个实例/模块/页面/应用/系统,在特定工程范围内,具备可复用性。

也就是说,组件是比它大一级的对象所定义的。

让我们来举个例子:

这里有 一个X系统,它封装了多个调用接口,

此时 公司内多个系统可以调用 X 来实现 与其他系统无关的 特定功能逻辑。

== X 具备组件化能力。即 X在公司内部是一个组件。

而 X系统是由 M+N...多个应用组合起来的,且 X可以重复创建利用,灵活编排 M,N...

== M...N具备组件化能力。即 M..N应用在X系统内部是一个组件。

此时你就明白,复用和复制的区别了。

复制:CTRL+C CTRL+V, 在一个或多个工程范围内,有一模一样的多个对象

复用:在同一个工程范围内,重复创建 或 利用 这个对象。

所以经常运营问一个问题:

明明这里已经有了功能A,为什么在那里不能加上直接用?

答案很明显:压根不在一个工程范围内。

第三节 一个组件有多复杂

1.碎碎念:

首先 当你需要组件时:也就意味着 你需要多次创建或利用它。可就是所谓 可复用性。

那么你 不光要关心 组件内部的逻辑,更要关心 组件与外部的关系。

组件本身是一个对象,这个对象本身包含相应的行为。

行为的执行,具备步骤的,也就是说 对象的内部 可以是面向过程的。

( 如果 组件内部可以再细颗粒度的拆分,也可以是 面向对象 乃至切面。)

而组件与外部的关系,是面向切面的,脱离了共同行为的统一抽象封装,就丧失了复用性。

那组件与外部的关系,就需要思考 对象的层级。

2.对象的层级需要分端思考:

之前我们讲过从宏观到微观是从 layer 到 entity。

如果说 layer到entity为横坐标,是 一个平面内的 宏观 到 微观

那分端作为纵坐标就是对象的层级,是 多个平面上的关系,

第一个大平面:大前端

移动与PC( H5--webview--bridge--客户端--操作系统) // 小程序,RN,flutter同理

web ( web--浏览器--操作系统)

第二个大平面:大前端与服务器的串联

( API/soket--CDN/网关BFF--node),

第三个大平面:业务后端与服务器的串联

( API/soket--服务器/容器)

第四个大平面:业务后端与业务admin

( 业务后端:MVC/MVP/MVVN/MVI--服务端之间的传输:RPC/MQ/kafka )

( API--各种各样的WEB配置admin)

第五个大平面:底层中台和中台admin

各种各样的底层服务:音视频 / 用户信息 / 关注 / 分享

本质上也是:

( 业务后端:MVC / MVVN--服务端之间的传输:RPC/MQ/kafka )

( API--各种各样的WEB配置admin)

第六个大平面:数据基础设施

( 数据存储:mysql / ES / redis / hive / flink / hbase / mongoDB....)

( 数据计算:spark / hadoop )

多个层级的关系,即 从 前端 到 后端,从 内部服务 到 外部服务 ,从 业务服务 到 基础设施

让我们 再改写一下 组件的定义:

某端的组件:在特定工程范围内,具备 可复用性的 一个 实例/模块/页面/应用/系统。

某端组件化:某个实例/模块/页面/应用/系统,在特定工程范围内,具备可复用性。

—>

业务的组件:由1到多端的 实例/模块/页面/应用/系统 有机组成,且整体具备可复用性。

业务组件化: 将1到多端的 个实例/模块/页面/应用/系统 有机组织,改造具备可复用性。

知道 你为什么每天被研发绕晕了吗?

因为 9成9的概率,他讲的组件,是他那一端的组件,不是你业务意义上的组件。

另外,为什么我只写到 系统,因为 业务意义上的组件 最多也就到 系统 这个宏观视角了。

尤其 一个系统,大概率 是前后端共同参与的,有用户端,管理端,sever。

你说 如果 admin管理端 聚合了多个系统且具备可复用性 叫什么?

如果 吹牛逼,可以叫 XX平台 or XX中台了。

image.png

第四节:组件是怎么前后端分离的

如果你已经熟练掌握 一个 mpa 应用的写法,比如像我们之前梳理的PRD模板。

你应该有了起码的 前后端 分离 意识。

备注:作为一个 次抛型的MPA应用的PRD,它应该是这样的。

页面层逻辑:对所有页面进行总体说明

 1.页面概述:一共有M个页面N个模块
 A 页面:XXXXXXX 
 模块1:标题xxxx
 模块2:标题xxxx
 B 页面:YYYYYYY
 分区1:yyyyy
 模块1 :标题mmmmm
 模块2: 标题nnnnn
 ...             
 2.页面胶水
 当未登录时,不显示A页面和B页面,仅显示C页面
 当已登录时,展示A页面和B页面,不显示C页面
 

模块详述:对单个模块进行书写

  所属页面:页面XXXXX
  所属分区:分区XXXXX
  父模块1:标题XXXXXX
  模块综述:该模块由 A+B+C 三个子模块组成 
  胶水逻辑:三个子模块,默认展示第一个。展开其他时,其余收起。
   
   子模块A:标题XXXXX
    前端逻辑  
    A 实例1:XXXXX
     1)展示逻辑: 从M,根据 X 取 Y ,在N 进行数据展示
     2)交互逻辑: 一共有哪些状态,怎么展示,怎么操作,不同状态有什么交互 
     3)胶水逻辑:(与其他前端对象的联动逻辑)当XXX模块发生XXX行为,此时该模块的XXX需要变更XXX
     4)埋点逻辑: 模块ID--模块名称--交互行为(曝光/点击)上报内容{ xxxx , xxxx }
    B 实例2:XXXXXX
     1)展示逻辑: 从M,根据 X 取 Y ,在N 进行数据展示
     2)交互逻辑: 一共有哪些状态,怎么展示,怎么操作,不同状态有什么交互 
     3)胶水逻辑:(与其他前端对象的联动逻辑)当XXX模块发生XXX行为,此时该模块的XXX需要变更XXX
     4)埋点逻辑: 模块ID--模块名称--交互行为(曝光/点击)上报内容{ xxxx , xxxx }
    后端逻辑: 
    A 接口1:XXXXXXX
     1)依赖逻辑:从哪个后台配置什么后端功能, ID是多少
     2)实现逻辑:使用什么数据,计算什么,判断什么,
     3)存储逻辑:数据存多久,如何更新同步
     4) 接口逻辑:出参是...,入参是......
    B 接口2:XXXXXXX
     1)依赖逻辑:从哪个后台配置什么后端功能, ID是多少
     2)实现逻辑:使用什么数据,计算什么,判断什么,
     3)存储逻辑:数据存多久,如何更新同步
     4) 接口逻辑:出参是...,入参是......
     

我们知道:

前端——>负责 展示UI,执行交互,胶水通信,埋点上报

后端——>负责 数据接口,逻辑计算,数据存储,更新同步

如果是一个组件,必然涉及到 非常多的 开关和配置。

这些个 开关与配置,又也是需要有个admin管理和存起来的。

那就是说:前端的UI和交互也是要有个后端去管理的。

到底怎么分离好呢。

主要有以下3种模式:

第1种:H5/navtive 统一对应 1个admin 和 1个sever

UI/交互/逻辑/数据所有都在一起配

优点:对运营非常友好,不用跑来跑去;逻辑变更升级友好,前后端一起改,不用担心兼容性

缺点:这套admin和sever,仅能管理这一套 H5/navtive,别的管不了,复用性变低了

第2种:大前后端分离各自配各自,人工单向绑定

1)H5/navtive + 编辑器admin +编辑器的sever

只配 UI/交互/传参数据(比如业务sever 按id绑定请求数据接口,获取数据)

2)业务admin+业务sever:

只配 业务逻辑相关的数据和开关,比如 任务类型/计算周期/用户名单/奖品库存/上下架状态

优点:

H5/navtive 可以区分多个组件,分别获取不同sever的数据,但是每个组件还是对应固定的sever

业务sever的可复用性也提高了,可以应用在 产研活动/H5编辑器等多个场景

缺点:

单向数据绑定运营差评,配个东西跑来跑去,也不清楚 哪个前端组件 对应 哪个后端,要填什么参数

sever仅提供接口,也不清楚到底有多少前端H5和组件使用它,每次修改逻辑都可能导致前端不兼容

第3种:1与2的结合体,自动双向数据绑定

1)H5/navtive + 编辑器admin +编辑器的sever

UI/交互/逻辑/数据都在编辑器一起配,然后编辑器的sever调用业务的sever进行创建/修改

存储 H5/navtive 与 业务sever的绑定关系

2)业务admin+业务sever:

业务sever专门提供接口给编辑器sever进行创建/修改,仅提供部分能力

存储 H5/navtive 与业务sever的绑定关系

业务admin隐藏由编辑器sever创建的实例,admin照样可以配置给 产研乃至其他使用

优点:运营不用跑来跑去,也不用清楚有什么参数,业务sever可以根据调用方进行逻辑和数据隔离

缺点:业务sever和admin有修改和新增特性,需要 H5/navtive 编辑器 也进行变更

画外音:

当然还有更奇葩与复杂的情况:

比如 

发布:H5的编辑器配置完,zip打包,用压缩文件到另外一个admin进行发布到cdn的

多语言:遍历文案后,文字翻译的映射要到某个gooledoc或者admin配,再跑回来导入的

埋点:搞 可视化埋点,需要到埋点平台创建,再跑回来绑定的

前置:业务admin里需要配置 中台的配置,然后 跑N个后台 连续配的

打包:前端组件作为npm包或者iframe,给第三方工程依赖,然后配置还在编辑器的

反推:前端组件约定通用协议格式,别的sever按照协议推数据过来进行绑定的

真不好意思,这些我都 做过和见过...

但是我们后面就不展开讲了。

此时,当我们制定完组件的基础实现,就可以开始尝试 设计一个 组件 了。