You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在多抽象维度建模复杂类型?基于函数式DDD的Payment案例

函数式DDD中多维度状态类型建模问题

背景

我尝试采用Scott Wlaschin所著《领域建模的函数式方法》(Domain Modeling Made Functional)中的函数式DDD风格建模,核心思路是将单一类型(比如Payment)拆分为多个类型,精准匹配原类型不同“状态”下的数据结构。

Scott在书中第84页的示例里,把Order拆分为UnvalidatedOrder和ValidatedOrder,用联合类型表示Order:

type Order = 
  | UnvalidatedOrder of { ... }
  | ValidatedOrder of { ValidatedAt: DateTime; ... }

此处使用类F#伪代码简化表述

但这种方案只针对“验证”这单一变化维度有效,如果类型存在两个变化维度,该怎么处理?

Payment建模示例

以Payment模型为例,需求如下:

需求1:Payment状态流转

Payment需处于New、InProgress、Complete三种有效状态;仅New状态可转为InProgress,仅InProgress状态可转为Complete。

需求2:Payment完成结果

处于Complete状态的Payment需要包含结果:Approved、Cancelled、Declined、Failed。其中Approved需附带ApprovedAmount,其余结果需附带Reason。

需求3:基础信息

所有Payment都必须包含唯一Guid标识和RequestedAmount。

我的初始建模尝试

type PaymentResult =
  | Approved of { ApprovedAmount: decimal }
  | Cancelled of { Reason: string; }
  | Declined of { Reason: string; }
  | Failed of { Reason: string; }

type PaymentStatus = 
  | New
  | InProgress of { StartedAt: DateTime; }
  | Complete of { 
      StartedAt: DateTime; 
      CompletedAt: DateTime; 
    }

type Payment = {
  Id: Guid;
  RequestedAmount: decimal;
  Status: PaymentStatus;
}

type StartPayment = (Status: PaymentStatus.New) (Now: DateTime) -> PaymentStatus.InProgress
type FinishPayment = (Status: PaymentStatus.InProgress) (Now: DateTime) (Result: PaymentResult) -> PaymentStatus.Complete

这个方案的问题在于,部分Payment相关信息被塞进了PaymentStatus里,但PaymentStatus应该只存储和状态本身相关的数据。

按照Scott的方式拆分后的模型

按照Scott的思路拆分Payment后,得到如下模型:

type Payment =
  | NewPayment of { Id: Guid; RequestedAmount: decimal; }
  | InProgressPayment of { Id: Guid; RequestedAmount: decimal; StartedAt: DateTime; }
  | CompletedPayment of { 
      Id: Guid; 
      RequestedAmount: decimal;
      StartedAt: DateTime; 
      CompletedAt: DateTime; 
      Result: PaymentResult; 
    }

这个模型把Payment的所有数据都保留在各自的类型记录中,但如果新增“任意状态的Payment都可被废弃”的需求,仅针对废弃维度建模会得到:

type Payment =
  | NonAbandonedPayment of { ... }
  | AbandonedPayment of { AbandonedAt: DateTime; ... }

如果要同时结合“状态”和“废弃”两个维度,就需要创建6个类型来覆盖所有组合:

type Payment =
  | NonAbandonedNewPayment of { Id: Guid; RequestedAmount: decimal; }
  | AbandonedNewPayment of { Id: Guid; RequestedAmount: decimal; AbandonedAt: DateTime; }
  | NonAbandonedInProgressPayment of { Id: Guid; RequestedAmount: decimal; StartedAt: DateTime; }
  | AbandonedInProgressPayment of { Id: Guid; RequestedAmount: decimal; StartedAt: DateTime; AbandonedAt: DateTime; }
  | NonAbandonedCompletePayment of { Id: Guid; RequestedAmount: decimal; StartedAt: DateTime; CompletedAt: DateTime; Result: PaymentResult; }
  | AbandonedCompletePayment of { Id: Guid; RequestedAmount: decimal; StartedAt: DateTime; CompletedAt: DateTime; Result: PaymentResult; AbandonedAt: DateTime; }

这会产生维度的笛卡尔积,哪怕场景简单也显得非常繁琐。

技术问题

在遵循“类型相关信息均存储于该类型内”的前提下,该如何表示类型的多抽象变化维度?我目前想到的最优方案是放弃这个前提,把PaymentStatus和AbandonedStatus作为属性嵌入单一Payment类型:

type AbandonedStatus =
  | NotAbandoned
  | Abandoned

type PaymentStatus =
  | New
  | InProgress
  | Complete

type Payment = {
  Id: Guid;
  RequestedAmount: decimal;
  Status: PaymentStatus;
  AbandonedStatus: AbandonedStatus
}

内容的提问来源于stack exchange,提问作者Tyler Kasper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 08:55:05