AICon 上海站|日程100%上线,解锁Al未来! 了解详情
写点什么

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

评论 4 条评论

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

查看mac电脑的温度信息, 并且给mac电脑降温

lmymirror

macos Mac terminal

for-range造就循环永动机?快来看看go中for-range的那些事!

Gopher指北

后端 for Go 语言

实践分享丨物联网操作系统中的任务管理

华为云开发者联盟

华为 数据 物联网 进程

深入理解MySQL中事务隔离级别的实现原理

X先生

MySQL 数据库 后端 事务

一文纵览向量检索

华为云开发者联盟

数据 搜索 检索 检查

Electron 快速入门及最新安装教程

程序员学院

Java html 大前端 Electron node,js

H5选图预览到上传最佳实践

阿里云金融线TAM SRE专家服务团队

android H5

牛皮!应届生面试阿里Java岗,七轮过后定级P6,薪资44.8W

面试 计算机基础 编程开发 架构师技能

奈学开发者社区分享:Java - 设计模式的7个设计原则

古月木易

Java 设计模式

数字货币是大势所趋,新冠疫情后必须率先发展DCEP

CECBC

数字货币 银行

初学源码之——银行案例手写IOC和AOP

Java架构师迁哥

bug 回忆录(一)

志学Python

三年筑一“用”:长跑中的智能IP网络

脑极体

第 0 次面试

escray

程序员 面试 面经

奈学开发者社区分享:Java - 设计模式的7个设计原则

奈学教育

Java 设计模式 设计原则

华为全联接2020:环信AI领跑,输出5大行业最佳实践

DT极客

关于深浅拷贝

西贝

Java 大前端 基础

一个草根的日常杂碎(9月28日)

刘新吾

随笔杂谈 生活记录 社会百态

一个草根的日常杂碎(9月27日)

刘新吾

随笔杂谈 生活记录 社会百态

一文领略 HTTP 的前世今生

yes

互联网 网络 HTTP 阿帕网

世界的下一个主宰——人工智能

CECBC

人工智能 智能时代

大学四年我是怎么写操作系统和计算机网络的?掏心掏肺的分享!

小林coding

学习 程序员 计算机网络 操作系统 计算机基础

中国Prime会员独享巅峰64小时超长跨境网购时间

爱极客侠

架构1期第三周作业一

道长

极客大学架构师训练营

关于互联网留存和收益你知道多少—带你走近用户成长体系

滴滴普惠出行

Binder那么弱怎么面大厂?

博文视点Broadview

Java android 通信 移动开发 Android进阶

区块链会替代大数据吗?

CECBC

区块链 大数据

戴尔G系列游戏本助玩家激战英特尔大师挑战赛

E科讯

写给新人算法工程师

峰池

互联网 新人 推荐算法 算法工程师

公有云厂商哪家强?本月UCloud、百度云、阿里云位居三甲——2020年8月云主机性能评测排名

博睿数据

PPT画成这样,述职答辩还能过吗?

小傅哥

Java 小傅哥 流程图 架构师 PPT

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