代数化函数式领域建模的案例


我们可以完全实现一个运算,而不需要查看任何组合运算的具体实现。就像你在数学中操作代数表达式一样。

假设将generateTrades作为领域服务 TradingService 的一部分。其目的是生成当天发生的所有交易(操作的输入),并由用户(操作的输入)在证券交易机构的后台执行。

trait TradingService:

  /** 按特定用户生成当天的交易。具体步骤如下
    *
    * 1.查询当天的所有执行情况
    * 2.按执行顺序分组
    * 3.对于每个订单号,从订单详情中获取账号
    * 4.将交易分配到客户账户
    * 5.存储交易
    */

  def generateTrades(
      date: LocalDate,
      userId: UserId
  ): ZStream[Any, Throwable, Trade] =
    queryExecutionsForDate(date)                        
// 1
      .groupByKey(_.orderNo):                            
// 2
        case (orderNo, executions) =>
          executions
            .via(getAccountNoFromExecution)              
// 3
            .via(allocateTradeToClientAccount(userId))  
// 4
            .via(storeTrades)                            
// 5

 
/** 3、从执行流中获取客户账号
    */

  def getAccountNoFromExecution: ZPipeline[Any, Throwable, Execution, (Execution, AccountNo)]

 
/**4、从执行中生成交易并分配到相关客户账户
    */

  def allocateTradeToClientAccount(userId: UserId): ZPipeline[Any, Throwable, (Execution, AccountNo), Trade]

 
/**5、将交易持久化到数据库并返回存储的交易
    */

  def storeTrades: ZPipeline[Any, Throwable, Trade, Trade]

 
/**1、将交易持久化到数据库并返回存储的交易
    */

  def queryExecutionsForDate(date: LocalDate): ZStream[Any, Throwable, Execution]

在下面的示例中,请注意构成代数建模核心的以下 2 个原则:

  1. 方法规范中作为注释提到的步骤序列,与实现 generateTrades 时使用的操作类型一一对应。上面代码中注释了实现中的步骤 1、2、3、4、5、6。
  2. generateTrades 的实现与我们在此使用的 queryExecutionsForDate、getAccountNoFromExecution 等操作的实现完全分离。只需遵循这些操作的类型,并使用适当的组合器进行组合即可。