筆者之前寫過一篇關(guān)于JDK8特性以及基于JDK8的服務(wù)端架構(gòu)設(shè)計思想(此文已丟失),如今回頭去看,顯然當(dāng)年還是太嫩了,并沒有完全明晰函數(shù)式編程于服務(wù)端架構(gòu)設(shè)計的關(guān)聯(lián)性。但有一個宗旨我覺得還是對的缭保,那就是現(xiàn)下應(yīng)用開發(fā)仍要解決的是數(shù)據(jù)處理而非過去式的數(shù)據(jù)存儲問題。
0. 思考的起點(diǎn):阿里閑魚開源Fish-Redux框架
????????作為一名混跡于中小型企業(yè)的developer蝙茶,我所構(gòu)建的大部分應(yīng)用都是體量偏小的且業(yè)務(wù)邏輯相對簡單艺骂。所以雖然我個人比較熱衷于探索新技術(shù),但是在此之前構(gòu)建的工程當(dāng)中隆夯,即使使用React/React Native構(gòu)建前端或者移動端彻亲,但并沒有過深的引入redux技術(shù)孕锄。而后端往往采用MVC結(jié)構(gòu)足以應(yīng)付。但在以往構(gòu)建前端react工程或者移動端RN工程當(dāng)中苞尝,我也常常體會到邏輯代碼迅速膨脹畸肆,以至于失去可維護(hù)性,同時也被散碎在各個組件當(dāng)中的數(shù)據(jù)(state)維護(hù)所困擾宙址。時常想剝離一些高可復(fù)用的編程模型轴脐,但限于業(yè)務(wù)和工作的安排,這些想法往往也是最終被冷處理抡砂。而在昨天看咸魚團(tuán)隊的開源直播中大咱,通過大神對于Redux的講解,讓我深受啟發(fā)注益。
????????昨日晚上七點(diǎn)半(2019-03-07)碴巾,閑魚團(tuán)隊在釘釘直播平臺上,首次講解其開源的Fish-Redux框架丑搔。直播間很熱鬧厦瓢,因為作為一個跨平臺技術(shù)——Flutter本身從誕生之日起就備受期待,更是被冠以未來跨平臺解決方案之名啤月。同時煮仇,Google團(tuán)隊對Flutter于近期又做了依此更新,讓W(xué)eb前端也可以使用Flutter框架谎仲。無論是從目前跨平臺(Android/IOS/WP)考慮浙垫,還是基于未來快平臺(Fuchsia)考慮,無疑都讓Flutter成為各個技術(shù)團(tuán)隊的重要儲備技術(shù)之一郑诺。而閑魚的開源框架更是給國內(nèi)開發(fā)者打了一劑強(qiáng)心劑夹姥。
1. Fish-Redux框架設(shè)計核心思想
????????要說起Fish-Redux框架,就還是繞不過Flutter框架這個話題辙诞。正如閑魚直播當(dāng)中所說辙售,目前移動端跨平臺的解決方案有三種:
- 基于瀏覽器的HTML5技術(shù);
- 編譯原生代碼技術(shù)倘要, 例如RN圾亏;
-
基于系統(tǒng)C++層次構(gòu)建的跨平臺技術(shù)十拣,例如Flutter封拧,其使用的Dart Engine是構(gòu)建于移動端系統(tǒng)的C++層當(dāng)中。
轉(zhuǎn)自閑魚Flutter應(yīng)用框架Fish Redux
Flutter在設(shè)計時采用了一個高效的分治策略夭问, 即UI完全組件化(Widgets)泽西,再由組件構(gòu)建Layout/Page,例如一個簡單的頁面實現(xiàn)
// Flutter 官方給出的一個示例
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Page
return MaterialApp(
title: 'Flutter layout demo',
// App Bar
home: Scaffold(
appBar: AppBar(
title: Text('Flutter layout demo'),
),
// body container
body: Center(
child: Text('Hello World'),
),
),
);
}
}
組件化是現(xiàn)代前端框架的一個設(shè)計特點(diǎn)缰趋,其主要目的是實現(xiàn)組件邏輯間的解耦捧杉,以及提高組件的復(fù)用性陕见。而對于狀態(tài)(state)的維護(hù),F(xiàn)lutter給出了一個設(shè)計思想公式
這其實是與React(無Redux)的設(shè)計是相似的味抖,我們可以對比來看
class Example extends React.Component{
constructor(props){
super(props);
this.state = {title:"Hello World"}
}
render(){
return(
<h1>{ this.state.title}</h1>
)
}
}
同樣的對于Flutter來說也可以
class MyHomepage extends StatefulWidget {
@override
_MyHomepageState createState() => _MyHomepageState();
}
class _MyHomepageState extends State<MyHomepage> {
String title = "Hello world";
@override
Widget build(BuildContext context) {
return MaterialApp(
body: Center(
child: Text(title),
),
);
}
}
雖然這樣一個分治策略看起來能夠解決組件復(fù)用性和解耦等問題评甜,但是在實際操作中,由于State的特異性和業(yè)務(wù)邏輯的復(fù)雜性仔涩,這種分治策略反而會拖垮我們的架構(gòu)的簡潔忍坷,也無法真正的做到復(fù)用和解耦。因此熔脂,實際工程中佩研,如果采用這種規(guī)范,往往使得代碼變得不可控制的膨脹霞揉,以及代碼的重復(fù)率大大提升旬薯,且往往組件之間的通信問題變得異常復(fù)雜甚至不可解。為了解決該問題适秩,F(xiàn)acebook于2014年提出了Flux架構(gòu)绊序,2015年Redux誕生,將Flux架構(gòu)與函數(shù)式編程進(jìn)行了整合隶症,使其自身成為前端技術(shù)棧當(dāng)中十分重要的一個架構(gòu)政模。其核心思想是構(gòu)建store來存儲包含所有數(shù)據(jù)的state, view并不能直接修改state數(shù)據(jù)片段蚂会,而是需要從action.creator中創(chuàng)建的action淋样,并由store.dispatch對action進(jìn)行分發(fā),當(dāng)store接受到一個action后胁住,他需要變更相應(yīng)的state數(shù)據(jù)片段趁猴,該過程則由Reducer 來完成。具體可參考阮一峰的系列博客彪见,這里不再贅述儡司。
????????而Fish-Redux框架的核心就是在Flutter framework層次與Application層次之間,實現(xiàn)了Redux這樣一個框架規(guī)范余指。并且在此基礎(chǔ)之上捕犬,根據(jù)dart語言的特點(diǎn)(js對象不需要setter/getter)實現(xiàn)了connector部分,以及為優(yōu)化幀率問題而抽象出的adapter部分酵镜。實現(xiàn)了一個完全由數(shù)據(jù)驅(qū)動的架構(gòu)方案
當(dāng)然碉碉,正如閑魚團(tuán)隊在直播當(dāng)中所說,他們從眾多解決方案中篩選出該方案其目的也是為了迎合業(yè)務(wù)場景的需求淮韭。而其函數(shù)編程模型的目的也是為了保證架構(gòu)的可持續(xù)演進(jìn)垢粮。特別是他們提到前端未來可能的NN(神經(jīng)網(wǎng)絡(luò))根據(jù)設(shè)計草稿生成前端代碼的黑盒問題,很有啟發(fā)性靠粪。
2. 存疑:OOP與MVC
????????OOP在誕生之初蜡吧,其目的就是能夠在低耦合的前提下對代碼與數(shù)據(jù)進(jìn)行聚合毫蚓,提高代碼的可讀性,可維護(hù)性和延展性昔善。在我的個人經(jīng)歷中元潘,主要是以java語言來構(gòu)建服務(wù)端應(yīng)用,當(dāng)然期間不乏有采用nodejs君仆,python柬批,.Net以及Go語言構(gòu)建服務(wù)端的業(yè)務(wù)場景⌒涠可以說Java是最典型的OOP語言氮帐,然而實際業(yè)務(wù)當(dāng)中往往有OOP思想不得不被削弱的情況發(fā)生,例如多線程情境下
// 這里不考慮線程安全問題
class ThreadExample implements Runnable{
private int exampleResource = 0;
@Override
public void run(){
exampleResource ++洛姑;
}
public void main(String...args){
ThreadExample te = new ThreadExample();
new Thread(te).start();
new Thread(te).start();
}
}
在該例子當(dāng)中上沐,我們構(gòu)建ThreadExample這樣一個類僅僅是為了實現(xiàn)Runnable接口的run方法,并讓線程共享exampleResource這樣一個資源楞艾。而這個類本身并沒有很強(qiáng)的OOP聚合概念参咙,也就是說我們?yōu)榱说玫揭粋€run函數(shù)而不得不去構(gòu)建一個對象,其代價不能不說是有些昂貴了硫眯。
????????同樣的事情在java以及C#語言技術(shù)體系當(dāng)中并不少見蕴侧。例如java中采用Spring MVC框架實現(xiàn)MVC三層架構(gòu),其Controller和Service往往只是一些函數(shù)的聚合两入,而數(shù)據(jù)則往往是放在Model中展現(xiàn)的净宵。這就給我們提出了一個問題,我們是不是應(yīng)該放棄Controller和Service實現(xiàn)過程中采用類的概念聚合代碼裹纳,而采用函數(shù)式編程模型呢择葡?事實上,這個問題的答案應(yīng)該是肯定的剃氧,jdk8 對Function的補(bǔ)充敏储,以及C#混合F#開發(fā)的過程中,我們可以看到在這個主要以數(shù)據(jù)驅(qū)動的時代朋鞍,用函數(shù)式編程模型來處理業(yè)務(wù)邏輯代碼的聚合和解耦問題已然是一種趨勢已添。Spring技術(shù)棧在5.0版本之后也同樣實現(xiàn)了Flux架構(gòu)。
????????不過也不得不說滥酥,類似Java這種傳統(tǒng)的OOP語言更舞,對于實現(xiàn)函數(shù)式編程仍有困難,一個典型的例子就是Carrying(科里化)恨狈,在Java當(dāng)中實現(xiàn)Carrying會讓代碼變得異常復(fù)雜難懂疏哗,其主要原因就是受到泛型的限制呛讲。
????????OOP編程模型思想的削弱禾怠,似乎給了函數(shù)式編程模型一個鋪墊返奉,而如下情景可能更需要函數(shù)式編程模型的介入。
3. PaaS/FaaS與無處不在的黑盒
????????相信很多人對于Container吗氏, PaaS以及微服務(wù)這些后端技術(shù)已經(jīng)不再陌生芽偏。其實這些歷史進(jìn)程不難讓我們想到,我們正在以更細(xì)小的粒度來拆分我們的業(yè)務(wù)邏輯弦讽。從而提高應(yīng)用的穩(wěn)定性和可維護(hù)性污尉。FaaS誕生也恰恰符合這樣一個歷史規(guī)律。即函數(shù)既是服務(wù)的概念往产。我覺得這一概念的提出被碗,其目的主要有兩點(diǎn):
- 在云服務(wù)部署過程當(dāng)中更具有彈性,(要知道云資源即是成本仿村,所以更好的彈性控制是十分重要的)
- 更為容易控制算力
????????舉一個業(yè)務(wù)場景的例子锐朴,最近在客戶的業(yè)務(wù)當(dāng)中拆解出了一些NLP(自然語義處理)的需求,這驅(qū)使我想去構(gòu)建一個基于內(nèi)網(wǎng)服務(wù)器進(jìn)行集中訓(xùn)練神經(jīng)網(wǎng)絡(luò)或其他機(jī)器模型應(yīng)用的想法蔼囊。那么像構(gòu)建這樣一個應(yīng)用焚志,首先考慮的問題也有兩個:
- 算力使用權(quán)控制
- 神經(jīng)網(wǎng)絡(luò)或機(jī)器學(xué)習(xí)模型可熱插拔
????????所以在設(shè)計系統(tǒng)時把Julia實現(xiàn)的機(jī)器學(xué)習(xí)模型看作是本地化的FaaS,而采用java構(gòu)建一個web應(yīng)用統(tǒng)一根據(jù)時間和算力資源來調(diào)度模型的訓(xùn)練過程畏鼓。如此可見FaaS的概念的必要性酱酬。
????????不過,在細(xì)粒度拆分業(yè)務(wù)時云矫,我們由不得不面臨一個問題就是黑盒膳沽。當(dāng)一個復(fù)雜的應(yīng)用在組織這些FaaS時,往往面對的問題让禀,就是時我們并不知道FaaS是如何實現(xiàn)的贵少,當(dāng)然,我們可以利用OOP數(shù)據(jù)對Function進(jìn)行配置以確保黑盒能夠?qū)尤胛覀兊南到y(tǒng)堆缘。而這恰恰是一個數(shù)據(jù)驅(qū)動的例子滔灶,該過程可以看為數(shù)據(jù)(配置)驅(qū)動FaaS分發(fā)一個Function給我們的系統(tǒng),而我們的系統(tǒng)則可以根據(jù)數(shù)據(jù)狀態(tài)來決定是否消費(fèi)該Fanction吼肥。而這個過程恰恰提供給我們一個將黑盒接入系統(tǒng)的策略——數(shù)據(jù)驅(qū)動录平。
4. 分治策略與數(shù)據(jù)驅(qū)動
????????最近,客戶公司的一個需求是做數(shù)據(jù)的聚合整理(Grouping)缀皱,該聚合過程有些特殊斗这,它不同于傳統(tǒng)聚合過程構(gòu)建樹形結(jié)構(gòu)的數(shù)據(jù),而是由于關(guān)聯(lián)性構(gòu)建了一個無向圖的數(shù)據(jù)結(jié)構(gòu)啤斗,繼而對圖進(jìn)行區(qū)塊劃分表箭,以此來聚合數(shù)據(jù)。
????????另外值得一提的就是該應(yīng)用的原始代碼已經(jīng)膨脹的無可救藥钮莲,很難再進(jìn)行維護(hù)免钻,不夸張的說彼水,輕輕改一行代碼,就會毀掉一大片功能极舔,其耦合程度可見一斑凤覆。
????????那么在這樣的前提下,我嘗試了混合OOP和Function編程模型拆魏,其思想就是構(gòu)建一個無向圖的計算引擎盯桦,由他裝配圖的節(jié)點(diǎn)和區(qū)塊數(shù)據(jù),而具體的裝配過程是通過調(diào)度dispatch來根據(jù)業(yè)務(wù)代碼的需求配置一個function并分發(fā)給計算引擎渤刃。而業(yè)務(wù)代碼每次修改數(shù)據(jù)都會觸發(fā)dispatch調(diào)度function給計算引擎拥峦,實現(xiàn)自動組裝節(jié)點(diǎn)數(shù)據(jù)和區(qū)塊數(shù)據(jù),最終由計算引擎根據(jù)函數(shù)配置來給出數(shù)據(jù)的處理結(jié)果卖子,并重新整理數(shù)據(jù)事镣。
????????就這樣,一個看似復(fù)雜的業(yè)務(wù)邏輯揪胃,通過區(qū)塊和節(jié)點(diǎn)的分治策略就給解決了璃哟。而且由于function都是可配置的,且由dispatch統(tǒng)一調(diào)度喊递,那么該段業(yè)務(wù)代碼的復(fù)用性很好随闪。雖然對比傳統(tǒng)的MVC結(jié)構(gòu)沒有明顯的添加業(yè)務(wù)層次。但是在處理分治過程中骚勘,在效率方面確實付出了一定的讓步铐伴。
????????不過總而言之,采用以數(shù)據(jù)驅(qū)動業(yè)務(wù)邏輯俏讹,以及采用函數(shù)式編程模型当宴,確實給我們解決了一些傳統(tǒng)架構(gòu)上帶來的問題。至于效率的優(yōu)化泽疆,在有限的未來中户矢,我相信會有新的技術(shù)突破和工具來處理,這也就引出了如下一個話題殉疼。
5. 未來與持續(xù)演進(jìn)
????????總結(jié)來說梯浪,采用單向數(shù)據(jù)驅(qū)動和分治策略的函數(shù)式編程模型可以作為我們未來服務(wù)端可持續(xù)演進(jìn)框架的一個思路。而這里我們要立足未來著眼的可能的問題有兩點(diǎn):
- 利用機(jī)器學(xué)習(xí)等非硬編碼過程優(yōu)化分治策略瓢娜。不同于前端挂洛,在服務(wù)端進(jìn)行硬編碼時分治策略的目的往往是解決某個數(shù)據(jù)處理過程的通用性,然而通用性勢必導(dǎo)致分治策略執(zhí)行的效率下降眠砾,其可能的一個思路是采用機(jī)器學(xué)習(xí)等非硬編碼手段通過搜集和發(fā)現(xiàn)數(shù)據(jù)模式來優(yōu)化分治策略虏劲,繼而達(dá)到效率優(yōu)化的效果。
- 單向數(shù)據(jù)驅(qū)動過程中,容納黑盒服務(wù)接入的能力柒巫。往往在最初框架設(shè)計過程中励堡,我們很難考慮到所有的業(yè)務(wù)場景,那么框架的可持續(xù)演進(jìn)就顯得尤為重要吻育。在單向數(shù)據(jù)驅(qū)動的鏈路中,我們同樣無法為更多黑盒服務(wù)預(yù)設(shè)位置淤井,此時FaaS與應(yīng)用的函數(shù)式編程模型就顯得更為優(yōu)秀布疼,因為我們總可以在業(yè)務(wù)鏈路當(dāng)中隨時對黑盒FaaS進(jìn)行插拔,并同時保證系統(tǒng)的穩(wěn)定性币狠。
以上游两,便是我對未來服務(wù)端開發(fā)思路的一個淺顯的理解。希望能與更多相同的朋友交流漩绵。