写点什么

条件型业务规则的抽象与实现——从 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:152161

评论 4 条评论

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

炸了!阿里又一力作上传GitHub,Spring Cloud Alibaba差距不止一点

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

由于太全被各大厂要求Github连夜下架

Java架构师迁哥

2021全球高性能云计算创新大赛,9月15日重磅启动!

亚马逊云科技 (Amazon Web Services)

云计算

发布60分钟!霸榜Github的阿里面试参考指南,啃透涨薪10k

Java架构师迁哥

32岁的我裸辞了,八年Java老鸟,只因薪水被应届生倒挂,在闭关三个月后拿到阿里Offer,定级P7!

Java架构师迁哥

GitHub星标70K阿里大佬手写的Spring Boot实战手册

Java架构师迁哥

牛客网论坛最具争议的Java面试成神笔记,看过的人都已经成功进入大厂

Java 编程 程序员 架构 面试

Filecoin算力矿池挖矿系统开发案例

薇電13242772558

区块链 IPFS

实时数据引擎系列(五): 关于 SQL Server 与 SQL Server CDC

tapdata

一位年薪 180 万的字节大佬扔给我的四份学习笔记

Java架构师迁哥

诧异!GitHub上竟有阿里专家用800页笔记,只讲MySQL调优而且火了

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

openGauss X ShardingSphere,分布式方案的另一种最佳实践

SphereEx

数据库 开源

如何获取系统错误报告--Bug Report

Changing Lin

9月日更

Vue进阶(幺幺贰):package-lock.json 文件解析

No Silver Bullet

Vue 9月日更

如何高效学习 Kubernetes 知识图谱?

阿里巴巴云原生

Kubernetes 容器 云原生

探秘 JavaScript 世界的神秘数字 1.7976931348623157e+308

清秋

JavaScript 大前端 浮点数 引航计划 IEEE754

开源即巅峰!阿里首次分享:Java架构师全栈“成长笔记”

Java架构师迁哥

中原银行分布式批处理调度平台介绍

中原银行

分布式 批处理 中原银行

北鲲云超算平台为何能够被高性能计算行业认可?

北鲲云

GitHub获120w+star的JDK源码剖析手册,竟出自Alibaba高管之手?

Java 程序员 架构 面试 Alibaba

真香!霸榜掘金首页第一,阿里内部Java性能调优笔记终于开源了

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

阿里P8手抄本惨遭泄露,并出现病毒式传播,致28人斩获大厂offer

Java架构师迁哥

The Data Way Vol.4|开源是创造软件诸多方法中最好的一种形式

SphereEx

数据库 开源

JavaScript进阶(二)上

Augus

JavaScript 9月日更

网络攻防学习笔记 Day137

穿过生命散发芬芳

网络基础 9月日更

Flutter IM跨端架构设计和实现

OpenIM

一文揭示,DevOps与企业数字化究竟有何联系?

飞算JavaAI开发助手

DevOps 自动化 软件工程

云备份和恢复的优缺点

云计算

“京东商城”亿级高并发秒杀系统到底是怎么设计的?自己做该如何下手?

Java架构师迁哥

足足541页!费了大力气才从GitHub上扒下的阿里Java优化“圣经”

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

网络协议之NAT穿透原理

Linux服务器开发

网络协议 p2p NAT Linux服务器开发 网络穿透

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