FE BLL 架构:前端复杂业务的逻辑治理方案

一、概述

FE BLL(Frontend Business Logic Layer) 是一套面向前端复杂业务的逻辑治理架构,核心是落地领域驱动设计(DDD) 理念 ------ 通过标准化目录结构与事件驱动机制,将业务逻辑与视图逻辑彻底解耦,解决大型前端应用中 "逻辑散、耦合重、维护难" 的问题

二、架构设计

2.1 目录规范

FE BLL 采用 "分层 + 领域划分" 的目录结构,核心分为业务逻辑层(BLL )和视图层(View),示例如下:

typescript 复制代码
src/
├── bll-business/       # BLL层:按业务域封装逻辑
│   ├── main-flow/      # 主流程业务域
│   │   ├── handover/   # 交接记录子域
│   │   │   ├── actions.ts   # 业务逻辑执行单元
│   │   │   ├── index.ts     # 业务上下文容器
│   │   │   ├── schema.ts    # UI配置单一来源
│   │   │   └── types.ts     # 业务类型定义
├── views/              # View层:仅负责UI展示
│   ├── main-flow/
│   │   ├── handover-record-list.vue  # 交接记录页面

该结构确保每个业务域的逻辑、配置、类型独立封装,便于团队协作与模块复用

2.2 BLL 层核心文件

BLL 层由 3 个职责明确的核心文件组成,实现 "业务逻辑的内聚与隔离"

2.2.1 actions.ts - 业务逻辑执行单元

actions.tsBLL 层的 "逻辑核心",负责封装所有业务行为(数据请求、参数校验、状态变更、事件触发等)

核心特点
  • 支持异步操作处理,适配接口调用等场景
  • 逻辑内聚:同一业务的所有操作(查询 / 创建 / 批量处理)集中管理
  • 解耦视图 :通过 $emit 触发事件,不直接依赖任何 UI 代码
主要功能
  1. 数据获取:处理列表查询、详情获取等接口请求
  2. 业务操作 :处理 "创建 / 编辑 / 删除" 等核心业务行为
  3. 状态管理 :通过 updateStore 更新业务数据
  4. 事件触发 :通过 $emit 向视图层传递业务结果
2.2.2 index.ts - 业务上下文容器

index.tsView 层与 BLL 层的 "交互入口",核心职责如下

  • 初始化业务基础数据(如下拉选择列表)
  • 整合 actions.tsschema.ts,构建完整业务上下文
  • 统一管理业务状态(Store)的初始值
2.2.3 schema.ts - UI 配置单一来源

schema.ts"UI 配置的集中管理中心",主要功能如下

  • 表格配置:定义列名、字段、宽度、插槽等
  • 筛选器配置:定义输入框、下拉选择等筛选组件
  • 表单配置:定义字段类型、验证规则、交互属性等

三、代码示例

3.1 actions.ts:交接记录的业务逻辑实现

typescript 复制代码
export const actions = {
  // 获取交接记录列表
  async getHandoverRecordList({ updateStore, store }, res: Function, pagination: Pagination) {
    let result: { data: HandoverTaskSearchResponse[], total: number } = {
      data: [],
      total: 0,
    };
    
    try {
      // 从Store读取搜索参数
      const params = { ...store.searchParams };
      // 接口请求
      const { data } = await api.handoverTaskSearch(params);
      // 处理数据并更新Store
      const list = data?.list ?? [];
      updateStore(list, 'listData.list');
      
      result = { data: list, total: data?.total ?? 0 };
    } catch (e) {
      console.error('get handover record list error: ', e);
    } finally {
      // 同步返回结果
      res(result);
    }
  },

  // 创建交接任务
  async createHandoverTask({ $options, $emit }) {
    try {
      const { data: { handover_task_number }} = await api.handoverTaskCreate();
      // 触发视图层事件(不直接处理UI)
      $emit(eventTypes.onCreateSuccess, handover_task_number);
    } catch (e) {
      console.error('create handover task error: ', e);
    }
  },

  // 批量打印交接单(含二次确认逻辑)
  async batchPrintHandover({ store, $options }) {
    const { selectedRows } = store;
    
    // 校验选中项
    if (!selectedRows.length) {
      $options.$message.error(i18n('Please select task!'));
      return;
    }
    
    // 二次确认:含进行中任务的风险提示
    const hasOngoing = selectedRows.some(item => item.status === 1);
    if (hasOngoing) {
      const confirm = await $options.$confirm(
        i18n('Ongoing tasks will be marked as completed if printed'),
        i18n('Tips'),
        { confirmButtonText: i18n('Print') }
      );
      if (!confirm) return;
    }
    
    try {
      const { data: { print_data, email_rule_not_matched_list: notMatchedList }} = 
        await api.handoverBatchPrintSheetRecordCreate({ 
          task_number_list: selectedRows.map(v => v.task_number) 
        });
      // 执行打印逻辑
      print_data?.forEach(v => v.html && print_transit_handover(v.html));
      // 提示未匹配规则的任务
      if (notMatchedList?.length) {
        $options.$message.error(i18n('Task %s has no matching rule', [notMatchedList.join(',')]));
      }
    } catch (e) {
      console.error('batch print handover error: ', e);
    }
  },

  // 其他逻辑:筛选变更、表单处理等...
  onFilterChange({ updateStore }, key: string, _: string, value: string[]) {
    if (key === 'destination_list') {
      const newValue = value?.includes(ALL_VALUE) 
        ? [ALL_VALUE] 
        : value?.filter(v => v !== ALL_VALUE) ?? [];
      updateStore(newValue, 'searchParams.destination_list');
    }
  }
};

3.2 index.ts:交接记录的上下文容器

typescript 复制代码
export default class HandoverRecordListContext {
  schema: any = {};
  
  async init() {
    // 并行初始化业务基础数据
    const [ilhRes, serviceCodeRes, changeFlRes, tpTagRes] = await Promise.all([
      api.getIhList(),
      api.getServiceCodeList(),
      api.getChangeFlList(),
      api.getTpTagList()
    ]);
    
    // 转换为下拉选择格式
    const ilhList = (ilhRes?.data ?? []).map((item: string) => ({ value: item, label: item }));
    const serviceCodeList = (serviceCodeRes?.data?.service_code_list ?? []).map((item: string) => ({
      value: item, label: item
    }));
    const changeFlList = (changeFlRes?.data?.list ?? []).map((value: string) => ({
      value, label: i18n(value)
    }));
    const tpOptions = Object.keys(tpMap).map(key => ({ label: key, value: tpMap[key] }));
    
    // 组装Schema(关联表格/筛选器/表单配置)
    this.schema = {
      tableListSchema: getHandoverRecordListTableColumns(),
      filterSchema: getHandoverRecordListFilter(
        serviceCodeRes?.data?.service_code_of_destination_map ?? {},
        serviceCodeList, ilhList, tpOptions
      ),
      changeFormSchema: getChangeSchema(changeFlList, tpTagRes?.data ?? [])
    };
  }
}

3.3 schema.ts:交接记录的 UI 配置

typescript 复制代码
// 表格列配置
export const getHandoverRecordListTableColumns = (): JSCSchema.ExtendSchema[] => {
  return [
    {
      label: 'Task ID',
      field: 'task_number',
      xData: { fixed: 'left', width: 170, slots: { task_number: '' } }
    },
    { label: 'Operator', field: 'operator', xData: { width: 100 } },
    {
      label: 'Action',
      field: 'action',
      xData: { fixed: 'right', width: 150, slots: { action: '' } }
    }
  ];
};

// 筛选器配置
export const getHandoverRecordListFilter = (
  serviceCodeMap: Record<string, any> = {},
  serviceCodeList: Option[] = [],
  ilhList: LHLineItem[] = [],
  tpOptions: Option[] = [],
): JSCSchema.ExtendSchema[] => {
  const countryOptions = generateEnums([ALL_VALUE, ...Object.keys(serviceCodeMap).sort()]);
  
  return [
    { type: 'input', field: 'task_number', label: 'Task ID' },
    {
      type: 'select',
      field: 'service_code',
      label: 'Service Code',
      xProps: {
        'use-virtual': true,
        'options': [ALL_OPTIONS, ...serviceCodeList]
      }
    }
  ];
};

3.4 View 层:交接记录页面(仅负责 UI)

typescript 复制代码
import { promisify } from 'bll-cerberus/dist/bll-core';
import { HandoverRecordListContext } from '@/bll-business/main-flow/handover/handover-record-list';

export default class HandoverRecordList extends Vue {
  // 初始化BLL上下文
  bll = new HandoverRecordListContext();
  
  // 关联BLL的事件、Schema、状态
  get bllEventTypes() { return this.bll.eventTypes; }
  get bllSchema() { return this.bll.schema ?? {}; }
  get bllStore() { return this.bll.store ?? {}; }

  // 转换Schema为UI组件可用的配置
  get listTableSchema() {
    return getProTableColumns(this.bllSchema.tableListSchema ?? []).map(item => ({
      ...item, label: this.$i18n(item.label)
    }));
  }

  // 注册事件监听(组件创建时)
  created() {
    this.bll.$on(this.bllEventTypes.fetchData, this.reloadTable);
    this.bll.$on(this.bllEventTypes.onCreateSuccess, this.handleCreateSuccess);
  }

  // 触发BLL的查询逻辑
  async getList(params: Record<string, any>) {
    return await promisify(this.bll, this.bllEventTypes.onListSearch, params);
  }

  // 视图交互:创建任务(仅触发事件,不处理业务)
  handleCreateClick() {
    this.bll.$emit(this.bllEventTypes.onCreate);
  }

  // 视图响应:创建成功后跳转页面
  handleCreateSuccess(_: any, task_number: string) {
    this.$router.push({ name: 'main.flow.handover.task', query: { task_number } });
  }
}

四、实践分析

以上图为例,分析 FE BLL 架构中 "视图层 - 事件中心 - 业务逻辑层 - API 层" 的协作链路与各层的职责边界

4.1 架构优势

1. 职责边界绝对清晰
  • BLL 层(业务逻辑层):负责封装所有业务逻辑,为 View 层提供完整的业务支持。该层专注于定义 "业务应该做什么",严格避免包含任何 UI 相关代码
  • View 层(视图层):仅负责 "页面应该怎么展示与交互触发",不包含任何业务逻辑
2. 事件驱动解耦复杂逻辑
  • 通过事件订阅 / 发布 ,实现 "业务行为" 与 "视图响应" 的解耦(如创建成功后 View 层跳转,BLL 层无需关心)
  • 支持异步操作的同步化调用,适配多步骤、联动型业务场景
3. 团队协作效率提升
  • 标准化目录让新人快速定位代码(eg:找业务逻辑 → actions.ts)
  • 同一业务域的代码集中管理,避免跨文件协作冲突
4. 低改造成本适配现有项目
  • 无需升级框架版本,可在现有 Vue / React 项目中 "局部试点、逐步推广"
  • 不依赖特定状态管理库,适配 Vuex、Pinia、Redux 等方案

4.2 实践要点

1. Store 数据的 "业务域划分"
  • 仅将 "业务核心数据" (列表、搜索参数)放入 Store
  • 按业务域拆分 Store (如交接记录 Store转运规则 Store),避免全局臃肿
2. 事件的 "精准订阅"
  • TypeScript 枚举定义事件类型(如 HandoverEventTypes),避免拼写错误
  • 仅订阅 "与当前视图相关的事件",减少不必要的监听。
3. 视图与逻辑的 "严格边界"
  • BLL 层禁止调用 $message / $routerUI 工具
  • View 层禁止写 "参数校验、接口调用" 等业务逻辑

4.3 架构思考:BLL vs 传统 Mixins

疑问

把业务逻辑放到 Mixins 中,用同步方法调用,是否也能解耦,而不需要使用 "事件驱动" 这种异步机制来通信?

结合场景判断
  • Mixins 的局限 :同步调用会导致 "视图层直接依赖业务逻辑实现",耦合度更高;且无法实现跨组件无耦合通信
  • 事件机制的价值:对于复杂异步业务(如批量操作需二次确认),事件能解耦逻辑;但对于简单同步逻辑(如表单字段变更),事件会增加复杂度
结论

FE BLL"事件驱动""复杂业务的解耦工具" ,而非 "通用方案"

4.4 潜在问题与优化方向

1. 事件类型的类型安全问题

问题字符串类型的事件 易出错且无提示
优化 :用 TypeScript 枚举定义事件类型,配合类型守卫强化检查

2. UI 与业务逻辑的混淆风险

问题BLL 层调用 UI 工具会导致依赖耦合
优化BLL 层仅触发事件,由 View 层订阅后处理 UI 交互(如提示弹窗)

五、总结

FE BLL 是前端复杂业务的 "逻辑治理工具" ,核心价值是通过 DDD 理念,将散落在视图中的业务逻辑集中封装,实现 "业务逻辑可复用、视图代码可维护"

5.1 应用场景

  • 适合场景:多步骤、异步联动、多人协作的复杂业务(如订单流程、转运规则)
  • 不适合场景 :简单 CRUD 操作(会显得过度设计)

5.2 落地建议

  1. 先在核心复杂业务模块试点,再逐步推广
  2. 团队统一目录结构与编码规范
  3. 结合业务复杂度,灵活选择 "事件驱动""同步调用"
相关推荐
玄同7651 小时前
LangChain 1.0 框架全面解析:从架构到实践
人工智能·深度学习·自然语言处理·中间件·架构·langchain·rag
Aloudata1 小时前
高并发指标中台选型:Aloudata CAN 横向扩展与架构稳定性深度评估
数据库·架构·数据分析·etl·指标平台
止观止2 小时前
拒绝“都是 string”:品牌类型与领域驱动设计 (DDD)
前端·typescript
没有bug.的程序员2 小时前
Istio 服务网格:流量治理内核、故障注入实战与云原生韧性架构深度指南
spring boot·云原生·架构·istio·流量治理·故障注入·韧性架构
芸简新章2 小时前
微前端:从原理到实践,解锁复杂前端架构的模块化密码
前端·架构
范纹杉想快点毕业2 小时前
状态机设计模式与嵌入式系统开发完整指南
java·开发语言·网络·数据库·mongodb·设计模式·架构
pusheng20252 小时前
燃料电池电化学传感器在硫化物固态电池安全监测中的技术优势解析
前端·人工智能·安全
それども2 小时前
Excel文件解析 - SAX和DOM方式的区别
java·前端·excel
それども2 小时前
Excel文件解析 - SAX startRow cell endRow 执行顺序
java·前端·excel