本章导读

通过本文,您可以了解:

  1. 什么是场景(UseCase)
  2. 为什么要做场景级抽象
  3. 一个场景级SDK抽象的样例

环境准备

您需要:

  1. 用于运行程序的IDE(集成开发环境),比如IntelliJ IDEA 或其类似工具;
  2. Java™ Development Kit (JDK),需要JDK 8及以上版本

什么是场景 UseCase

关于这个概念的介绍,大家可以参考《架构整洁之道》的第22章节,关于“用例层”的描述:

软件的⽤例层中通常包含的是特定应⽤场景下的业务逻辑,这⾥⾯封装并实现了整个系统的所有⽤例。这些⽤例引导了数据在业务实体之间的流 ⼊/流出,并指挥着业务实体利⽤其中的关键业务逻辑来实现⽤例的设计⽬标。

为什么要做场景级抽象

在用例层,随着业务场景的不断丰富,复杂度也会随之急剧变大。在场景细节发生变化时,我们不希望这层的改变会影响到业务实体、UI等。如果一个场景没有进行很好的抽象,就很难做到不同场景的逻辑隔离以及场景级复用。 在TOGAF中,有一个“企业连续体(Enterprise Continumm)”概念,这个概念中比较关键的要点就是如何能沉淀不同级别的组件进行复用:

  • 架构统一体(Architecture Continuum): 该统一体能从特定架构中提取出可复用的组件到仓库中(Repository),为后续的类似业务的重用(Gerneralization for future re-use)。在具体应用中,可以从组件仓库中选择可复用的组件并进行与实际应用场景适配(Adaptation for use)
  • 解决方案统一体(Solutions Continuum):与架构统一体类似,在面对不同的市场,需要能从可复用的解决方案库中选择并快速复制。对于新兴市场的交付,也能提取成可复用的解决方案到资产库中

场景级抽象,可以理解为是一种解决方案统一体。 他和功能级复用最大的区别在于,场景级他是跨越多个业务活动、多个角色,在某个场景下能做到全流程的复用。场景级复用,能够让业务创新更加敏捷、所需迭代周期更短!

场景级 SDK 抽象样例

我们在电商体系下,我们在可以对各种交易场景、交易模式进行总结与归纳:

  • 担保交易模式:担保交易指提供第三方担保账户,在交易过程中保管交易资金,在获得买卖双方确认之后才完成资金的转移。这种方式避免了线上交易,由于信任问题可能导致的交易失败,同时又保证了买卖双方的利益
  • 预售交易模式:预售交易是一种分阶段交易,在下单过程中会有定金支付、尾款支付两个阶段。定金支付阶段支付一部分费用,尾款阶段支付剩下的费用。卖家收到尾款后,进行发货。这种模式,比较常用于大促营销活动中,能起到锁定买家,库存确定性等作用
  • 电子凭证交易模式:电子凭证交易是指通过线上购买/领取凭证,然后到线下进行核销凭证,最终完成消费或提货的交易流程。其主要的特点是采用了以凭证为载体,基于身份的校验机制。常见的凭证有:电子兑换券、提货券、电子票类、电子会员卡等。凭证的载体通常有串码、二维码等形式

我们以日常生活中常见的线上买电影票为例。 这种场景交易一般不会把电影票以实物物流的方式邮寄给消费者,而是用一个二维码或者串码通过短信、App发送给消费者。消费者凭借这个二维码或者串码,可以在观影前到电影院自助取票。电影票一旦取出,这个电子凭证就会被核销,不能再次使用。这是一种单次核销的电子凭证交易模式。

再比如,我们可以在线上购买并预约一个汽车4S保养服务。在预约成功后,商家会给消费者通过短信或者App发送服务兑换码。消费者可以凭借这个二维码去汽车4S店享受保养服务,比如洗车。汽车保养服务可能是一个套餐,比如这个套餐包含了12次洗车服务。那么,这个电子凭证所包含的权益,是可以进行多次核销的。

我们不难看出,电子凭证交易模式,是可以被不同形态的业务进行解决方案级复用的。业务使用方不需要自行完整的去构建凭证生成、凭证发码、凭证核销、凭证退货等基础设施。他们只需要去申明使用这个场景/模式,并在一些特殊点上定义规则,比如核销这个点:

  • 电影票业务:需要申明在使用电子凭证这个场景模式时,他的核销方式是“单次核销”
  • 汽车4S保养服务:需要申明在使用电子凭证这个场景模式时,他的核销方式是“多次核销”

我们可以在电子凭证这个场景模板上,定义场景级开发SDK。我们针对凭证是否允许多次核销,可以定义如下:

public interface ETicketTradeSDK extends IBusinessExt {

    String EXT_ETICKET_IS_SUPPORT_MULTI_WRITE_OFF = "EXT_ETICKET_IS_SUPPORT_MULTI_WRITE_OFF";

    @Extension(
            code = EXT_ETICKET_IS_SUPPORT_MULTI_WRITE_OFF,
            name = "Is Voucher support multiple write-off",
            reduceType = ReduceType.NONE
    )
    Boolean isVoucherSupportMultiWriteOff();
}

电影票业务,在使用电子凭证场景时,他可以实现该扩展点,并申明为“不允许多次核销”,如下:

@Realization(codes = HiMovieBusiness.CODE)
public class HiMovieETicketExt extends BlankETicketTradeSDK {

    @Override
    public Boolean isVoucherSupportMultiWriteOff() {
        return false;
    }
}

同样,我们还可以再以预售交易模式举例。预售交易分为定金支付以及尾款支付两个阶段。但是,不同的业务,对于定金支付比例会有不同的要求,有的是要求30%的定金,也有的只要求5%的定金。因此,预售交易场景模式,也可以提炼出“自定义定金支付比例”这个扩展点,让各个业务自行决定。如下:

public interface PreSaleTradeSDK extends IBusinessExt {

    String EXT_PRE_SALE_CUSTOM_DOWN_PAY_RATIO = "EXT_PRE_SALE_CUSTOM_DOWN_PAY_RATIO";

    @Extension(
            code = EXT_PRE_SALE_CUSTOM_DOWN_PAY_RATIO,
            name = "Custom PreSale Down Payment Ratio",
            reduceType = ReduceType.FIRST
    )
    Double getCustomDownPaymentRatio();
}

我们假设电影票业务也有预售(比如大型实景演出、演唱会场景),电影票的预售定金比例,要求是40%。 业务定制逻辑如下:

@Realization(codes = HiMovieBusiness.CODE)
public class HiMoviePreSaleExt extends BlankPreSaleTradeSDK {

    @Override
    public Double getCustomDownPaymentRatio() {
        return 0.4;
    }
}

通过上面两个例子,大家应该可以很容易看出。 面向场景级SDK进行定制,会比较符合逻辑思维。开发与产品可以很好的做到语言一致,代码与需求一致。 业务在进行逻辑定制时,并没有感知到底层的实体结构。 这个结构与数据的转换,被电子凭证、预售交易场景模板封装掉了。

大家都知道,在海量数据下,存在数据库中的订单结构是不可能随意变化。我们一般会采用一些结构化的字段进行业务信息存储,比如K/V结构、JSON结构等。不同的业务场景,最终存到订单上,是会在这个结构化的字段上进行业务信息保存。在上面的这个例子中,电子凭证是否允许多次核销,就是提现在这个 K/V 结构上。 在订单实体的“保存”行为上,我们可以定义一个扩展点,叫做“自定义订单属性”,如下:

public interface OrderLineSaveExt extends IBusinessExt {

    String EXT_ENTITY_ORDER_LINE_CUSTOM_ATTRIBUTES = "EXT_ENTITY_ORDER_LINE_CUSTOM_ATTRIBUTES";

    @Extension(
            code = EXT_ENTITY_ORDER_LINE_CUSTOM_ATTRIBUTES,
            reduceType = ReduceType.NONE
    )
    Map<String, String> getCustomOrderAttributes(OrderLine orderLine);
}

电子凭证交易场景,他可以去实现这个扩展点,从而完成讲有业务语义的表达,完成在实体上的数据映射。 如下:

@Realization(codes = EticketTradeUseCase.CODE)
public class ETicketTradeBusinessExt extends BlankOrderLineSaveExt {

    @Override
    public Map<String, String> getCustomOrderAttributes(OrderLine orderLine) {
        EticketOrderLineAbility ability = new EticketOrderLineAbility(buildEticketOrderLine(orderLine));
        Boolean supportMultiWriteOff = ability.isVoucherSupportMultiWriteOff();
        Map<String, String> output = Maps.newHashMap();
        output.put("e_multi_write_off", supportMultiWriteOff ? "1" : "0");
        return output;
    }
    ......
}

通过这种方式,底层的实体层的代码逻辑会非常干净,他不会感知上层场景的细节变化。而且这种方式,可以很轻松的去应对多场景叠加组合的情况。 比如,本文所附的Sample中,我简单的写了一个业务同时使用 电子凭证场景模板以及 预售交易场景模板的。大家可以运行:org.hiforce.lattice.sample.starter.LatticeUseCaseSample 来观测运行结果,结果如下:

[UseCase]PreSaleTrade load custom down payment ratio: .40
[UseCase]ETicketTrade is support multiple write-off: false
Save OrderLine id: 1, bizCode: hi.movie
-- Total Pay Price: 4000
-- Order Attributes: [e_multi_write_off:0]

关于本文所述样例代码,可访问:https://github.com/hiforce/lattice-sample/tree/main/lattice-usecase-sample