Reactive设计语言与范式


这是来自Scala语言的TypeSafe公司的Dean Wampler在五月上旬React 2014大会上的演讲,演讲从面向对象范式 DDD领域驱动设计到函数编程范式。最后试图论证Akka是DDD最好的实现,虽然我个人对该观点有保留观点,但是想将大意翻译一下分享其一些精彩观点。原文PDF点击标题。

Dean主要是以Reactive四个原则为衡量标准,对目前各种技术实现进行比较,这四个原则是:

1.Event-Driven事件驱动,异步非堵塞
2.Scalable可伸缩扩展,松耦合,可组合的,分布式的,网络问题是一等公民。
3.Responsive响应式,必须能有响应,哪怕错误。
4.Resilient弹性,失败恢复是一等公民。

且不论这四个原则是否完全合理,如果一味以这四个标准来衡量一切恐怕有所偏颇,比如从我个人角度了解到,Node.js领域的人们对于Scalable的理解就不一样,Node.js认为代码配置一点都不需要修改就能伸缩扩展才是真的Scalable,这点Java和Akka都不一定做到,Akka中有remote和local之分,JVM在内存增加时至少要修改XMX等参数。Akka将网络问题暴露给程序员处理,这本身好像有积极拥抱网络错误的态度,但是也造成抽象泄漏,迫使程序员对代码进行个别定制化,程序通用性也差得多。

好了,废话不多说,让我们继续听听Dean的演讲。

首先,第一个被衡量的范式是一种状态和行为混淆绑定在一起的传统富模型做法,这是与函数风格将状态值和行为函数分离完全相反,说白了,Dean提倡贫血模型,状态和行为捆绑是一种富模型做法。这个范式在四个标准对比如下:
Event-driven: 好处是事件能产生正常的对象(应该是指事件值对象),但是事件处理逻辑会混淆对象的边界。
Scalability: 坏的,不好,因为很多没有必要的领域模型产生过度工程,这使得程序难以划分为微服务,限制了可扩展性,对于高吞吐量系统,将每条记录初始化为对象是昂贵的。
Responsive: 任何代码膨胀和逻辑分散实现都会导致一些隐藏的性能BUG。
Resilient:难于具体化错误处理。

这种富模型的问题是,比如有一个Customer客户对象,客户在各种场景用例下有各种行为状态,如果都塞在同一个Customer对象中,那么无疑使得Customer模型膨胀。(banq注:我个人提倡以DCI或DDD上下文来分解分离)

下面,Dean提出一个重要观点:
OOP面向对象编程最大的错误是用OO方式去实现领域模型

我想注解一下,这里有一个隐含概念,Dean肯定OO分析设计,使用面向对象分析设计是OK,但是你不能用同样的方式去实现设计,编制代码。条条大路通罗马,OOP只是一条路而已,FP也许才是更便捷的那条路。

Dean认为i使用OOP实现领域模型,会导致大量ad-hoc类,基本包装的是基础类型变量或集合,每个用例故事会被切割到大量琐碎的小对象中,不能集中在一起便于阅读分析。难于重用。

OOP面向对象编程强调可变状态优先,这与函数编程强调不变性优先不同,在大多数OOP库包中,会将状态可变重要性超过性能,实现效率低的复制,状态变化当然可以很快,但是别忘记锁的存在,必须尽可能使用无锁的数据结构。

没有纪律约束的可变状态使得BUG难于发现,代码弹性降低,复用难度增加。

Dean的观点不是要消灭观点,而是让状态变化得到约束,比如封装在特定模块中如专门的数据库,如果想在内存中实现状态可变,那么也必须有纪律,封装需要状态变化的地方,让其他更多地方是逻辑上不可变的。

Dean引用Alan Kay对 OOP观点:
OOP仅仅意味着消息,本地状态保留和保护,隐藏状态的处理过程。

Dean认为iKay的这种OOP观点更接近Reactive.

下面Dean开始谈到DDD领域驱动设计了:
DDD是以一种面向对象方式建模,寻找领域中概念模型,领域中事件是第一公民,虽然DDD鼓励以OO方式实现模型,但是这不一定是必须的,也可以使用FP实现DDD.

DDD中倡导状态和行为封装(比如使用聚合根守卫状态),反对贫血模型,但是Dean认为贫血模型应该是首选的。

Dean然后从FP观点对DDD中概念进行了点评:

1. 实体Entity: 有状态,由其唯一标识和生存时间决定。
2.值对象: 封装不变的状态
3.聚合Aggregate: 将对象群有边界的划分在一起,由根实体控制变化。
4.领域事件: 感兴趣的事件,可以作为对象。
5.服务: 一系列不属于任何对象的操作。
6.仓储Repository: 数据存储的抽象
7.工厂: 实例构建的抽象

DEAN认为领域事件 服务和工厂是好的,仓储是避免ORM,Dean也呼吁不要使用ORM,不要抽象数据存储,积极面对它和它的细节, 因为你需要利用这些细节获得最大的性能,ORM破坏性能,限制数据存储的有效应用。

对于聚合Dean提倡使用集合Collection,不要再创建一个个ad-hoc琐碎的聚合对象,使用不可变的集合,因为集合支持强有力的流操作如(filter, map, fold, groupby, etc.),你就不必自己再做一套了。

至于实体和值对象就作为不变性的例外,因为它们代表可变的状态,所有的对象缺省应该是不变的,可变是一种例外特殊情况。

关于DDD提倡的统一语言,Dean认为这听上去很好,但是容易导致臃肿不灵活的代码,开发者更关心的是代码中领域模型,所以提倡一种“实现语言”可能更合适。

因为DDD是针对设计诞生的,关于实现使用了更多抽象概念,也就是没有明确如何实现,DDD以一种非OOP方式实现也许更好。Reactive方式很合适DDD实现。

DDD是鼓励大家去理解业务领域,而不是指定如何实现那些业务领域模型。

Dean在接下来的函数编程中对比了几种函数编程在四个标准上的差别,分别是有界的队列 回调Callback Rx观察者和Actor模型。

函数编程让我们用数学方式去思考,计算机需要我们教会它怎么做,因此我们需要从数学角度告诉它怎么做。

函数编程有组合函数 不可变值以及状态和行为分离(贫血模型)。

具体详细可点按标题查看原文。
[该贴被admin于2014-06-02 11:56修改过]