RxJS 快速入门

这是一篇给新手的 RxJS 快速入门,它可能不精确、不全面,但力求对新手友好。

异步与“回调地狱”

我们都知道 JavaScript 是个多范式语言,它既支持过程式编程,又支持函数式编程,两者分别适用于不同的场合。在同步环境下,两者各有优缺点,甚至有时候过程式会更简明一些,但在异步环境下(最典型的场景是一个 Ajax 请求完成后紧接着执行另一个 Ajax 请求),由于无法控制执行和完成的顺序,所以就无法使用传统的过程式写法,函数式就会展现出其优势。

问题在于,传统的函数式写法实在太不友好了。

传统写法下,当我们调用一个 Ajax 时,就要给它一个回调函数,这样当 Ajax 完成时,就会调用它。当逻辑简单的时候,这毫无问题。但是我要串起 10 个 Ajax 请求时该怎么办呢?十重嵌套吗?恩?似乎有点不对劲儿!

这就是回调地狱。

不仅如此,有时候我到底需要串起多少个 Ajax 请求是未知的,要串起哪些也同样是未知的。这已经不再是地狱,而是《Mission: Impossible》了。

我,承诺(Promise),帮你解决

事实上,这样的问题早在 1976 年就已经被发现并解决了。注意,我没写错,确实是 1976 年。

承诺,英文是 Promise [ˈprɑmɪs],它的基本思想是借助一个代表回执的变量来把回调地狱拍平。

我们以购物为例来看看日常生活中的承诺。

  1. 你去电商平台下单,并付款
  2. 平台会给你一个订单号,这个订单号本质上是一个回执,代表商家做出了“稍后我将给你发货”的承诺
  3. 商家发货给你,在这个过程中你不用等待(异步)
  4. 过一段时间,快递到了
  5. 你签收(回调函数被调用)商品(回调参数)
  6. 这次承诺结束

这是最直白的单步骤回调,如果理解了它,再继续往下看。

你跟电商下的单,但是却从快递(并不属于商家)那里接收到了商品,仔细想想,你不觉得奇怪吗?虽然表面看确实是商家给你的商品,但我们分解开中间步骤就会发现还有一些幕后的步骤。

  1. 商家把商品交给快递公司,给快递公司一个订单号(老的回执)并拿回一个运单号(新的回执)
  2. 快递公司执行这个新承诺,这个过程中商家不用等待(异步)
  3. 快递公司完成这个新承诺,你收到这个新承诺携带的商品

所以,事实上,这个购物流程包括两个承诺:

  1. 商家对你的一个发货承诺
  2. 快递公司对商家的运货承诺

因此,只要把这些承诺串起来,这些异步动作也就同样串起来了。

当我们把每个承诺都抽象成一个对象时,我们就可以对任意数量、任意顺序的承诺进行组合,变成一个新的承诺。因此回调地狱不复存在,前述的 Mission 也变得 Possible 了。

Promise 的缺点

Promise 固然是一个重大的进步,但在有些场景下仍然是不够的。比如,Promise 的特点是无论有没有人关心它的执行结果,它都会立即开始执行,并且你没有机会取消这次执行。显然,在某些情况下这么做是浪费的甚至错误的。仍然以电商为例,如果某商户的订单不允许取消,你还会去买吗?再举个编程领域的例子:如果你发起了一个 Ajax 请求,然后用户导航到了另一个路由,显然,你这个请求如果还没有完成就应该被取消,而不应该发出去。但是使用 Promise,你做不到,不是因为实现方面的原因,而是因为它在概念层(接口定义上)就无法支持取消。

此外,由于 Promise 只会承载一个值,因此当我们要处理的是一个集合的时候就比较困难了。比如对于一个随机数列(总数未知),如果我们要借助 Web API 检查每个数字的有效性,然后对前一百个有效数字进行求和,那么用 Promise 写就比较麻烦了。

我们需要一个更高级的 Promise。

Observable

它就是可观察对象(Observable [əbˈzɜrvəbl]),Observable 顾名思义就是可以被别人观察的对象,当它变化时,观察者就可以得到通知。换句话说,它负责生产数据,别人可以消费它生产的数据。

如果你是个资深后端,那么可能还记得 MessageQueue 的工作模式,它们很像。如果不懂 MQ 也没关系,我还是用日常知识给你打个比方。

Observable 就像个传送带。这个传送带不断运行,围绕这个传送带建立了一条生产线,包括一系列工序,不同的工序承担单一而确定的职责。每个工位上有一个工人。

整个传送带的起点是原料箱,原料箱中的原料不断被放到传送带上。工人只需要待在自己的工位上,对面前的原料进行加工,然后放回传送带上或放到另一条传送带上即可,简单、高效、无意外 —— 符合程序员的审美。

而且这个生产线还非常先进 —— 不接单就不生产,非常有效地杜绝了浪费。

FRP

这种设计,看上去很美,对吧?但光看着漂亮可不行,在编程时要怎么实现呢?实际上,这是一种编程范式,叫做函数响应式编程(FRP)。它比 Promise 可年轻多了,直到 1997 年被人提出来。

顾名思义,FRP 同时具有函数式编程和响应式编程的特点。响应式编程是什么呢?形象的说,它的工作模式就是“饭来张口,衣来伸手”,也就是说,等待外界的输入,并做出响应。流水线每个工位上的工人正是这种工作模式。

工业上,流水线是人类管理经验的结晶,它所做的事情是什么呢?本质上就是把每个处理都局部化,以减小复杂度(降低对工人素质的要求)。而这,正是软件行业所求之不得的。响应式,就是编程领域的流水线。

那么函数式呢?函数式最显著的特征就是没有副作用,而这恰好是对流水线上每个工序的要求。显然,如果某个工序的操作会导致整个生产线平移 10 米,那么用不了多久这个生产线就要掉到海里了,这样的生产线毫无价值。

因此,响应式和函数式几乎是注定要在一起的。

ReactiveX

2012 年,微软 .NET 开发组的一个团队为了给 LinQ 设计扩展机制而引入了 FRP 概念,却发现 FRP 的价值不止于此。于是一个新的项目出现了,它就是 ReactiveX。

严格来说 ReactiveX 应该是一 FRP 库,因为它几乎在每个主流语言下都提供了实现,而且这些实现都是语言原生风格的,不是简单地迁移。如果你在任何语言下用过带有 Rx 前缀的库,那多半儿就是 ReactiveX 的一个实现了,如 RxJava、Rx.NET、RxGroovy、RxSwift 等等。

ReactiveX 本身其实并不难,难的是 FRP 编程范式以及对操作符(operator)的理解。所以,只要学会了任何一个 Rx* 库,那么其它语言的库就可以触类旁通了。

宝石图

为了帮助开发者更容易地理解 ReactiveX 的工作原理,ReactiveX 开发组还设计了一种很形象的图,那就是宝石图。这货长这样(英文注释不必细看,接下来我会简单解释下):

中间的带箭头的线就像传送带,用来表示数据序列,这个数据序列被称为“流”。上方的流叫做输入流,下方的流叫做输出流。输入流可能有多个,但是输出流只会有一个(不过,流中的每个数据项也可以是别的流)。

数据序列上的每个圆圈表示一个数据项,圆圈的位置表示数据出现的先后顺序,但是一般不会表示精确的时间比例,比如在一毫秒内接连出现的两个数据之间仍然会有较大的距离。只有少数涉及到时间的操作,其宝石图才会表现出精确的时间比例。

圆圈的最后,通常会有一条竖线或者一个叉号。竖线表示这个流正常终止了,也就是说不会再有更多的数据提供出来了。而叉号表示这个流抛出错误导致异常中止了。还有一种流,既没有竖线也没有叉号,这种叫做无尽流,比如一个由所有自然数组成的流就不会主动终止。但是要注意,无尽流仍然是可以处理的,因为需要多少项是由消费者决定的。你可以把这个“智能”传送带理解为由下一个工位“叫号”的,没“叫号”下一项数据就不会过来。

中间的大方框表示一个操作,也就是 operator —— 一个函数,比如这个图中的操作就是把输入流中的条目乘以十后放入输出流中。

看懂了宝石图,就能很形象的理解各种操作符了。

RxJS

主角登场了。RxJS 就是 ReactiveX 在 JavaScript 语言上的实现。对于 JavaScript 程序员来说,不管你是前端还是 NodeJS 后端,RxJS 都会令你受益。

由于 JavaScript 本身的缺陷,RxJS 不得不采用了很多怪异的写法。它对于 Java / C# 等背景的程序员来说可能会显得比较怪异,不过,你可以先忽略它们,聚焦在编程范式和接下来要讲的操作符语义上。

典型的写法

of(1,2,3).pipe(
  filter(item=>item % 2 === 1),
  map(item=>item * 3),
).subscribe(item=> console.log(item))
`</pre>

它会输出:
<pre>`3
9

其中 of 称为创建器(creator),用来创建流,它返回一个 Observable 类型的对象,filter 和 map 称为操作符(operator),用来对条目进行处理。这些操作符被当作 Observable 对象的 pipe 方法的参数传进去。诚然,这个写法略显怪异,不过这主要是被 js 的设计缺陷所迫,它已经是目前 js 体系下多种解决方案中相对好看的一种了。

Observable 对象的 subscribe 方法表示消费者要订阅这个流,当流中出现数据时,传给 subscribe 方法的回调函数就会被调用,并且把这个数据传进去。这个回调函数可能被调用很多次,取决于这个流中有多少条数据。

注意,Observable 必须被 subscribe 之后才会开始生产数据。如果没人 subscribe 它,那就什么都不会做。

简单创建器

广义上,创建器也是操作符的一种,不过这里我们把它单独拿出来讲。要启动生产线,我们得先提供原料。本质上,这个提供者就是一组函数,当流水线需要拿新的原料时,就会调用它。

你当然可以自己实现这个提供者,但通常是不用的。RxJS 提供了很多预定义的创建器,而且将来可能还会增加新的。不过,那些眼花缭乱的创建器完全没必要全记住,只要记住少数几个就够了,其它的有时间慢慢看。

of – 单一值转为流

它接收任意多个参数,参数可以是任意类型,然后它会把这些参数逐个放入流中。

from – 数组转为流

它接受一个数组型参数,数组中可以有任意数据,然后把数组的每个元素逐个放入流中。

range – 范围转为流

它接受两个数字型参数,一个起点,一个终点,然后按 1 递增,把中间的每个数字(含边界值)放入流中。

fromPromise – Promise 转为流

接受一个 Promise,当这个 Promise 有了输出时,就把这个输出放入流中。

要注意的是,当 Promise 作为参数传给 fromPromise 时,这个 Promise 就开始执行了,你没有机会防止它被执行。

如果你需要这个 Promise 被消费时才执行,那就要改用接下来要讲的 defer 创建器。

defer – 惰性创建流

它的参数是一个用来生产流的工厂函数。也就是说,当消费方需要流(注意不是需要流中的值)的时候,就会调用这个函数,创建一个流,并从这个流中进行消费(取数据)。

因此,当我们定义 defer 的时候,实际上还不存在一个真正的流,只是给出了创建这个流的方法,所以叫惰性创建流。

timer – 定时器流

它有两个数字型的参数,第一个是首次等待时间,第二个是重复间隔时间。从图上可以看出,它实际上是个无尽流 —— 没有终止线。因此它会按照预定的规则往流中不断重复发出数据。

要注意,虽然名字有相关性,但它不是 setTimeout 的等价物,事实上它的行为更像是 setInterval

interval – 定时器流

它和 timer 唯一的差别是它只接受一个参数。事实上,它就是一个语法糖,相当于 timer(1000, 1000),也就是说初始等待时间和间隔时间是一样的。

如果需求确实是 interval 的语义,那么就优先使用这个语法糖,毕竟,从行为上它和 setInterval 几乎是一样的。

思考题:假设点了一个按钮之后我要立刻开始一个动作,然后每隔 1000 毫秒重复一次,该怎么做?换句话说:该怎么移除首次延迟时间?

Subject – 主体对象

它和创建器不同,创建器是供直接调用的函数,而 Subject 则是一个实现了 Observable 接口的类。也就是说,你要先把它 new 出来(假设实例叫 subject),然后你就可以通过程序控制的方式往流里手动放数据了。它的典型用法是用来管理事件,比如当用户点击了某个按钮时,你希望发出一个事件,那么就可以调用 subject.next(someValue) 来把事件内容放进流中。

当你希望手动控制往这个流中放数据的时机时,这种特性非常有用。

当然,Subject 其实并没有这么简单,用法也很多,不过这部分内容超出了本文的范围。

合并创建器

我们不但可以直接创建流,还可以对多个现有的流进行不同形式的合并,创建一个新的流。常见的合并方式有三种:并联、串联、拉链。

merge – 并联

从图上我们可以看到两个流中的内容被合并到了一个流中。只要任何一个流中出现了值就会立刻被输出,哪怕其中一个流是完全空的也不影响结果 —— 等同于原始流。

这种工作方式非常像电路中的并联行为,因此我称其为并联创建器。

并联在什么情况下起作用呢?举个例子吧:有一个列表需要每隔 5 秒钟定时刷新一次,但是一旦用户按了搜索按钮,就必须立即刷新,而不能等待 5 秒间隔。这时候就可以用一个定时器流和一个自定义的用户操作流(subject)merge 在一起。这样,无论哪个流中出现了数据,都会进行刷新。

concat – 串联

从图中我们可以看到两个流中的内容被按照顺序放进了输出流中。前面的流尚未结束时(注意竖线),后面的流就会一直等待。

这种工作方式非常像电路中的串联行为,因此我称其为串联创建器。

串联的适用场景就很容易想象了,比如我们需要先通过 Web API 进行登录,然后取学生名册。这两个操作就是异步且串联工作的。

zip – 拉链

zip 的直译就是拉链,事实上,有些压缩软件的图标就是一个带拉链的钥匙包。拉链的特点是两边各有一个“齿”,两者会啮合在一起。这里的 zip 操作也是如此。

从图上我们可以看到,两个输入流中分别出现了一些数据,当仅仅输入流 A 中出现了数据时,输出流中什么都没有,因为它还在等另一个“齿”。当输出流 B 中出现了数据时,两个“齿”都凑齐了,于是对这两个齿执行中间定义的运算(取 A 的形状,B 的颜色,并合成为输出数据)。

可以看到,当任何一个流先行结束之后,整个输出流也就结束了。

拉链创建器适用的场景要少一些,通常用于合并两个数据有对应关系的数据源。比如一个流中是姓名,另一个流中是成绩,还有一个流中是年龄,如果这三个流中的每个条目都有精确的对应关系,那么就可以通过 zip 把它们合并成一个由表示学生成绩的对象组成的流。

操作符

RxJS 有很多操作符,事实上比创建器还要多一些,但是我们并不需要一一讲解,因为它们中的很大一部分都是函数式编程中的标配,比如 map、reduce、filter 等。有 Java 8 / scala / kotlin 等基础的后端或者用过 underscore/lodash 的前端都可以非常容易地理解它们。

本文重点讲解一些传统方式下没有的或不常用的:

retry – 失败时重试

有些错误是可以通过重试进行恢复的,比如临时性的网络丢包。甚至一些流程的设计还会故意借助重试机制,比如当你发起请求时,如果后端发现你没有登录过,就会给你一个 401 错误,然后你可以完成登录并重新开始整个流程。

retry 操作符就是负责在失败时自动发起重试的,它可以接受一个参数,用来指定最大重试次数。

这里我为什么一直在强调失败时重试呢?因为还有一个操作符负责成功时重试。

repeat – 成功时重试

除了重复的条件之外,repeat 的行为几乎和 retry 一模一样。

repeat 很少会单独用,一般会组合上 delay 操作,以提供暂停时间,否则就容易 DoS 了服务器。

delay – 延迟

这才是真正的 setTimeout 的等价操作。它接受一个毫秒数(图中是 20 毫秒),每当它从输入流中读取一个数据之后,会先等待 20 毫秒,然后再放到输出流中。

可以看到,输入流和输出流内容是完全一样的,只是时机上,输出流中的每个条目都恰好比输入流晚 20 毫秒出现。

toArray – 收集为数组

事实上,你几乎可以把它看做是 from 的逆运算。 from 把数组打散了逐个放进流中,而 toArray 恰好相反,把流中的内容收集到一个数组中 —— 直到这个流结束。

这个操作符几乎总是放在最后一步,因为 RxJS 的各种 operator 本身就可以对流中的数据进行很多类似数组的操作,比如查找最小值、最大值、过滤等。所以通常会先使用各种 operator 对数据流进行处理,等到要脱离 RxJS 的体系时,再转换成数组传出去。

debounceTime – 防抖

在 underscore/lodash 中这是常用函数。 所谓防抖其实就是“等它平静下来”。比如预输入(type ahead)功能,当用户正在快速打字的时候,你没必要立刻去查服务器,否则可能直接让服务器挂了,而应该等用户稍作停顿(平静下来)时再发起查询。

debounceTime 就是这样,你传入一个最小平静时间,在这个时间窗口内连续过来的数据一概被忽略,一旦平静时间超过它,就会往把接收到的下一条数据放到流中。这样消费者就只能看到平静时间超时之后发来的最后一条数据。

switchMap – 切换成另一个流

这可能是相对较难理解的一个 operator。

有时候,我们会希望根据一个立即数发起一个远程查询,并且把这个异步取回的结果放进流中。比如,流中是一些学生的 id,每过来一个 id,你要发起一个 Ajax 请求来根据这个 id 获取这个学生的详情,并且把详情放进输出流中。

注意,这是一个异步操作,所以你没法用普通的 map 来实现,否则映射出来的结果就会是一个个 Observable 对象。

switchMap 就是用来解决这个问题的。它在回调函数中接受从输入流中传来的数据,并转换成一个新的 Observable 对象(新的流,每个流中包括三个值,每个值都等于输入值的十倍),switchMap 会订阅这个 Observable 对象,并把它的值放入输出流中。注意图中竖线的位置 —— 只有当所有新的流都结束时,输出流才会结束。

不知道你有没有注意到这里一个很重要的细节。30 只生成了两个值,而不是我们所预期的三个。这是因为当输入流中的 5 到来时,会切换到以 5 为参数构建出的这个新流(S5),而这时候基于 3 构建的那个流(S3)尚未结束。虽然如此,但是已经没人再订阅 S3 了,因为同一时刻 switchMap 只能订阅一个流。所以,已经没人会再朝着 S3 “叫号”了,它已经被释放了。

规律:operator 打包学

当你掌握了一些基本操作符之后,就可以让自己的操作符知识翻倍了。

这是因为 RxJS 中的很多操作符都遵循着同样的命名模式。比如:

xxxWhen – 满足条件时 xxx

它接受一个 Observable 型参数作为条件流,一旦这个条件流中出现任意数据,则进行 xxx 操作。

retryWhen(notifier$),其中的 notifier$ 就是一个条件流。当输入流出现异常时,就会开始等待 notifier$ 流中出现数据,一旦出现了任何数据(不管是什么值),就会开始执行重试逻辑。

xxxCount – 拿到 n 个数据项时 xxx

它接受一个数字型参数作为阈值,一旦从输入流中取到了 n 个数据,则进行 xxx 操作。

bufferCount(3) 表示每拿到 3 个数据就进行一次 buffer 操作。

这个操作可以看做是 xxxWhen 的语法糖。

xxxTime – 超时后 xxx

它接受一个超时时间作为参数,从输入流中取数据,一旦到达超时时间,则执行 xxx 操作。

比如前面讲过的 debounceTime 其实遵循的就是这种模式。

这个操作可以看做 xxxWhen 的语法糖。

xxxTo – 用立即量代替 Lambda 表达式

它接受一个立即量作为参数,相当于 xxx(()=&gt;value))

比如 mapTo('a') 其实是 map(()=&gt;'a') 的语法糖,也就是说无论输入流中给出的值是什么,我往输出流中放入的都是这个固定的值。

坑与最佳实践

取消订阅

subscribe 之后,你的回调函数就被别人引用了,因此如果不撤销对这个回调函数的引用,那么与它相关的内存就永远不会释放,同时,它仍然会在流中有数据过来时被调用,可能会导致奇怪的 console.log 等意外行为。

因此,必须找到某个时机撤销对这个回调函数的引用。但其实不一定需要那么麻烦。解除对回调函数的引用有两种时机,一种是这个流完成(complete,包括正常结束和异常结束)了,一种是订阅方主动取消。当流完成时,会自动解除全部订阅回调,而所有的有限流都是会自动完成的。只有无尽流才需要特别处理,也就是订阅方要主动取消订阅。

当调用 Observablesubscribe 方法时,会返回一个 Subscription 类型的引用,它实际上是一个订阅凭证。把它保存下来,等恰当的时机调用它的 unsubscribe 方法就可以取消订阅了。比如在 Angular 中,如果你订阅了无尽流,那么就需要把订阅凭证保存在私有变量里,并且在 ngOnDestroy 回调中调用它的 unsubscribe 方法。

类型检查

只要有可能,请尽量使用 TypeScript 来书写 RxJS 程序。由于大量 operator 都会改变流中的数据类型,因此如果靠人力来追踪数据类型的变化既繁琐又容易出错。TypeScript 的类型检查可以给你提供很大的帮助,既省心又安全,而且这两个都是微软家的,搭配使用,风味更佳。

代码风格

如同所有 FP 程序一样,ReactiveX 的代码也应该由一系列小的、单一职责的、无副作用的函数组成。虽然 JavaScript 无法像 Java 中那样对 Lambda 表达式的副作用做出编译期限制,但是仍然要遵循同样的原则,坚持无副作用和数据不变性。

寄语 – 实践出真知

ReactiveX 大家族看似庞大,实则简单 —— 如果你已经有了 Java 8+ / Kotlin / underscore 或 lodash 等函数式基础知识时,新东西就很少了。而当你用过 Rx 大家族中的任何一个成员时,RxJS 对你几乎是免费的,反之也一样。

唯一的问题,就是找机会实践,并体会 FRP 风格的独特之处,获得那些超乎具体技术之上的真知灼见。


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

白话中台战略2:中台到底长啥样?

在上篇《白话中台战略-1开篇:中台是个什么鬼?》中,我试着依据自己的经验和理解,阐述了中台产生的原因以及最终建设目的,可能会过于抽象,大家听得还是云里雾里,本文就试图通过我的收集和思考,带着大家一起来看看中台到底“长啥样”,以期让大家有个直观的印象。话不多说,咱们直接开讲。

数据业务双中台

提起中台,绕不开也是最先想到的应该都是阿里巴巴的数据业务双中台。毕竟阿里的“大中台小前台”战略人尽皆知,其威力也是显而易见的。

以阿里这么大的体量,经过了这么多年的厮杀,在互联网快速迭代创新的竞争环境中,仍然可以保持快速迭代创新,上演了一场接一场现实版的大象跳舞,中台战略的成功居功至伟。

(摘自《企业核心业务数字化转型最佳实践》)

阿里的数据业务双中台堪称经典,上图摘取自钟华(古谦)于刚刚结束的2018云栖大会《企业核心业务数字化转型最佳实践》分享。

从图中可见,阿里中台主要体现为由业务中台和数字中台并肩构成的双中台,并肩扛起了所有前台业务。

  • 业务中台将后台资源进行抽象包装整合,转化为前台友好的可重用共享的核心能力,实现了后端业务资源到前台易用能力的转化。
  • 数据中台从后台及业务中台将数据流入,完成海量数据的存储、计算、产品化包装过程,构成企业的核心数据能力,为前台基于数据的定制化创新和业务中台基于数据反馈的持续演进提供了强大支撑。
  • 业务中台与数据中台相辅相成、互相支撑,一起构建起了战场强大的后方炮火群和雷达阵。

移动中台

同样来自于2018年云栖大会杭州站,在移动研发平台EMAS专场上,阿里巴巴高级技术专家泠茗带来的分享中就为我们揭开了阿里移动中台的面纱。

(摘自《数字化转型 移动化先行 云栖大会上发布了哪些移动研发新利器》)

可见阿里的移动中台是构建在业务&数据中台之上,为更好更快的利用中台能力、快速迭代移动端产品,又生生的挤出(或是说沉淀)出了一个新的中台层。

移动中台建立在业务数据双中台之上,更靠近移动前端战场,我们可以类比成战场上的坦克群,近距离支撑一线战场。

技术中台

大中台小前台,并不代表前台不重要,相反,大中台的建设就是为了更好地服务好小前台,大中台的威力也需要靠小前台的引导才能真正发挥和体现出来。

就像是深入敌后的前台特种部队如果定位不到敌人的精确位置,就算有再强大的水陆空中台炮火群,也只是一群废铁而已。

大中台小前台战略,其实是给小前台的构建提出了更高的要求,就像我们对于特种兵的要求也比一般的士兵高出很多一样。

那如何快速构建出短小精悍,武器精良,战斗力十足的特种兵前台应用,充分发挥和释放出中台炮火群的威力,就需要依靠这里提到的技术中台。

(摘自《阿里技术手册-研发篇》)

技术中台就是将使用云或其他基础设施的能力以及应用各种技术中间件的能力进行整合和包装。过滤掉技术细节,提供简单一致、易于使用的应用技术基础设施的能力接口,助力前台和业务中台数据中台的快速建设。

如果将业务数据双中台比喻成强大的中台炮火群,可以直接对敌人进行进攻。那技术中台的作用就有些间接,有点像前台特种兵身上各种先进的武器装备。精良易用的武器装备,可以在大幅缩短前台特种兵的建设周期的同时大幅提高单兵作战能力,令敌人胆寒。

研发中台

软件开发是一项工程,涉及到管理、流程、测试、团队协作等方面。如何将企业的开发流程最佳实践沉淀成可重用的“能力”,从而助力创新性应用的快速开发迭代,也是我们看到的很多企业正在做的事情,我们可以管这种关注与开发效能管理的平台叫做研发中台。

如果说技术中台为前台应用提供了基础设施重用的能力,那研发中台就为前台应用提供了流程和质量管控以及持续交付的能力。

(摘自《阿里技术手册-研发篇》)

上图摘自阿里的技术手册,展示了阿里的效能事业部一直致力于构建的阿里研发效能平台,有兴趣的同学可以去了解一下阿里自家的云效平台。

(ThoughtWorks帮助客户打造的DevOps云平台)

ThoughtWorks在敏捷与开发效能方面一直走在行业领先,持续总结过去的经验,并基于客户自身的需求和实际情况、结合最新的技术,与客户一起携手成功打造了多个定制化的开发效能平台。

总之,开发效能是构建前中台应用过程中必不可少的重要一环。这方面的实践和能力通过沉淀,结合快速开发框架平台,例如微服务开发平台,就形成了企业的研发中台。

如果将技术中台比喻成前台特种兵的武器装备,那效能中台就是前台特种兵的管理训练基地以及可以快速将战士运送到一线战场的机动运输部队。

组织中台

以上无论是业务中台、数据中台、技术中台、研发中台……都是围绕技术展开的,也是企业在中台建设中最关注的方面。

但真正实际经历过几次企业级中台的建设后,我深刻的体会到:围绕技术展开并不是基于在中台构建中技术的重要性,而是因为技术的改进相对简单。

而中台建设真正困难的是组织上的重构,这往往是大家有意无意避而不谈的。

中台战略的成功、能否实现技术架构与组织架构的匹配,是一道绕不过去必须要迈过的门槛。从阿里成立共享事业部,海尔的人单合一、职能并联到近期大家关注的腾讯的组织架构重构都是这些企业在这方面做出的努力。

举个实际的例子,在很多企业中一个新项目的启动,从筹划到申请经过层层审批和评审短则一两月,多则需要几个月的时间。要知道在当前所处的互联网时代,几个月都够人家App都上线了。

(摘自《释放潜能-平台型组织的进化路线图》)

为了真正解决企业创新在组织层面的摩擦和阻力,构建真正的平台型组织。《释放潜能-平台型组织的进化路线图》提出了上图这样一个平台型组织的组织结构。

组织中台很像企业中的内部风投和创新孵化机构,为前台组织和团队构建创新型前台应用提供类似于投资评估(项目甄别)、投资管理、投后管理(孵化与风控),真正从组织和制度上支撑前台组织和应用的快速迭代规模化创新。

知易行难,ThoughtWorks在科技时代精益企业背景下提出的“价值驱动决策”框架并用“EDGE”命名,主要针对如何在市场高速变化时保证投资有效性,即致力于帮助客户在组织上和投资管理上真正的助力创新,也算是在组织中台建设上迈出的一步。

如果还用前边军队的比喻,那组织中台是什么呢?可能你已经想到了,对,就是战场指挥部。

到底中台长啥样?

列举了这么多各式各样的中台,最后都扯到了组织层面,是不是有种越听越晕的感觉,是不是感觉什么东西加个“中台”的后缀都可以靠到中台上来,估计很快就会看到例如AI中台,VR中台,搜索中台,算法中台……对了,算法中台已经有了……

让我们引用一段阿里玄难在接受极客公园采访时提到对于中台的一段我非常认同的描述:

本文中我们一直提到的一个词就是“能力”,从玄难的这段采访也可以看出,在阿里“能力”也是中台的核心。

甄别是不是中台还要回到中台要解决的问题上,也就是我上篇文章主要关注的问题。我认为一切以”以用户为中心的持续规模化创新”为目的,将后台各式各样的资源转化为前台易于使用的能力,帮助我们打赢这场以用户为中心的战争的平台,我们都可以称之为中台:

  • 业务中台提供重用服务,例如用户中心,订单中心之类的开箱即用可重用能力,为战场提供了强大的后台炮火支援能力,随叫随到,威力强大;
  • 数据中台提供了数据分析能力,帮助我们从数据中学习改进,调整方向,为战场提供了强大及时的雷达监测能力,帮助我们掌控战场;
  • 移动及算法中台提供了战场一线火力支援能力,帮助我们提供更加个性化的服务,增强用户体验,为战场提供了陆军支援能力,随机应变,所向披靡;
  • 技术中台提供了自建系统部分的技术支撑能力,帮助我们解决了基础设施,分布式数据库等底层技术问题,为前台特种兵提供了精良的武器装备;
  • 研发中台提供了自建系统部分的管理和技术实践支撑能力,帮助我们快速搭建项目,管理进度,测试,持续集成,持续交付,是前台特种兵的训练基地及快速送达战场的机动运输部队;
  • 组织中台为我们的项目提供投资管理,风险管理,资源调度等,是战场的指挥部,战争的大脑,指挥前线,调度后方。

所以,评判一个平台是否称得上中台,最终评判标准不是技术也不是长什么模样,最终还是得前台说了算,毕竟前台才是战争的关键,才是感受得到战场的残酷,看得见用户的那部分人。

前台想不想用,爱不爱用,好不好用,帮了前台多大的忙,从中台获得了多大的好处,愿意掏出多少利润来帮助建设中台,这才是甄别中台建设对错好坏的唯一标准。 对于中台来讲,前台就是用户,以用户为中心,在中台同样适用。


参考阅读


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

Corda – 为了商业而设计的区块链平台

位置

2018年5月第18期技术雷达,平台象限,建议评估。(最新版技术雷达已经发布,点击这里下载

标签

Blockchain,DLT

目标受众

区块链架构师,开发人员

关注问题

区块链技术允许不同组织之间直接达成没有中间人参与的交易,这大大提高了交易的效率。但是“传统”区块链平台要求所有用户复制所有交易,这带来了大量的重复和浪费,性能很难满足现实商业世界的要求,另外,尽管有加密技术存在,大家依然担心数据的隐私性是否能够得到足够保证。

解决方案

Corda 在继承了区块链点对点网络的基础上,将网络区分为不同的兼容区(compatility zone),每个兼容区内可以部署不同的智能合约(smart contract),同时辅以可插拔的共识机制(pluggable consensus),以便针对不同类型的应用对共识算法进行优化。同时,在交易数据的存储上,作为联盟链的 Corda 采用了每个节点只需存储与自己参与或需要知道的数据,全网共识由兼容区内的公证人(Notary)节点集群来保证。

解读

随着区块链热度的逐渐消退,公众对于区块链技术的看法逐渐趋于理性,依然对区块链技术保持热忱的人们开始思考区块链究竟能带来怎样的商业价值,这就要求各大区块链平台针对普及区块链遇到的阻力提供解决方案。Corda 作为其中的一员,将关注点投入在如下几个方面:

  • 隐私性(privacy)
  • 交易可终结性(transaction finality)
  • 参与方身份认证(legally identified parties)
  • 可扩展性(ability to scale)
  • 开发者效率和企业级集成(developer productivity and enterprise integration)

隐私性

将我所有的交易数据发布到所有节点(包括竞争对手)?任何一位企业管理者在听到这样的提案时都没法坦然接受这样的技术“革命”吧?更何况很多行业还面临着合规性审计的压力。

Corda 选择只让交易相关方存储交易数据。如何阻止“双花”(double spend)?交给公证人节点吧。

交易可终结性

什么?我付了钱还要等6个区块才能确认交易达成?还会分叉?那交易到底是发生了还是没发生?我的交易是薛定谔的猫吗?

别担心,Corda 将网络分为不同的兼容区,并允许在每个兼容区内自主配置共识算法,以帮助兼容区内的节点以最快速度达成共识。

参与方身份认证

公有链每个客户端和节点都不需要使用物理世界中真实存在的身份进行交易,而对于真实商业世界中的交易,我的交易对手方对我考虑一笔交易至关重要。Corda 作为联盟链,使用业界已经比较成熟的 X509 证书为每个节点提供身份。

可扩展性

区块链平台主要的性能瓶颈在于处理每笔交易并达成共识的过程中,这里存在着巨大的网络开销和计算工作。

Corda 根据承载业务的不同将网络划分为不同的兼容区,每个兼容区内节点数量更少,性能要求更低;同时,Corda 选择将达成共识的职责与账本层解耦,由公证人节点负责达成共识;每个兼容区可以根据节点数量和所承载的业务自主选择更佳合适的共识算法,让 Corda 可以满足真实商业需求的性能需求。

开发者效率和企业级集成

Corda 选择了已经发展成熟 JVM 平台以及 Kotlin 语言作为开发工具,关系型数据库作为数据存储。大部分企业的 IT 部门早已经在这些领域驾轻就熟,大大降低了企业拥抱新技术的技术切换成本。

相关 Blip

延展阅读

Share

一次Testing in Production方案的探索

引子

传统的软件测试大多是在测试环境下进行的。人们普遍认为生产环境是服务于最终用户的,只有在测试环境下进行充分测试后才会发布给用户。

基于非生产环境的测试-单元测试、集成测试、功能测试等,很多都是基于预期结果的测试,测试人员一般是带着这样的思路来工作 “如果这样做会发生什么呢” -属于known-unknowns。而生产环境往往充满了惊喜-属于unknown-unknowns。我们不知道最终用户怎么操作(参考同事姚琪琳的文章《被踢出去的用户》),数据是什么样的,基础设施有什么差异等。

Stage作为类生产环境,是和生产环境最接近的一个测试环境。然而每一次新的发布都是一组代码和环境的组合,只有真正部署到了对应的环境,我们才能确定到底有没有问题。Stage环境也是测试环境,是抱着一定目的进行的操作,并不能完全反应真实用户的行为。

项目背景

我们当前的测试流程如下:

产品经受了多个测试环境的考验,但是在部署生产环境后依然暴露出很多意想不到的问题,初步分析后归结为下面两个因素:

1. 生产环境下数据复杂多样

我们对客户报过来的production问题进行了分析,下图可以看出由于数据问题导致的功能/性能问题在10%左右的区间波动。

软件系统的灵活性给与了用户各种各样的操作可能,代码/脚本的不确定性也会造成数据的不一致。这些都赋予了生产环境下数据的多样性,是其他环境无法模拟的。

2. 软件配置的集中化

ThoughtWorks团队主要负责软件的开发,而Stage和Prod环境部署在云平台上,这些访问权限严格控制在客户手中,基础设施严重依赖于客户。

项目即将大规模将配置由原来的SVN迁移到ZooKeeper实现集中管理。作为一个技术的改进,同时也蕴含着风险 – Stage和Prod的配置将由客户进行单点手工维护,对ThoughtWorks团队不可见,因此我们无法预知某个配置是否已经被添加/修改以及是否赋予了正确的值。

Testing in Production如何做

环境的特殊性带来了产品的不确定性,我们希望把测试的触角向前延伸,到生产环境去做测试,提前暴露产品的潜在问题,提高用户的满意度。

由于各种因素的约束,在生产环境能做的事情往往有限。比如我们项目的安全等级很高,开发团队是不能够访问生产环境的服务器的,甚至连脱敏的数据也接触不到。Stage环境下的数据也仅仅是客户的测试数据,不能把生产环境下的数据迁移过来。

业界实践

TiP并不是一个全新的事物,业界已经有了很多成熟实践:蓝绿部署、金丝雀测试、A/B测试等。

蓝绿部署是在有两个一样环境的前提下,不停老版本,部署新版本进行测试。测试没问题之后直接把流量切到新版本上,再把老版本也升级到新版本。一般适用于对用户体验有一定的忍耐度、机器资源丰富的团队。

“金丝雀测试”得名于以前旷工下井前会先放一只金丝雀去看是否有有毒气体,以金丝雀能否存活进行判断。一般是部署新版本到很小比例的服务器上,并允许小部分用户来使用新版本,测试通过则把剩余的服务都升级为新版本。一般适用于对新版本缺乏信心的团队。

A/B测试主要用于产品功能对比,版本A和版本B分别部署在不同的服务器上并开放给不同的用户使用,一般适用于收集用户反馈辅助产品功能设计。

蓝绿部署

基于当前产品环境的复杂架构,构建另一套相同的生产环境来实现蓝绿部署作为第一方案被提出来。蓝绿部署的思路如图:

在同一个时间段,蓝作为当前的生产环境供线上用户使用,绿作为部署新功能的测试环境供部分用户使用。两个环境的基础设施相同,配置一样,数据都是真实的生产环境数据。绿环境下发现的问题可以随时诊断修复,确认满足上线需求后即可把线上用户引流到绿环境,实现了最小化的宕机时间。

蓝绿部署的这个优势看似极好的契合了项目当前的诉求,但是准备一套同样的生产环境需要的成本在可视化出来之后也是令人震惊的!新的服务器就需要7台,而且每个月还需要预留出足够的时间来同步数据。在功能交付的压力之下,客户是不会为这样一个昂贵且成果未知的方案买单的,我们连自己都说服不了。

改进的方案

就在焦灼的时候,在一次头脑风暴中我们获取到一条线索-客户的灾备环境(Disaster Recovery)在定期从生产环境同步数据,但也仅仅是同步数据,代码已经很久没有部署过。也就是说灾备环境没有真正起到它应有的灾难备份和恢复,只是一个数据的备份而已。

方案就此而得到转机 – 是否可以复活灾备环境,利用它可以访问生产环境数据的天然优势来解决前面的痛点呢?在蓝绿部署方案的基础上,改进的方案如下:

鉴于灾备环境的基础设施不足以支撑其作为线上环境供所有用户使用,但是它的配置是等同于产品环境的。DR的定位为分时的灾备和测试环境 – 大部分时间用于灾备,小部分时间作为金丝雀进行新版本的测试。

灾备环境测试通过后的版本按照当前的部署流程进行生产环境的部署。这样一来不仅能恢复其本来的灾备作用,也解决了之前数据和配置集中化问题带来的痛点。

展望

从当前的测试流程来看,QA和Stage环境承担的工作有很大一部分重叠,带来了一定的浪费。希望未来有一天能去掉Stage环境,直接把这些server用在生产环境下构建一套新的环境,做到充分的基于生产环境的测试,实现新老版本的无缝切换。 期待测试流程会变成如下所示:

当今软件的部署越来越多的基于第三方的云平台,给团队带来了不可控因素。Testing in Production是基于生产环境下真实用户的行为和数据进行的一系列QA活动。传统的基于测试环境进行的测试活动,辅助以生产环境下的QA活动为提高软件的质量注入了新的活力。


更多精彩洞见,请关注微信公众号:思特沃克

Share

ThoughtWorks的专业精神

我们说ThoughtWorks是一家专业服务公司(Professional Service Firm),专业服务是一种业务模式,早期通常指的是医生、律师、会计等,后来管理咨询顾问、IT或生物等领域的科技顾问也加入了这个行列。专业服务的特点是:高度知识密集,专业从业人员需要长期积累经验并持续成长,通常是以帮人解决问题来创造价值。

当我们提到“专业”的时候,通常是相对于“业余”来说的。专业区别于业余,是在于对专业发展的长年持续投入,以及在此基础上建立起来的在相关知识领域的权威性。

ThoughtWorks的权威来自对卓越的追求。过去,ThoughtWorker以“在代码级对设计和质量的追求,对新兴技术的快速掌握,以及我们解决工程技术问题的能力”,让我们在软件工程实践领域成为业界的翘楚。未来,是否能在更大的广度和深度范围里进一步取得卓越的效果,则取决于我们能力的发展。

专业能力的发展一方面是在知识、技能的持续更新;另一方面是通过刻意的锻炼和实践,持续提升专业所需的素养,或者也叫胜任力。我们以往常把专业知识和胜任力合在一起看,现在,我们在新的Competency Model里定义了Craft Skill和Amplifier,分别探寻个人在其方向的发展方式。

可能不少同事已经听说,我们开始在Lead Consultant范围开始试点和推进新的能力模型,这个试点的重点是Amplifier。虽然每个人对各种能力的提升有着自己的偏好,不过囿于自己的文化、业务,以及相对应的战略,我们能力的发展也凸显着ThoughtWorks的特质。以能力模型里的Core Competency为例。

Self-Confidence(自信)

“自信”使得ThoughtWorker总能够突破自身现有水平,迎难而上,能够做别人不敢做,做不到的事情。在公司久的人可能听到过一些故事,比如在新的技术领域,跟客户团队同时起步的情况下,我们的咨询师总能领先一步,趟过客户难以解决的问题,进而辅导客户的团队。在客户面前,我们ThoughtWorker总能积极寻求合适的时机,挺身而出,表达我们的主张。跟小伙伴一起,我们总能真诚地赞扬ThoughtWorker出色的行为,同样,我们也应该勇敢坦诚地给身边同事反馈可以改进的地方,互相帮助成长。

Developing Others(发展他人)

一位ThoughtWorker到了新的团队、新的办公室,甚至出差到另一个国家,只要一进办公室, 那熟悉温暖的感觉不仅仅来自公司的Logo,更来自第一时间感受到的周围同事热情的支持、鼓励和无私的分享和帮助。不过发展他人不仅仅是在看到需要的时候提供帮助,每个ThoughtWorker都有责任,都要具备能力,发现同伴的不足并提出反馈,通过安排合适的锻炼机会,鼓励同伴突破舒适区,掌握新的能力。

Delivering Customer Results (客户成果交付)

直接的或间接的,客户价值也是ThoughWorker价值的体现。我们在专业上确实会有自己的追求和兴趣,但是在项目上,我们的设计,我们的分析,我们的代码,以及我们的各种决定,如果不能产生客户价值,不能帮助客户成功,那都是耍流氓。

Technical Expertise(技术专长)

如果说几乎所有的公司都对员工有技术掌握程度的要求,我们在跟很多客户合作过程中发现,ThoughtWorker与其它公司的技术从业者相比,我们具备更加广泛的视野,我们更能够坚持探索和推动正确高效的问题解决办法,而对业界最新进展的全面深刻的理解是我们超越业界同侪的标志。

是不是有了超强技能就够了?我们有的时候会对某人作出直观地判断 ——“这人看上去很不专业”。这种判断可能并不是来自这个人解决问题时的表现,而是来自于对其态度和行为的观察。有责任的态度和行为体现在两个层面。一个是个人对客户层面,一个是组织对社会层面。

个人和客户层面上, 拥有自己行业的行为操守(Code of Conduct)通常是专业化的一个特征。在最早的几个专业服务领域,比如医生、律师、会计等,对道德规范都制定有强制性的守则,这在注册认证中都是必考的内容。如果被发现有违反行为,就会吊销相关注册。作为面向客户的咨询师,我们依据业界的实践,针对公司的核心价值,制定了专业操守相关的规范 ——《ThoughtWorks员工行为准则》。

当然我们不能满足于只是遵循这几条基本的要求,还要做到一些更能体现我们专业范儿的行为。我们面对挑战的环境要展现自信,却不需要表现得居高临下,显得自以为高人一等。跟客户和团队内外小伙伴沟通的时候,应及时同步更新,不要轻易假设某些信息跟他们没有关系,全面提供上下文和自己做出判断的背景信息,不要对他人提出问题和挑战有什么负面的情绪。

对于要推动的事情,我们既然很难全面考虑客户的立场,那么不妨多给几个选项,看看对方是如何权衡。客户是有血有肉有诉求的个体,他们找到我们并不是因为人傻钱多,我们的服务也不仅仅是完成一个个合同要求的交付物,就像我们使用设计精良的产品时一样,客户跟我们合作的体验应该是充满兴奋和激情的。即使做不到兴奋和激情,至少也要痛并快乐着。

展现这些行为都很不容易,特别是还有很多其它压力的时候,需要大量的刻意练习和不断的反思,才能慢慢练就。但如果是随便什么人都能做到,那还叫专业吗?

专业还意味着对于这个社会所承担的道德贡献。我们印象中只关心数字准确与否的注册会计师,在这个群体的道德规范里明确说明,他们的服务对象不仅仅是企业,而是包含社会各方“所有与企业有关并关心企业的人士,可泛指为社会公众”。注册会计的信誉实际是要为社会经济生活中至关重要的市场信誉兜底。会计师是如此,律师这样对社会个体是否能获得公正待遇,以及医生这样与个人生命健康息息相关的专业,在专业技能之上的道德目标就显得愈发重要。

那么ThoughtWorks作为有志成为社会模范的公司,把社会和经济的公正列为了三个支柱之一。跟很多在讲社会责任的公司不同,我们更把对社会的影响当做目的而非手段。这也是为什么我们在各个城市Office举办开拓眼界的session,组织”你我同行”这样的体验活动,积极跟各种社会组织建立合作关系。我们特别希望更多的ThoughtWorker在P3领域能够经历从知道,以至理解,最后到行动的旅程。

MD心声

提升TW的Professionalism水准有什么合适的方法?Professionalism是个很大的话题,不过还是渴望想听到从不同角度的思考。


推荐阅读

Share