写点什么

条件型业务规则的抽象与实现——从 Spring Profile 得到的灵感

  • 2020-04-06
  • 本文字数:3336 字

    阅读完需:约 11 分钟

条件型业务规则的抽象与实现——从Spring Profile得到的灵感

摘要

当我们更倾向于使用具体的场景沟通的时候,团队更不容易意识到需要从中寻找稳定的抽象。那么我们需要花费精力去改变用户的思维方式吗,如果需要又应该使用什么样的方式?又或者我们需要使用更抽象的方式来撰写用户故事吗?


最近,有幸参与了一个平台型的项目,该平台支持多种类型的产品预订,并且对于不同的产品类型,支持不同的预订规则。开发团队想尽可能地将主流程实现得更通用,以便在将来更快速地支持新的产品类型。因此,团队决定在主流程中,以产品类型作为条件,决定是否应用某个给定的预订规则。


例如其中有一个对于配送地址的验证规则,它只对特定产品类型(火车票)生效:


(经过简化的用户故事——火车票预订)

作为用户,当我预订火车票时,我应该被告知配送地址无法送达,以便我调整配送地址或选择上门取票


该平台还支持预订酒店,不过由于没有凭据需要配送,所以并不需要检查配送地址是否可达。于是有了以下实现:


public class AddressIsAvailableToDelivery implements PlaceOrderRule {
@Override public void verify(PlaceOrderCommand command) { if (command.getProduct().isTypeOf(RAILWAY)) { // check if the adress is available for delivery the ticket } else { // hotel, makes no sense of deliering tickets } }}
复制代码


预订主流程会依次执行所有的PlaceOrderRule,并由各个PlaceOrderRule的实现决定需要对哪些产品生效。


几个迭代过后有了新的产品需要支持:观光景点,需要配送门票给用户,所以一个类似的用户故事诞生了:


(经过简化的用户故事——门票预订)

作为用户,当我预订景点门票时,我应该被告知配送地址无法送达,以便我调整配送地址或选择上门取票


于是,团队修改了条件表达式,增加了对门票景点的判断:


public class AddressIsAvailableToDelivery implements PlaceOrderRule {
@Override public void verify(PlaceOrderCommand command) { if (command.getProduct().isTypeOf(RAILWAY) || command.getProduct().isTypeOf(SIGHTSEEING)) { // check if the adress is available for delivery the ticket } else { // hotel, makes no sense of deliering tickets } }}
复制代码


到这里,我们闻到到了一些”坏味道”:随着需要验证地址是否达的产品类型增加,代码的圈复杂度会随之升高,意味着需要更多的测试用例来保护。如果将来再有一个新的类型需要检查配送地址是否可达,可以预见此处还会修改;如果系统中有越来越多的条件型业务规则使用当前的方式实现,系统将会越来越脆弱。

找到稳定的抽象

那么问题出在哪里?我认为这是由于没有找到正确的抽象,对于条件型的业务规则,其实是有稳定的步骤的:


  1. 检测当前情况是否需要验证给定的业务规则

  2. 如需要,执行验证;如不需要则略过


如果将AddressIsAvailableToDelivery修改为:


public class AddressIsAvailableToDelivery implements PlaceOrderRule {
@Override public void verify(PlaceOrderCommand command) { if (command.getProduct().isDeliverableAddressRequired()) { // check if the adress is available for delivery the ticket } else { // hotel, makes no sense of deliering tickets } }}
复制代码


这样,条件表达式依赖了稳定的抽象。代码不需要再关心产品类型了,当新的产品加入平台时,只需要知道该产品是否需要验证配送地址就行了。这样就做到了当新产品加入时,核心的规则验证逻辑不需要变更,系统更加稳定。

但这样好难用

工程师对这个重构感到满意,于是找到了 BA(业务分析师),尝试对用户故事做一些变化


(经过简化的用户故事——产品预订)

  1. 作为用户,当我预订需要检查配送地址是否可达的产品时,我应该被告知配送地址无法送达,以便我调整配送地址或选择上门取票

  2. 作为运营人员,我可以设置产品在预订时是否需要检查配送地址,以避免预订后无法配送凭证的情况


BA 对此提出了担心:


  1. 在这个实现方案中,平台运营团队需要为不同的产品设置不同的规则吗?如果规则数量很多,配置起来是不是很麻烦?因为对于某个产品类型,几乎不需要做规则的调整,要求运营团队去配置这些功能在现阶段反而使他们的工作变复杂了

  2. 平台运营团队在平时的工作中,还是按照产品类型的思维在工作的,他们更习惯于”如果产品类型是火车,那么。。。”这样的沟通方式,想要改变这样的思维方式不是那么容易

  3. 修改后的用户故事似乎太抽象了,这样能否帮助团队有效地理解真实的业务场景?


当有大量规则的时候,细粒度的产品配置方式确实有些繁琐,可能需要“配置专家”才能搞定。



(大量规则的时候,细粒度的产品配置方式可能需要”配置专家”才能搞定)


这些担忧不无道理,团队一下子陷入了两难的境地。

意外的灵感

我在阅读该项目一段配置代码的时候发现了这样一个细节:


if (isSmsEnabled()) {   //enable sms sending}
if (isEmailEnabled()) { //enable email sending}


// application.propertiessms.enabled: falseemail.enabled: false
// application-dev.propertiessms.enabled: falseemail.enabled: false
// application-qa.propertiessms.enabled: false email.enabled: true
// application-prod.propertiessms.enabled: true email.enabled: true
复制代码


这段代码表示,在不同的环境中,通过细粒度的配置项,可以精确地控制某个特定功能是否起效。配置项的控制范围很小,而且可能会有许多这样的配置项,但团队根据各个环境上的测试约定,将这些配置项归拢到以环境命名的配置文件中,这是spring boot提供的Profile机制。在启动应用的时候,并不需要一一指定各个配置项的值,而是指定粗粒度的profile即可: --spring.profiles.active=prod


这个方案给了我一个灵感:能否将之前的预订规则表达式类比为配置项,产品类型类比为Profile呢?


在这个思路下,我们保持AddressIsAvailableToDelivery依赖稳定的isDeliverableAddressRequired


public class AddressIsAvailableToDelivery implements PlaceOrderRule {
@Override public void verify(PlaceOrderCommand command) { if (command.getProduct().isDeliverableAddressRequired()) { // check if the adress is available for delivery the ticket } else { // hotel, makes no sense of deliering tickets } }}
复制代码


而在实例化Product时,注入预先设置的配置项,将产品类型和配置项的转换从核心的规则校验中剥离出去。


# railwayplaceOrderRule.RAILWAY.deliverableAddressRequired=trueplaceOrderRule.RAILWAY.anotherConstraint1=falseplaceOrderRule.RAILWAY.anotherConstraint2=false# sightseeingplaceOrderRule.SIGHTSEEING.deliverableAddressRequired=trueplaceOrderRule.SIGHTSEEING.anotherConstraint1=falseplaceOrderRule.SIGHTSEEING.anotherConstraint2=true
复制代码


这样,既能让核心的规则校验依赖稳定的抽象,在变化时保持结构稳定,又暂时避免了给运营团队带来繁琐的配置工作。

遗留的问题

回顾这个过程,实在有些偶然,而且我认为我们只是用了最熟悉的技术手段暂时缓解了之前 BA 提出的第一点担心。


  1. 平台运营团队在平时的工作中,还是按照产品类型的思维在工作的,他们更习惯于”如果产品类型是火车,那么。。。”这样的沟通方式,想要改变这样的思维方式不是那么容易。

  2. 修改后的用户故事感觉太抽象了,这样能否帮助团队有效地理解真实的业务场景?


而 2、3 则涉及到项目团队和干系人对产品的思考方式,当我们更倾向于使用具体的场景沟通的时候,团队更不容易意识到需要从中寻找稳定的抽象。那么我们需要花费精力去改变用户的思维方式吗,如果需要又应该使用什么样的方式?又或者我们需要使用更抽象的方式来撰写用户故事吗?在这里,想听听大家的意见。


作者介绍


周宇刚,拥有 10 年的 JAVA EE 开发经验,在 ThoughtWorks 担任高级咨询师。在加入 ThoughtWorks 之前,在一家国内领先的航旅企业担任架构师,专注于持续交付实践和大型企业应用架构治理。


本文转载自 ThoughtWorks 洞见。


原文链接


https://insights.thoughtworks.cn/identity-rule-abstraction-implementation/


2020-04-06 10:152096

评论 4 条评论

发布
用户头像
配置全放在配置文件里,这就导致了非常依赖于开发人员,跟配置专家应该没有两样,并没有本质上减少什么。
而且在微服务环境下,大多数项目都集成了类似applo、nacos等配置中心。
2020-04-07 11:46
回复
用户头像
请问作者,为什么不把配置存在数据库呢?感觉这种业务细节的配置放在配置文件不太舒服。如果不想让运营团队设置配置,可以不开放相关后台管理入口呀。
2020-04-07 07:55
回复
存数据库,在高并发下应该不是一种好的方案
2020-04-07 08:31
回复
这种配置 更多像是一种开机启动的配置。并不会随时被改变。
2020-04-07 11:47
回复
没有更多了
发现更多内容

Express Flutter SDK 全面支持空安全

ZEGO即构

flutter 大前端 音视频 空安全

万人连麦的幕后技术详解

拍乐云Pano

Vue3异步数据加载组件:suspense

devpoint

Vue 3 8月日更

用好这两个小工具,制作乐谱更高效!

懒得勤快

鬼斧神工!阿里架构师把多线程编程精华全部总结到这份《Java并发手册》里面了

Java 编程 架构 面试 计算机

基于docker的分布式性能测试框架功能验证(三)

FunTester

分布式 性能测试 接口测试 测试框架 测试开发

精彩回顾 | 阿里云 Serverless Developer Meetup 杭州站亮点有这些!

阿里巴巴云原生

阿里云 Serverless 云原生 Meetup

Vue进阶(一):Vue 学习资料汇总

No Silver Bullet

Vue 8月日更

我国数字经济规模已达41万亿元 总量跃居世界第二

CECBC

【案例】服务邮政快递业安全监管 星环科技助力国家邮政局“绿盾”大数据平台建设

星环科技

【前端 · 面试 】HTTP 总结(三)—— HTTP 请求方法

编程三昧

面试 大前端 HTTP 8月日更 HTTP方法

客户选型零代码软件到底在对比哪些方面?

明道云

Vue进阶(二):Vue 项目文件结构介绍

No Silver Bullet

Vue 8月日更 项目结构

使用SpringAop对方法进行增强

捡对象的cy

spring aop

斯图飞腾产品升级!Stratifyd数据分析平台全新改版

全球生态合作伙伴纷至沓来 解码AppGallery 2021“期中成绩单”

叶落便知秋

华为

「SQL数据分析系列」15. 元数据

Databri_AI

sql 脚本 元数据

Snowflake如日中天是否代表Hadoop已死?大数据体系到底是什么?

阿里云大数据AI技术

分布式性能测试框架用例方案设想(三)

FunTester

性能测试 接口测试 测试框架 测试开发

能力圈与焦虑:焦虑是因为自己能力不够吗?

非著名程序员

个人成长 提升认知 焦虑 8月日更

声网Agora发布教育信息化解决方案 助力教育公平提效

声网

人工智能 在线教育

架构实战营 毕业设计

竹林七贤

架构实战营 模块四作业

孫影

架构实战营 #架构实战营

Fil行情:投资fil的成本有哪些?

区块链 分布式存储 IPFS fil fil成本

他是如何被公司辞退,再到1000个小时后拿到阿里巴巴offer的?

Java架构师迁哥

赋能后疫情时代的商业创新,用友BIP的力量

海比研究院

格物致知,零代码训练营第三期顺利结业

明道云

【IT运维】快速解决IT疑难故障就用行云管家!

行云管家

系统运维 堡垒机 IT运维

打造区块链“三大平台” 助推数字化转型

CECBC

阿里高工从入门,基础,进阶到项目实战,全面讲解spring boot

Java 程序员 架构 面试 spring Boot Starter

体验设计落地的四个原则和十个步骤

石云升

用户体验 关键时刻 体验设计 8月日更

条件型业务规则的抽象与实现——从Spring Profile得到的灵感_软件工程_张凯峰_InfoQ精选文章