前端业务项目整洁架构

173 阅读17分钟

1. 引言

随着前端业务复杂度的增长,代码维护成本、可扩展性、团队协作效率等问题日益凸显。根据《Measuring Program Comprehension》研究,开发者 80% 的时间消耗在代码理解与导航上,而非实际编码。这种困境主要源于两大维度:

  • 架构层面的失控:业务逻辑与框架实现强耦合,导致复用性差、迁移成本高;

  • 代码组织模式的局限性:逻辑碎片化、层层嵌套、组件臃肿等。

本文将以积分商城项目为例,探讨如何通过 整洁架构 实践以及 Vue 3 Composition API 的深度应用来处理复杂前端代码,从而提高代码的可维护性和扩展性。

2. 积分商城项目背景与需求

2.1. 项目背景

应用宝积分商城是一个非典型的电商类前端应用,主要涉及用户资产及商品展示、使用应用宝会员积分兑换商品等功能。其核心功能包括:

  • 用户资产展示:用户可以查看自己的积分余额及组成、积分获取记录等。

  • 商品展示:商品列表,包括商品名称、价格、库存、前置兑换条件、商品关联的游戏等信息。

  • 积分兑换:用户可以通过积分完成商品的兑换。

积分商城的商品种类繁多,不同种类的商品涉及不同的交互逻辑,例如:

  • 实物商品需要填写收货地址,与电商商品无异。

  • 游戏道具需要选择区服。

  • Q 币需要填写 QQ 号。

  • 微信立减金需要关联微信。

  • 宝券需要展示可用游戏。

2.2. 商品架构迭代

在需求迭代中,积分商城商品架构经历过几次变更:从单个商品切换到单 SKU 商品,从单 SKU 商品切换到 SPU 商品,SPU 商品可以关联多个 SKU 商品,并在弹窗中展示所有关联的 SKU 商品供用户选择。

2.3. 项目痛点

我们平常开发时常常会因为组件逻辑简单,将数据的请求、逻辑的处理统一放置在组件中。但随着需求迭代,频繁的商品架构变更、以及新商品种类的接入让积分商城的逻辑变得越来越复杂,组件越来越臃肿的同时,组件与组件之间的数据嵌套层级也越来越深,代码的理解难度陡增,日益凸显出代码可维护性和扩展性的重要性。

基于此,在商品架构由单 sku 升级至 spu 时,我们对积分商城的数据流、代码组织等进行了梳理,意图解决一些痛点。

3. 剥离框架:整洁架构

3.1. 整洁架构

应用宝前端组内业务一直在推动整洁架构在前端的应用实践:整洁架构(Clean Architecture)由 Robert C. Martin 提出,强调以领域模型为核心,通过分层解耦实现技术无关性。其典型分层包括:

领域层(Domain Layer) :纯业务实体与规则,如电商中的订单计算、库存管理逻辑;

应用层(Application Layer) :编排领域能力实现用例(如“用户下单流程”);

适配器层(Adapter Layer) :对接UI、API等外部服务,实现协议转换;

框架层(Framework Layer) :Vue/React等具体技术栈的集成。

依赖规则:整洁架构在层与层之间有一个非常明确的依赖关系,外层的逻辑依赖内层的逻辑 (图中黑色箭头指向), 但是内层的代码不可以依赖外层。

3.2. 组内分层实现调研

笔者在调研时也参考了组内的一些分层代码实现,发现组内大部分代码在具体实现上基本未按照由内至外的单向依赖关系,组内实现更倾向于是 DDD 的实践规范。

DDD

整洁架构

分层逻辑

按业务领域划分(如用户、订单域)

按技术职责分层(实体→用例→适配器→框架)

依赖方向

允许跨层调用(如应用层调用领域层)

严格单向依赖(内层不感知外层)

与框架的关系

不强制解耦,可结合具体框架实现

核心逻辑必须与框架解耦

典型组件

聚合根、领域事件、防腐层

用例控制器、适配器、依赖注入容器

为了探讨整洁架构在积分商城落地的可能性,笔者梳理了组内分层开发的一些常见不符合整洁架构规范的实现:

  • 在实体层、用例层更新与框架强耦合的 store 数据,以便直接触发视图更新

  • 无论是从实体概念本身、还是从整洁架构设计来看,实体都应该是高聚合的属性及与实体行为相关的方法,不应该绑定 store

  • 用例层一般是组装实体数据、方法以完成业务逻辑,不应该耦合与框架强相关的 store

本质还是因为 vue3 提供了响应式能力,自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。在 vue 里直接更新 store 十分便捷,修改即所得。

  • 在实体层调用防腐层(适配器层)方法更新实体属性:防腐层在前端一般是请求后端、终端接口,合理的操作应该是在适配器层调用接口后通过实例层调用实体方法来更新实体属性

  • 同上,实体本身应该是高聚合的属性及与实体行为相关的方法,与后台接口实现无关

现有业务大部分实现在实体里关联了 store,以便直接把防腐层接口数据写入 store 来便捷更新 UI。依然是利用了 vue 的响应式能力。

  • 在实体层、用例层耦合了 UI 视图:逻辑处理应该和视图分离

  • 保持逻辑处理和视图分离是一个重要的设计原则,尤其是在使用分层架构时。这样可以提高代码的可移植性、可维护性、可测试性和可扩展性等

  • 业务实际开发常见的不规范处理主要集中在针对内部不满足条件/错误的接口响应 toast 提示用户

  • 就前端而言:如果 toast 组件封装于前端基础库且实现与框架无关,在用例层调用影响可控;但考虑特殊业务有额外的 toast 样式适配,这样也会导致逻辑和视图业务强耦合

  • 不满足逻辑和视图分离的规范又要捕获接口错误以 toast,需要更优雅地解决

笔者认为产生该现象的原因如下:

1、开发效率优先

  1. 快速迭代需求:业务方常要求“先上线再优化”,直接在接口处处理错误可快速实现功能。

  2. 心智负担低:开发者无需设计中间层,减少对全局架构的思考成本

2、框架特性诱导

  1. 代码模板惯性:主流框架(如 Vue/React)的官方示例常将 API 调用与 UI 组件写在一起。

3、错误反馈即时性

  1. 开发体验驱动:前端需要实时反馈(如网络错误),直接处理能缩短开发感知路径。

按照整洁架构的规范,可以针对上述错误处理加一个中间件统一处理,通过事件总线的方式触发 UI 更新;或者使用全局错误 store 管理,在统一入口处触发 UI 更新。

由于工程量较大,笔者在实践时并未针对性处理,而是直接向外部抛出错误,视图层捕获异常自行处理。

方案

优点

缺点

事件驱动的方式

灵活,可以处理多种类型的事件。

适合复杂的交互场景。

实现相对复杂。

需要管理事件的订阅和发布。

使用中间层

清晰的职责分离。

通过中间层解耦用例层和视图层。

引入了额外的中间层。

增加了系统的复杂性。

依赖注入

灵活性高。

实现适合依赖注入框架。

引入了额外的中间层。

增加了系统的复杂性。

状态管理:全局错误状态

错误集中管理。

异常处理

简单直接。

利用现有的异常处理机制。

代码简洁。

需要小心控制异常的抛出和捕获。

过度使用异常处理可能难以维护。

有额外的性能开销。

3.3. 积分商城现状

调研完组内实现后,笔者对积分商城的实现也进行了梳理。具体到业务场景,普遍存在 3.2 内的问题,其中最严重的问题就是逻辑和 UI 严重耦合:兑换逻辑耦合了页面数据更新、订单弹窗展示、兑换结果弹窗的UI展示。导致了一系列问题:

  • 代码阅读困难:积分商城每类商品都有独特的兑换逻辑及视图展示,每个兑换逻辑又耦合了视图、UI 状态,兑换结束还需要调用接口触发页面视图更新,承担的职责过多;各个商品业务逻辑散落在各个文件中,需按照调用链路来理解业务。

  • 无法复用导致代码臃肿:每类商品自己维护自己的特异性逻辑,后续迭代(eg.游戏道具新增判断渠道染色逻辑)需要在游戏道具对应的每个品类原兑换逻辑上新增分支处理,加速代码臃肿,新增加的商品种类又继续在新的兑换逻辑里耦合对应 UI 逻辑。

  • 复杂度高:数据流混乱,呈现螺旋网状调用,牵一发而动全身。

  • 兑换接口入参结构变更,每个商品对应兑换文件传入的参数都要变更

  • 兑换接口出参变更,每个商品对应兑换文件对应的视图处理也要变更

3.4. 积分商城整洁架构实践

按照整洁架构的思想,合理的解决途径是:

1、把兑换弹窗、兑换结果弹窗等 UI 逻辑剥离,兑换用例只需处理兑换这一动作的逻辑,只涉及数据的流转。

2、组件视图层消费兑换逻辑流转出的数据、调用方法来处理视图逻辑数据(比如打开关闭订单确认弹窗、兑换结果弹窗、兑换错误处理等)。

3.4.1. 提取领域对象、用例

领域实体抽象(Domain Layer) - 商品实体(spu、sku) 

  • 降低复杂度

  • 将原来 15 个文件减少至 5 个

  • 原来每个商品的兑换逻辑内需要的异化处理信息,按照 sku 商品信息的属性聚合成三类:分类信息、兑换信息、关联游戏信息等

  • 厘清数据流向

  • 兑换逻辑封装到用例层

  • 兑换逻辑只涉及兑换数据的适配和流转、以及商品实体自身处理的方法

  • 逻辑和视图分离

  • 在发起兑换的组件拿到兑换后的数据在视图层触发兑换结果视图

笔者认为对于 TypeScript 项目,我们声明的一个接口类型其实就是对应一个实体,特别是对于只有属性没有特殊方法的实体类声明。

合理的写法是我们定义一个实体的类型,通过适配器把需要消费的数据转成实体类型其实比操控类的写法更简洁,心智负担更轻,同时不违反整洁架构的规范。

用例层设计(Application Layer)

兑换逻辑和视图剥离的同时,还需考虑兑换数据消费后要刷新用户积分余额、刷新兑换商品库存、个人限量等视图信息。

原来的处理是捕获兑换用例层逻辑控制流,在 finally 统一处理,这样就在用例层耦合了 store 的异步处理逻辑,不符合整洁架构的思想。

在实际处理中,我们面临以下问题:

对于 vue3,store 是消耗用例出流数据更新还是在用例里直接更新 store?

  • 如果在用例层直接更新 store,会导致用例层与框架强耦合,违反了整洁架构里与框架无关的设计原则。

  • 而如果在视图层通过 store 刷新,每次用例调用后需要自己更新 store,又会导致代码重复。

  • 此外,考虑整洁架构的单向依赖关系,vue3 的响应性提供了双向绑定,store 和 UI 必定不是单向依赖的关系,变成了一个悖论。

3.4.2. 分层设计

“计算机领域的任何问题都可以通过增加一个间接的中间层来解决”,针对上述问题,我们也打算通过增加中间层来解决,且力图使数据流的流向是更清晰的,甚至希望他是单向的。同时结合整洁架构和 DDD 的思想实践了我们的分层设计。

  • 适配器层

  • 转换数据和接口防腐也应该是与框架无关的,我们将其纳入了不易变的容器内

  • 实体通常意义上可以被认为是前端的一个 TS 类型:适配器依赖实体类型引用保证转换的数据正确性

  • 存储层、消费层用来 store 的更新和消费

  • 增加的这两层用来隔离与框架强耦合的 store,不影响用例、适配、实体层,真正让业务重点逻辑做到与框架无关

  • 副作用:框架迁移需要额外适配这两层逻辑

  • 保证整洁架构的单向依赖关系、数据单向流动,store 不是在适配器层、用例层等自更新

分层设计是美好的,实践是痛苦的,任何设计都有它的双面性。

上述设计在实践中出现了一系列问题:

  • 工作量加大

  • 增加了两个中间层,需要额外实现两个层的代码逻辑

  • 开发者心智负担加重

  • 考虑需求扩展: n 个不同场景(移动端、PC 端、游戏内等)需要展示不同的结果弹窗,那么对应 n 处的弹窗组件我们需要写 n 次刷新逻辑

  • 代码组织臃肿

  • 额外的中间层使得代码组织更加复杂

  • 消费层其实就是对 store 的额外处理,把 UI 需要的 store 数据消费出去

3.4.3. 分层设计的再思考:Vue3 Hooks 的破局之道

在上述实践中发现,传统分层架构与前端开发范式存在根本性冲突: 整洁架构强调的"单向依赖关系"与 Vue3 响应式系统的"双向绑定特性"存在本质矛盾。特别是在积分商城 SPU/SKU 兑换场景中,严格执行整洁结构实现,会观察到以下现象:

  • 视图层逆向驱动数据流:用户点击兑换按钮触发状态更新,但兑换结果需要实时反馈到商品库存、积分余额等状态,形成循环数据流

  • 多层级状态同步困难:用户积分余额、商品库存、兑换状态等状态需要在5+组件间同步更新

  • 副作用管理困境:兑换操作可能触发积分扣除、库存刷新、用户通知等多个副作用

此时若机械套用整洁架构的单向依赖规则,会导致形成事实上的双向数据流,与架构设计初衷背道而驰。这说明在前端场景中严格遵循单向依赖不具备可行性。

4、破局方案:Composition API 的架构赋能

我们通过 Vue3 Hooks 实现分层架构的现代化改造:

1、响应式分层:通过Ref/Reactive包裹领域对象,实现业务逻辑的响应式升级

2、依赖倒置:Hooks 天然支持依赖注入,可通过 provide/inject 实现跨层访问

3、副作用聚合:将 UI 状态、路由监听等副作用集中管理,降低认知负荷

4. Vue3 函数式编程:composition

在前端开发中,Vue 3 的 Composition API 提供了一种全新的组件编写方式,它提供了和 React Hooks 相同级别的逻辑组织能力,通过逻辑功能聚合和高复用性解决了复杂组件的代码组织问题,它使我们能够通过组合函数(cn.vuejs.org/guide/reusa…

4.1. Composition API 与整洁架构的融合

整洁架构保证的是核心逻辑与框架无关,所以我们无需力求保证 UI 和 store 的依赖是单向的。因此,笔者认为 Composition API 为前端整洁架构提供了新的实践路径,核心业务逻辑使用整洁架构剥离框架,UI、store 逻辑积极拥抱框架组合式写法,使业务逻辑的封装更加灵活和高效。

4.2. 架构演进:Vue3 Hooks 与整洁架构的协同范式

我们提出响应式整洁架构的实践方案:

通过整洁架构设计使得兑换逻辑符合整洁结构规范,然后使用 hooks 组织积分商城兑换交互逻辑,这样即可将兑换的核心业务逻辑与交互视图逻辑分离,同时保持代码的可迁移性、可维护性和复用性。以下是一个具体实现的简单示例:

定义领域实体

// 领域实体抽象(Domain Layer)
// 用户实体(User)
class User {
  private balance: number;
  private consumptionRecords: ConsumptionRecord[];
}
// 商品实体(Product)
class Product {
  id: string;
  stock: number;
  checkStock(): boolean {
    return this.stock > 0;
  }
  applyLimitRule(): boolean {
    // 示例:限购规则逻辑
    return true;
  }
}

用例层设计

class ExchangeUseCase {
  constructor(private exchangeService: ExchangeService) {}
  async execute(user: User, product: Product): Promise<Order> {
    if (!product.checkStock()) throw new Error('库存不足');
    user.deductPoints(product.pointsRequired);
    return this.exchangeService.createOrder(user, product);
  }
}

使用 Composition API

// useExchange.ts(组合式业务逻辑)
export const useExchange = (product: Ref<Product>, user: Ref<User>) => {
  // 状态管理(适配器层)
  const orderStore = useOrderStore();
  const error = ref<Error | null>(null);
  // 用例逻辑(应用层)
  const executeExchange = async () => {
    try {
      const exchangeService = inject(ExchangeServiceKey); // 依赖注入
      await exchangeService.deductPoints(user.value, product.value.points);
      orderStore.orderResult = await exchangeService.createOrder(product.value);
    } catch (err) {
      error.value = err;
    }
  };
  // 视图交互(UI层)
  const showConfirmDialog = () => {
    // 调用UI组件库的弹窗方法
    Modal.confirm({ title: "确认兑换?", content: product.value.name });
  };
  return { executeExchange, showConfirmDialog, orderResult, error };
};

通过 Composition API,我们可以将代码组织得更加清晰,同时与整洁架构的分层思想相结合:

Composition API元素

对应整洁架构层级

职责说明

useExchange

UI 层/ hooks 层

组织领域实体与用例的交互流程

useProductAdaptor

UI 层/ hooks 层

转换API数据为领域模型

vue setup

框架层

组件 UI

核心优势对比

维度

传统分层架构

响应式整洁架构

数据流

严格单向

响应式闭环

状态管理

跨层传递props

响应式上下文共享

代码复用

继承/组合模式

Hooks自由组合

框架耦合度

完全解耦

选择性耦合

开发效率

高认知成本

符合前端开发直觉

典型代码量

5层结构

3层响应式

5. 总结

前端架构的本质是在确定性与灵活性间寻找平衡。Vue3 Composition API 允许我们建立更符合前端开发思维的架构模式:

1、响应式分层 > 严格分层

2、组合式复用 > 继承复用

3、渐进式架构 > 传统架构设计

最终衡量架构合理性的唯一标准是: 当业务需求变更时,修改代码是否像在积木塔中抽换模块般自然。这需要我们既掌握架构理论,又能根据具体场景做出务实选择。

在积分商城项目中,我们通过定义领域模型、兑换用例、数据访问接口等方式实现了整洁架构。同时,我们利用 Vue 3 Composition API 的特性,通过自定义 Hooks 封装了不同商品类型的交互逻辑和商品选择逻辑,从而提高了代码的可维护性和复用性。

通过这些实践,我们可以更好地应对积分商城项目中的复杂业务需求,同时为其他复杂前端项目提供了可借鉴的经验。

参考:

如何处理复杂的前端业务代码

如何处理复杂前端业务代码