业务代码解构利器--SWAK

摘要: 作者:闲鱼技术-紫思 简介 业务的不断发展、商品类型的不断增多、不断添加的业务需求使得闲鱼的代码出现“bad smell”——平台代码和业务代码耦合严重难以分离;业务和业务之间代码交织缺少拆解。这也是行业中的通病。

作者:闲鱼技术-紫思

简介
业务的不断发展、商品类型的不断增多、不断添加的业务需求使得闲鱼的代码出现“bad smell”——平台代码和业务代码耦合严重难以分离;业务和业务之间代码交织缺少拆解。这也是行业中的通病。为解决此类问题,闲鱼自研了一套技术框架——SWAK。本文带大家一起看看SWAK是怎么解构闲鱼代码的。

SWAK是Swiss Army Knife的简称,众所周知,瑞士×××是一款小巧灵活、适用于多种场景的工具。在闲鱼服务端,SWAK框架也是这样一种小巧灵活、适用于多种场景的技术框架, 它所要使用的场景都具有同一个特点——多实现间的规则化执行。本文将以一个例子开篇,来详细介绍其中的概念。

多实现和规则化执行
熟悉闲鱼的朋友们应该知道,在闲鱼App里面,商品有丰富的表现形式,不妨叫做类型A、类型B和类型C,各种类型也可以有各自的子类型。每种类型的业务逻辑存在一定的共性,但是也存在部分差异——如在分享页面中,subtitle字段的展示逻辑就不尽相同:

这种单一的实现通常会被写成如下的代码:

if(A类型) {
if(A1类型) {
doSomething1();
}else if(A2类型) {
doSomething2();
}
} else if(B类型) {
doSomething3();
} else if(C类型) {
if(C1类型) {
doSomething4();
}else if(C2类型) {
doSomething5();
}
}
类似的代码大家应该都写过不少。逻辑简单的时候写成这样无可厚非,但当逻辑开始变复杂的时候这种写法会具有较多的坏处:

难以抽出公共的逻辑,代码块愈发臃肿。
有较多相同点少量异同点的新类型的实现很难复用原先的代码。
各个类型的代码实际上融合在一块,更改代码可能会影响到其他类型,提高上线风险和测试回归成本。
对于新接手的开发人员来说,理解成本高,上手难度大,无形中降低开发效率。
按照面向对象的思想,获取title的方式对于所有类型都是一致的,应该沉淀成平台逻辑,而获取subtitle就可以抽象成一个接口方法,而类型A、类型B和类型C的宝贝都具有各自的实现而已。对于 获取subtitle这个接口方法来说,它有着多种实现。

那么什么是规则化执行呢?在上面的例子中,我们按照了商品的类型(type)进行了逻辑的分离,但通常情况下并非能分隔地如此彻底。举一个例子,运营团队的划分可能也按照商品类型(type)做划分,也有可能按照类目(category,如手机、3C数码、服饰、图书等)体系来做划分,甚至还有可能按照地域进行划分。那么一个商品可能既会受到商品类型体系的约束,又会受到类目体系的约束,还会受到地域的约束。如果几种约束不一致的话,就会产生冲突。比如subtitle字段,从类型A的视角上来看应该显示价格,在图书类目的视角下或许应该透出出版社——毕竟爱读书的人大多更关注质量而出版社是衡量质量的一个重要标准。是展示价格,还是出版社?或者都展示?如果都展示的话先展示价格还是先展示出版社?如果一行不够放下所有内容又怎么办?无论是上述的哪一种展示方式,背后都是“规则”(在设计模式里,称之为“策略”),代码也无非是按照“规则”进行编写而已。

以上的例子是多实现规则化执行的一个经典场景。类似地,如ABTest、双写等逻辑也是多实现规则化执行的应用场景。

基本思想
在上面的例子中,按照商品的类型或者按照商品的类目进行区分会产生冲突。其实无所谓类型或者类目,对于商品这个对象来说,无非是给其贴上了不同的标签而已——如一个类型A的图书类目宝贝被贴上“类型A”和“图书”两个标签。“类型A”的获取subtitle接口方法对应着一种实现,而“图书”的获取subtitle接口方法又对应着另一个实现。当一个对象被贴多个标签的时候,多个标签对应的实现就会产生冲突。

冲突的解决依赖于“规则”。“规则”最重要的两个部分是——优先级(Priority)和归约(Reduce)策略;执行的先后顺序由优先级决定,而显示第一个实现的结果、显示第二个实现的结果还是两个实现结果的拼接等都是归约策略。“规则”还可以包含如“并行执行方式”和“异常处理方式”等其他组成部分。

如上,可以得出SWAK的基本思想:

分析对象所具有的标签。
分离出不可变的逻辑和可变的逻辑。可变的逻辑抽象成接口。
可变的逻辑根据标签的不同有多种实现。每种实现是独立的,即每种实现是互相隔离的。
当对象同时具有多个标签时,使用优先级和归约策略来解决冲突问题。
值得一提的是,SWAK的基本思想借鉴自阿里巴巴中台的TMF架构,关于TMF的细节可以参考《尽在双11--阿里巴巴技术演进与超越》一书的《基于TMF框架的交易平台架构》章节。

相应地,使用SWAK框架将带来如下的好处:

代码逻辑清晰,可变和不可变一目了然。
代码复用度变高。
可变逻辑按照标签进行隔离,单个标签的实现不会影响到其他标签的实现,降低开发和测试成本。无论是按照“类型”分还是按照类目分,对应的开发和测试同学只需要关注对应的逻辑即可。
新接手的开发人员能够快速理解,轻松上手。
实现原理
相较于运行期才进行根据标签去扫描并加载实现类的方式,SWAK框架更倾向于在静态期就能分析出具有某几个标签的对象在不同的实现方法下会有着怎样的执行逻辑。一方面通过缓存可以明显降低响应时间,另一方面也便于在开发期间发现和排查问题。整体的实现原理可以分成两个部分:注册 和 执行。基本流程如下:

在注册过程中,SWAK框架将会扫描文件(多实现接口、归约策略、冲突优先级采用了Java注解或者XML文件进行了配置,下面的代码示例中介绍多实现接口和其实现类是如何配置的),扫描出的结果都注册到了本地缓存中,而在执行过程中SWAK框架会从本地缓存中直接查找其所需的冲突优先级配置和归约策略等,这样有助于减少响应时间。另外,使用统一的本地缓存有助于进行“可视化的展现”——开发人员可以直观地看到并分析出程序的执行流程;产品经理也可以直观地看到哪些功能点可以方便扩展,哪些地方的优先级需要更新等等,甚至有助于需求的估时和排期。使用统一的本地缓存也为“可视化的配置”提供了可能性,结合阿里内部的Diamond或者Switch框架(轻量级的开关和动态配置项管理框架),可以无需更新代码,仅需推送配置就可以更新冲突优先级,为开发和测试提供了极大的便利。

/**

  • 此处用一个简单的demo演示下基本的配置,实际的业务要远比demo复杂
    */
    @SwakInterface(desc="获取subtitle") // 使用注解声明这是一个多实现接口
    public interface SubtitleFetcher {br/>@SwakMethod
    String fetchSubtitle();
    }

@SwakTag(tags = {"tagA"}) // 使用SwakTag绑定tagA的实现br/>@Component
public class TagASubtitleFetcher implements SubtitleFetcher {
br/>@Override
public String fetchSubtitle() {
return "我是TagA";
}
}

@Component
@SwakTag(tags = {"tagB"}) // 使用SwakTag绑定tagB的实现
public class TagBSubtitleFetcher implements SubtitleFetcher {
br/>@Override
public String fetchSubtitle() {
return "我是TagB";
}
}
闲鱼服务端应用基本都基于Spring框架。为了便于在服务端应用上使用SWAK框架,在设计之初,我们就要求SWAK需要100%地兼容Spring框架。最终的实现版本做到了这一点,无论是业务的bean还是SWAK框架自身引入的bean,都完全由Spring容器托管。框架还使用了cglib代理了上图里执行过程中的一系列流程,完全由框架执行,对开发同学是完全透明、无感知的,使用起来如普通的单实现的接口一般,如下代码块所示。

@Autowired
private SubtitleFetcher subtitleFetcher;

//省略大段代码.......

String subtitle = subtitleFetcher.fetchSubtile();
//省略大段代码.......
在闲鱼的应用情况
目前,SWAK框架在闲鱼已经在商品发布和编辑的部分流程上得以应用,我们正在积极将SWAK框架扩展到到更多的流程上。下图是基于SWAK框架的商品域核心功能的改造计划。经过基于SWAK的升级改造,闲鱼商品域核心功能按照业务隔离,各业务开发同学仅需关系其对应业务的开发即可,其通用逻辑和业务隔离由基于SWAK框架的一层和二层充分保证。代码质量和开发效率将获得显著提升。

总结
闲鱼自研的SWAK这一多实现规则化执行框架,可以很好地解决平台代码和业务代码耦合严重难以分离、业务和业务之间代码交织缺少拆解的问题。并且SWAK 100%兼容Spring,使用方便,快速上手。名副其实地,SWAK框架就像瑞士×××一样可以适用于多种场景,小巧方便。当然,SWAK仍在不断进化,特性和功能仍在不断丰富。类似地,在闲鱼还有很多有意思的、创造性的尝试。如果对此感兴趣,欢迎加入我们。
br/>简历投递:**[email protected]
**

原文链接

本文为云栖社区原创内容,未经允许不得转载。

原文地址:http://blog.51cto.com/13952056/2287245

时间: 2024-08-29 03:46:42

业务代码解构利器--SWAK的相关文章

紫光集团半导体业务布局解构

48岁的紫光集团董事长赵伟国,面对中国证券报记者的专访,说话语速较快,声调沉稳.从收购展讯开始,紫光集团开启了一连串并购,俨然一跃成为国内集成电路的龙头. 在和中国证券报记者的交流中,赵伟国透露出一个明确的信息:同方国芯创纪录的800亿元增发,远远不是紫光集团收购动作的阶段性结束.“三年内成立规模达3000亿元的科技产业投资基金,后期还有并购宣布.未来要将紫光集团涉及的芯片细分领域做到世界前三.”显然,当下的紫光集团正开启“以国际并购带动自主创新”模式,公司业务仍然处于飞速扩张期. “买买买”之

ES6新特性:利用解构赋值 (destructuring assignment), 简化代码

本文的Demo的运行环境为nodeJS, 参考:让nodeJS支持ES6的词法----babel的安装和使用 : 解构赋值是一种表达式, 利用这种新语法, 可以直接从数组或者对象中快速提取值 赋值给不同的变量, 利用这种写法的好处是减少了代码量, 一定程度优化了代码, 也有一点缺点就是阅读代码不再那么直观了. 解构赋值最简单的例子 <script> "use strict"; let [a,b,c] = [1,2,3]; console.log( a +"|&qu

妙用ES6解构和扩展运算符让你的代码更优雅

Javascript ES6/ES2015尘埃落定,其中许多特性其实是为了简化代码.解构运算符,扩展运算符,和rest运算符就是其中很好的特性,它们可以通过减少赋值语句的使用,或者减少通过下标访问数组或对象的方式,使代码更加简洁优雅,可读性更佳.现在各浏览器及node.js都加快了部署ES6的步伐.ES6的学习正当其时. 解构 解构的作用是可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值 数组解构赋值: var arr = ['this is

ES6学习笔记(let、const、变量的解构赋值、字符串扩展)

一.let命令 1.let命令所在的代码块内有效,var声明的在全局范围内都有效 2.for循环的计数器,就很合适使用let命令 3.let声明的内部变量i和外部变量i是分离的 4.var命令会发生"变量提升"现象,即变量可以在声明之前使用,值为undefined   let命令不会发生"变量提升"现象,它所声明的变量一定要在声明后使用,否则报错 5.let不允许在相同作用域内,重复声明同一个变量 6.let会导致暂时性死区,在代码块内,使用let命令声明变量之前,

解构赋值,你不能不懂!

解构赋值 很多人可能和我一样,第一次看到这个词的时候摸不着头脑.但是冷静再看一遍好像明白了,"把数据结构分解开分别进行赋值". 我们先看几个小例子 let [a,b,c] = [1,2,3];console.log(a,b,c);//1 2 3 let {name,age} = {name:"jack",age:"18"};console.log(name,age);//jack 18 let {toString,length} = "

ES6 的解构赋值前每次都创建一个对象吗?会加重 GC 的负担吗?

本文来源于知乎上的一个提问. 为了程序的易读性,我们会使用 ES6 的解构赋值: function f({a,b}){} f({a:1,b:2}); 这个例子的函数调用中,会真的产生一个对象吗?如果会,那大量的函数调用会白白生成很多有待 GC 释放的临时对象,那么就意味着在函数参数少时,还是需要尽量避免采用解构传参,而使用传统的: function f(a,b){} f(1,2); 上面的描述其实同时提了好几个问题: 会不会产生一个对象? 参数少时,是否需要尽量避免采用解构传参? 对性能(CPU

es6学习 -- 解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). 以前,为变量赋值,只能直接指定值. let a = 1; let b = 2; let c = 3; ES6 允许写成下面这样. let [a, b, c] = [1, 2, 3]; 上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值. 本质上,这种写法属于"模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值.下面是一些使用嵌套数组进行解构的例子. 我认为

ES6解构赋值

前面的话 我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段.在ES6中添加了可以简化这种任务的新特性:解构.解构是一种打破数据结构,将其拆分为更小部分的过程.本文将详细介绍ES6解构赋值 引入 在ES5中,开发者们为了从对象和数组中获取特定数据并赋值给变量,编写了许多看起来同质化的代码 let options = { repeat: true, save: false }; // 从对象中提取数据 let repeat = options.repeat, save = option

三个角度解构云计算,商业驱动or技术驱动?

从云计算的使用者到云服务的输出者,大多互联网公司在过去一年完成了角色的转换,也让云计算的未来更加扑朔迷离.不过,抛却进入时间这个评判因素,单从技术和商业化的角度来解构云计算的话,对于云计算的格局以及未来可能的竞争局面,似乎并非那么模糊不清. 第一类玩家:商业驱动技术 之前在互联网圈流行着这样一个段子,如何打造一个牛逼的产品,在研发.产品.投入等都不给力的情况下,最后的担子居然落到了公关头上.事实上,云计算行业也存在这样的玩家,更准确的形容是商业驱动技术的类型.简单来说就是,先在商业上画一张饼,然