2B or not 2B: 前端大泥球
今天给大家带来的是 2B 领域的一个架构难题,我们最终也没有找到一个较好「解决」方案, 或者说它本来就是一个伪命题。
让我慢慢跟你道来…
软件的划分模式
首先从软件系统的划分模式讲起。软件系统的划分有很多种方式,拆分的目的无非都是:分治、复用、隔离、扩展性、抽象等等。
最为常见的便是分层架构
分层架构将软件系统划分为若干层次,每个层次都是相互独立的,各自负责不同的功能和职责,通过明确的接口和协议进行通信,从而实现系统的可扩展性、可维护性、可测试性、可重用性等特点。
比如一个典型的 Web 系统的“三层架构”:
表示层(Presentation Layer)
:负责用户界面、用户交互和用户输入输出等功能,通过 Web 页面或者客户端应用来展示和控制数据的呈现方式。业务逻辑层(Business Logic Layer
):负责系统的业务逻辑处理、数据处理和其他业务规则的实现,为表示层提供数据和业务逻辑的支持。数据访问层(Data Access Layer)
:负责数据的存储和访问,为业务逻辑层提供数据访问的接口和实现。
微服务架构
随着系统的复杂化,我们需要将系统拆分为更小的子系统,来解决性能、维护性、扩展性等诸多问题。比如引入了微服务、微前端等解决方案,这个本质上是一种垂直方向的拆分
:
甚至我们在应用内部还会进一步拆分, 按照业务聚合度拆分成不同的模块:
这就是分治的魅力吧。
多业态
在 2B 领域,让我们更棘手的是,还要面临多业态问题
。
什么是多业态?
让 ChatGPT
来解释一下:多业态是指一个企业或者品牌在不同的业务领域或行业中拥有不同的业态,例如同一个品牌既可以开设餐厅,也可以开设酒店、咖啡店、快餐店、影院等不同的业态。多业态的企业或品牌通过在不同的业态中提供不同的产品和服务,以满足不同消费者的需求和偏好,增加企业的收入和市场份额。
多业态并不是一种结果,而是一种手段。比如在垂直领域耕耘多年的企业,想要扩大创收,就会将触角伸到其他行业,即所谓的跨界。还有就是一些初创企业,就像无头苍蝇一样,将网撒向不同的行业,来摸索出路。
我们就是属于后者。不过也有可能前期策略是在模仿有赞
的嫌疑(毕竟有赞在 18、19 年是当红的 SaaS 炸子鸡),铺设了很多行业:医药、教育、文旅、零售、地产、汽车…
也就是说在这种「广撒网」的商业策略下, 我们需要在「一套代码」中适配“多业态”:
然而多业态并不是简单的垂直方向
的进一步细分,而是多了一个维度
。如果说分层是 1D、 垂直划分是 2D、再加上多业态,就是 3D 了!
这些行业多态
会横向击穿垂直拆分后的模块壁垒,行业的多样性会渗透到程序的各个角落,开闭原则形同虚设。不管是前端还是后端,这是都是一个非常大的挑战。
现状就是本文标题中讲的,多了一个维度之后,对开发而言是灾难性性,整个项目就是一个大泥球。
所有行业的代码都堆砌在一起,充斥着各种区分行业的 if/else 语句、耦合牵扯、渗透在项目的各个角落… 总之这可能是程序生涯难得一见的代码屎山!
给大家一个直观的体验
垂直的软件拆分有很多方法论,比如微服务、 DDD。而多业态,在软件行业并找不到太多这样的最佳实践。
且不论这是否是战略上的错误。作为技术开发我们只能服从它, 并需求在战术上进行弥补。企业对软件开发的要求并不会因此降低,它还是会要求你的代码要区别「复用性」,要能快速应变各种需求、支持快速迭代…
产品架构
上图是我们团队基本结构,也是产品结构
(康威定律)、更体现了我们的项目交付模式
。很多非 SasS 化的 2B 公司的应该都是这类模式。
对我们来说更大的挑战在于:下游的项目能尽量复用上游的功能,避免重复工作,并且要求上游的更新能向下传递,甚至不排除下游合并到上游的可能性。
能感受到它的难度了吗?
怎么解决?
战略上的调整
从近几年组织架构上面的调整,可以反映软件架构的战略调整,它定下整个研发体系的基调。另外这些变化,也反映了我们对 2B 行业探索和认知上面的变化:
初创团队就是一个单体团队(左图所示),接着开始多行业撒网,原本的项目上慢慢堆砌出各种行业的形态(右图所示)。
随着业务发展起来,一些发展较好的行业成立了事业部,专门负责项目的交付。
随着行业的深入,事业部慢慢积累起来了更多行业 Known How
,通用的标品已经无法满足需求,事业部开始成立行业标品团队
,在行业标准化产品上做更多深入的定制开发;另外事业部内部继续细分专门的交付团队。
💡 行业 Known How 指的是某个行业内部特定的、非公开的知识和技术,通常只有在该行业内部的从业人员才能够掌握和理解。这些知识和技术可能包括行业内部的标准、工艺、流程、技术、经验等方面的内容,是行业内部的核心竞争力和商业机密。
到这个阶段「产品研发」开发的「标品」就处于一种比较尴尬的位置。一来它毕竟不是为特定行业打造的,已经不能满足行业的定制化需求,无法实现开箱即用;二来产品研发部门离真实的客户也比较远,很容易闭门造车,产品没有经过市场打磨,质量较差。
人们开始对「产品研发部」的价值产生了怀疑。
于是「产品研发」的出路是继续做下沉,不再做所谓的大而全标品,而是做好核心的平台化能力
,比如会员体系、CDP、SCRM、MA(Marketing Automation)…
这些平台化能力,剥离了具体的行业属性,并向未来的 SaaS 化靠拢。
多业态的架构难题,从笔者的角度来看,在战术上是基本是无解的。通过上面的战略调整可以看出,我们慢慢规避了这些问题。
也就说,我们没有解决它,而是绕过了它,甚至说我们放弃了这条路线也不为过。
通过上面的调整我们可以看到:
- 覆盖的行业在慢慢收敛,不赚钱的、走不通的行业干脆就放弃了。
- 另一方面,在具体行业和平台上更加专注,不再追求打造一个大而全的、可以覆盖多行业的单一产品。
战术上的适配
虽然,我们最终通过战略上的调整规避了多业态架构难题,但这毕竟是「果
」。推动战略上调整的「因
」,并不是因为战术上的实施难度,而是企业的市场适应。
虽然战略的调整可以从根上解决了问题,但这是一个漫长的演进过程(大概五年)。 一开始我们也预料不到现在的结局。因此在过去相当长的时间内,我们一直都在尝试战术层面
去解决多业态问题。
下面讲一讲,前端是怎么应对这些问题。
原则
首先确立一些行事的原则,作为后面具体实施的指导方向, 比如:
- 统一规范。完整的项目版图设计到很多团队、上下游的参与,我们必须统一规范。
- 保持沟通。因为我们需要拉通上下游,进行一些知识和代码的传递,沟通是很重要的。
- 更新/联动,隔离/复用机制。我们需要在框架层面提供代码上下游更新、联动,复用、隔离等机制。
- 生态共建。规定上下游团队需要一起共建生态,沉淀知识和行业 Known How.
- 互不影响、独立开发、独立部署。
- 开闭原则。对下游扩展开放,对修改关闭。
- 分离关注点。
确立共建的范围和上下游的协作关系
即定义了一些团队之间的协作规范,比如:
- 上下游团队之间责任划分、共建的范围
- 沟通机制
- 发布更新的频率和形式
- 分支规范等等
宏观上:行业隔离/业务聚合
在宏观的层面上,定义了两大措施或建议:
- 行业隔离。为了隔离不同业态,我们提出了两个措施
- 分支隔离。不同业态有不同的分支前缀(比如
团队名/dev
、团队名/master
),从而做到开发上互不干扰。可以从上游标品团队的分支中单向合并
,来实现下游同步 - 模块/文件的隔离。在这里我们主要使用
业态扩展名
, 比如index.js
、index.ky.js
、index.home.js
, 有点类似于React Native
(*.android.js
,*.ios.js
) 或者Taro
的特定平台文件。在编译时,根据当前业态进行条件编译。
- 分支隔离。不同业态有不同的分支前缀(比如
按照业务聚合。模块按照业务进行聚合,而不是根据菜单/职能:
- ❌ 菜单。这个问题主要出在 B 端,很多前端理所当然会按照导航菜单来拆分应用,问题就是菜单并不一定能准确地表达业务的边界,而且菜单是多变的,受运营的影响比较大。
这个我在 微前端的落地和治理实战 中也有讨论 ❌ 职能聚合。例如, 全局按照职能划分目录,所有业务模块都堆在一起
src
/components
A.js
B.js
/pages
/api
/...✅ 业务聚合。按照业务领域的边界进行垂直的拆分:
A 领域
/components
/pages
/api
B 领域
/components
/pages
/api
- ❌ 菜单。这个问题主要出在 B 端,很多前端理所当然会按照导航菜单来拆分应用,问题就是菜单并不一定能准确地表达业务的边界,而且菜单是多变的,受运营的影响比较大。
微观上:复用和扩展模式
宏观的分支、业态扩展名可以实现行业隔离
,让多个团队在同一个仓库下互不干扰,又可以融合开发。
而微观上层面,主要关注代码的复用和扩展机制,目的则是让行业能够最大限度地「复用」标品
的功能和代码。
笔者在实践过程中,总结了很多「设计模式」, 比如:
模式 | 灵感来源 | 概要说明 | 适用场景 |
---|---|---|---|
原子能力组合模式 | 低代码平台,前端组件化搭建,流程编排 | 先对应用程序进行分层,再组成拆分原子能力。实现自上而下的可组合。 | 应用的整体架构 |
插件模式 | VSCode、各种支持插件的工具 | 分离关注点,提取核心能力和外壳。通过插件接口扩展核心能力。 | 固定、稳定的业务 |
钩子模式 | 很多 Web 框架都提供了钩子函数、或者生命周期方法,对框架进行扩展 | 预留插槽,按需填充。 | + 适合比较固化的业务流程,比如登录、下单 + 适合比较固化的界面布局,通过插槽扩展某个区块的显示 |
覆盖模式 | 依赖注入 | 通过依赖注入覆盖原有的实现 | |
继承模式 | 类继承 | 在覆盖模式基础深入定制 | 实现文件级别的覆盖,完全重新实现功能。和 index 导出兼容。业态隔离 |
重复/隔离模式 | React Native、Taro 条件编译 | 无可退路的退路。拷贝代码,深度定制,互不干扰 | 差异性较大,没有合理扩展方案的情况 |
由于文章篇幅原因,关于这些设计模式的细节,请移步到这里
没有银弹
在多业态的架构难题上,笔者并没有想到行之有效的办法。反而随着战略的调整,这个问题变得不再重要。
上文介绍的战术适配,也仅仅能够实现「隔离」问题。而「复用」问题,我只是给出了一些设计模式,实际执行起来比较困难:一则是需求的变化和多样性足以打破各种美好的假设; 二来这会提升复杂度,不是所有开发者都具备良好的架构设计素养。
最后,随着市场的变化,驱动企业战略上的调整,企业家逐渐放弃了在产品上做大做全的幻想。 我们的各种挣扎,最终不过是炮灰,并没有太多实际的意义。
当然,对我来说也不是一无所获,历史经验教训:
- 不要过度追求复用。它是一把双刃剑,重复不见得就是坏事,我们需要在复用性和隔离性之间权衡利弊。
- 不要追求大而全。更要做深做精。
- 用奥卡姆剃刀做减法。放弃那些不切实际的幻想。
- 战略和战术配合。不要只在战术层面钻牛角尖。
- Keep it simple and stupid。 简单即是美,在设计和开发过程中,尽量追求简单和易于理解的方案,避免过度设计和复杂性。关注用户最核心的需求。
本文完,求赞求收藏求转发。