我把 2000 行下单代码,重构成了一套交易前端架构

81 阅读4分钟

在很多金融类产品里,下单模块几乎永远是最复杂的前端代码之一

一个完整的交易流程通常包括:

  • 普通单 / 条件单
  • 创建订单 / 改单
  • 价格校验
  • 资产校验
  • 风险提示
  • 协议签署
  • 二次确认
  • 输入交易密码
  • 提交订单
  • 结果处理

随着需求不断叠加,很多项目最后都会演变成这样:

async function onOrder() {

  if (!priceValid()) return toast("价格不合法")

  if (!assetEnough()) return toast("资产不足")

  if (needRiskConfirm()) {
    await openRiskDialog()
  }

  if (!signedAgreement()) {
    await openAgreementDialog()
  }

  if (!await openConfirmDialog()) return

  const password = await openPassword()

  const res = await submitOrderApi()

  handleResult(res)

}

组件文件很容易膨胀到:

Order.vue2000

随着业务增长,会出现三个典型问题:

1️⃣ 校验逻辑越来越复杂
2️⃣ 创建单 / 改单到处是 if (isEdit)
3️⃣ 提交流程越来越长

后来我对这块代码做了一次系统性的重构,最终形成了一套 稳定可扩展的交易前端架构

OrderSession
+ OrderFlowStateMachine
+ RuleEngine
+ SubmitPipeline
+ Ports / Adapters

下面把这套架构完整拆解。


一、交易系统为什么容易失控

交易模块有一个典型特点:

流程很长 + 规则很多。

完整流程通常是:

填写订单
 ↓
前置校验
 ↓
风险确认
 ↓
协议签署
 ↓
最终确认
 ↓
输入密码
 ↓
提交订单
 ↓
结果处理

如果所有逻辑都写在组件里,代码结构会变成:

UI + 业务规则 + 流程控制 + API调用

全部混在一起。

解决办法只有一个:

拆层。


二、交易前端架构分层

重构后我们把交易模块拆成五层:

PresentationApplicationDomainPortsAdapters

整体结构如下:

UI (Vue / React)
      ↓
useOrderFlow
      ↓
OrderFlowMachineSubmitPipelineRuleEngineOrderSessionPortsAdapters(API / Store)

每一层职责都非常清晰。


三、OrderSession:统一创建单和改单

交易系统中有一个非常经典的问题:

创建订单
改单

很多项目的代码会变成:

if (isEdit) {
  // 修改逻辑
} else {
  // 创建逻辑
}

时间久了,整个项目会充满:

if (isEdit)

解决方案是引入一个领域模型:

OrderSession

interface OrderSession {

  type: "CREATE" | "EDIT"

  entrustId?: string

  originOrder?: OrderDTO

}

它表示:

用户的一次下单会话。

例如:

CREATE Session → 创建订单
EDIT Session → 修改订单

所有逻辑只依赖 session.type,而不是 isEdit


四、RuleEngine:把校验逻辑规则化

原来的校验代码通常是:

if (!priceValid()) return toast()

if (!assetEnough()) return toast()

if (priceDiffTooLarge()) openRiskDialog()

随着需求增加,preVerify 会变成一大坨 if

解决方案是 规则引擎化

定义统一结果结构:

interface ValidationResult {

  code: string

  severity: "block" | "warn"

  message: string
}

规则写成纯函数:

function priceRule(ctx): ValidationResult | null

function assetRule(ctx): ValidationResult | null

统一执行:

function runRules(ctx, rules) {

  return rules
    .map(rule => rule(ctx))
    .filter(Boolean)

}

新增规则只需要:

新增一个函数

而不需要修改原有代码。


五、SubmitPipeline:拆解提交流程

原来的提交流程通常是:

verify
↓
risk confirm
↓
confirm
↓
password
↓
submit

我们把它拆成 Pipeline

ValidateStepRiskConfirmStepAgreementStepFinalConfirmStepPasswordStepSubmitStepResultStep

代码实现:

async function runPipeline(ctx, steps) {

  for (const step of steps) {

    await step(ctx)

  }

}

好处是:

流程步骤可以自由插拔。

例如新增:

冷静期确认

只需要增加一个 step。


六、OrderFlowStateMachine:状态机驱动流程

交易流程本质上是一个 状态机

定义状态:

FORM
VALIDATING
RISK_CONFIRM
PASSWORD
SUBMITTING
SUCCESS
FAILED

状态转移:

FORMVALIDATINGRISK_CONFIRMPASSWORDSUBMITTINGSUCCESS

简单实现:

class OrderFlowMachine {

  state = "FORM"

  transition(next) {
    this.state = next
  }

}

UI 只根据状态渲染:

FORM → 表单
RISK_CONFIRM → 风险弹窗
PASSWORD → 密码输入

逻辑会变得非常清晰。


七、Ports / Adapters:隔离外部系统

交易模块通常依赖很多系统:

行情
资产
交易接口
协议系统

如果直接依赖 Store 或 API,会导致代码强耦合。

解决方案是 Ports + Adapters

定义接口:

interface OrderPort {

  submitOrder(command)

}

Adapter 实现:

export const orderAdapter = {

  submitOrder(cmd) {

    return api.trade.submit(cmd)

  }

}

Domain 只依赖:

Port

而不是具体 API。


八、完整目录结构

重构后的目录结构大致如下:

order/

domain/

  OrderSession.ts
  OrderContext.ts

  validation/
    priceRule.ts
    assetRule.ts
    modifyRule.ts

application/

  OrderFlowMachine.ts
  submitPipeline.ts

ports/

  OrderPort.ts
  MarketPort.ts

adapters/

  orderAdapter.ts
  marketAdapter.ts

composables/

  useOrderFlow.ts

结构非常清晰。


九、改造后的效果

重构前:

Order.vue2000

重构后:

UI组件
≈ 300

其余逻辑全部放在:

Domain
Application

新增规则或流程时:

新增 Rule
新增 Step

而不是修改原有代码。


十、总结

复杂交易系统的稳定架构通常由五个核心模块组成:

OrderSession
+
StateMachine
+
RuleEngine
+
SubmitPipeline
+
Ports / Adapters

简单理解就是:

Session 管数据
StateMachine 管流程
RuleEngine 管规则
Pipeline 管步骤
Ports 管依赖

当这些模块拆清楚之后,下单系统就会变成:

稳定、清晰、可扩展。

这也是很多大型金融系统前端常见的架构模式。