為什么我不再使用MVC框架

在我最近的工作中带膜,最讓人抓狂的就是為前端開(kāi)發(fā)人員設(shè)計(jì)API匾荆。我們之間的對(duì)話大致就是這樣的:

開(kāi)發(fā)人員:這個(gè)頁(yè)面上有數(shù)據(jù)元素x,y,z…蝗柔,你能不能為我創(chuàng)建一個(gè)API塘辅,響應(yīng)格式為{x: , y:, z: }

我:好吧

我甚至沒(méi)有進(jìn)行進(jìn)一步的爭(zhēng)論绝页。項(xiàng)目結(jié)束時(shí)會(huì)積累大量的API,這些API與經(jīng)常發(fā)生變化的頁(yè)面是關(guān)聯(lián)在一起的寂恬,按照“設(shè)計(jì)”续誉,只要頁(yè)面改變,相應(yīng)的API也要隨之變化初肉,而在此之前屈芜,我們甚至對(duì)此毫不知情,最終朴译,由于形成因素眾多且各平臺(tái)之間存在些許差異井佑,必須創(chuàng)建非常多的API來(lái)滿足這些需求。Sam Newman甚至將這種制度化的過(guò)程稱之為BFF模式眠寿,這種模式建議為每種設(shè)備躬翁、平臺(tái)當(dāng)然還包含APP版本開(kāi)發(fā)特定的API。 Daniel Jacobson在接受InfoQ的采訪時(shí)曾指出盯拱,Netflix頗為勉強(qiáng)地將“體驗(yàn)式API”與“臨時(shí)API(Ephemeral API)”劃上了等號(hào)盒发。 唉……

幾個(gè)月前,我開(kāi)始思考是什么造成了如今的這種現(xiàn)象狡逢,該做些什么來(lái)應(yīng)對(duì)它宁舰,這個(gè)過(guò)程使我開(kāi)始質(zhì)疑應(yīng)用架構(gòu)中最強(qiáng)大的理念,也就是MVC奢浑,我感受到了函數(shù)式反應(yīng)型編程(reactive)的強(qiáng)大威力蛮艰,這個(gè)過(guò)程致力于流程的簡(jiǎn)化,并試圖消除我們這個(gè)行業(yè)在生產(chǎn)率方面的膨脹情緒雀彼。我相信你會(huì)對(duì)我的發(fā)現(xiàn)感興趣的壤蚜。

在每個(gè)用戶界面背后,我們都在使用MVC模式徊哑,也就是模型-視圖-控制器(Model-View-Controller)袜刷。MVC發(fā)明的時(shí)候,Web尚不存在莺丑,當(dāng)時(shí)的軟件架構(gòu)充其量是胖客戶端在原始網(wǎng)絡(luò)中直接與單一數(shù)據(jù)庫(kù)會(huì)話著蟹。但是,幾十年之后梢莽,MVC依然在使用萧豆,持續(xù)地用于OmniChannel應(yīng)用的構(gòu)建。

Angular 2正式版即將發(fā)布蟹漓,在這個(gè)時(shí)間節(jié)點(diǎn)重估MVC模式及各種MVC框架為應(yīng)用架構(gòu)帶來(lái)的貢獻(xiàn)意義重大炕横。

我第一次接觸到MVC是在1990年源内,當(dāng)時(shí)NeXT剛剛發(fā)布Interface Builder(讓人驚訝的是葡粒,如今這款軟件依然發(fā)揮著重大的作用)份殿。當(dāng)時(shí),我們感覺(jué)Interface Builder和MVC是一個(gè)很大的進(jìn)步嗽交。在90年代末期卿嘲,MVC模式用到了HTTP上的任務(wù)中(還記得Struts嗎?)夫壁,如今拾枣,就各個(gè)方面來(lái)講,MVC是所有應(yīng)用架構(gòu)的基本原則盒让。

MVC的影響十分深遠(yuǎn)梅肤,以致于React.js在介紹他們的框架時(shí)都委婉地與其劃清界限:“React實(shí)現(xiàn)的只是MVC中視圖(View)的部分”。

當(dāng)我去年開(kāi)始使用React的時(shí)候邑茄,我感覺(jué)它在某些地方有著明顯的不同:你在某個(gè)地方修改一部分?jǐn)?shù)據(jù)姨蝴,不需要顯式地與View和Model進(jìn)行交互,整個(gè)UI就能瞬間發(fā)生變化(不僅僅是域和表格中的值)肺缕。這也就是說(shuō)左医,我很快就對(duì)React的編程模型感到了失望,在這方面同木,我顯然并不孤獨(dú)浮梢。我分享一下Andre Medeiros的觀點(diǎn):

作為服務(wù)端的API設(shè)計(jì)者,我的結(jié)論是沒(méi)有特別好的方式將API調(diào)用組織到React前端中彤路,這恰恰是因?yàn)镽eact只關(guān)注View秕硝,在它的編程模型中根本不存在控制器。

到目前為止洲尊,F(xiàn)acebook一直致力于在框架層面彌合這一空白缝裤。React團(tuán)隊(duì)起初引入了Flux模式,不過(guò)它依然令人失望颊郎,最近Dan Abramov又提倡另外一種模式憋飞,名為Redux,在一定程度上來(lái)講姆吭,它的方向是正確的榛做,但是在將API關(guān)聯(lián)到前端方面,依然比不上我下面所介紹的方案内狸。

Google發(fā)布過(guò)GWT检眯、Android SDK還有Angular,你可能認(rèn)為他們的工程師熟知何為最好的前端架構(gòu)昆淡,但是當(dāng)你閱讀Angular 2設(shè)計(jì)考量的文章時(shí)锰瘸,便會(huì)不以為然,即便在Google大家也達(dá)成這樣的共識(shí)昂灵,他們是這樣評(píng)價(jià)之前的工作成果的:

Angular 1并不是基于組件的理念構(gòu)建的避凝。相反舞萄,我們需要將控制器與頁(yè)面上各種[元素]進(jìn)行關(guān)聯(lián)(attach),其中包含了我們的自定義邏輯管削。根據(jù)我們自定義的指令如何對(duì)其進(jìn)行封裝(是否包含isolate scope倒脓?),scope會(huì)進(jìn)行關(guān)聯(lián)或繼續(xù)往下傳遞含思。

基于組件的Angular 2看起來(lái)能簡(jiǎn)單一點(diǎn)嗎崎弃?其實(shí)并沒(méi)有好多少。Angular 2的核心包本身就包含了180個(gè)語(yǔ)義(semantics)含潘,整個(gè)框架的語(yǔ)義已經(jīng)接近500個(gè)饲做,這是基于HTML5和CSS3的。誰(shuí)有那么多時(shí)間學(xué)習(xí)和掌握這樣的框架來(lái)構(gòu)建Web應(yīng)用呢遏弱?當(dāng)Angular 3出現(xiàn)的時(shí)候艇炎,情況又該是什么樣子呢?

在使用過(guò)React并了解了Angular 2將會(huì)是什么樣子之后腾窝,我感到有些沮喪:這些框架都系統(tǒng)性地強(qiáng)制我使用BFF“頁(yè)面可替換模式(Screen Scraping)”模式缀踪,按照這種模式,每個(gè)服務(wù)端的API要匹配頁(yè)面上的數(shù)據(jù)集虹脯,不管是輸入的還是輸出的驴娃。

此時(shí),我決定“讓這一切見(jiàn)鬼去吧”循集。我構(gòu)建了一個(gè)Web應(yīng)用唇敞,沒(méi)有使用React、沒(méi)有使用Angular也沒(méi)有使用任何其他的MVC框架咒彤,通過(guò)這種方式疆柔,我看一下是否能夠找到一種在View和底層API之間進(jìn)行更好協(xié)作的方式。

就React來(lái)講镶柱,我最喜歡的一點(diǎn)在于Model和View之間的關(guān)聯(lián)關(guān)系旷档。React不是基于模板的,View本身沒(méi)有辦法請(qǐng)求數(shù)據(jù)(我們只能將數(shù)據(jù)傳遞給View)歇拆,看起來(lái)鞋屈,針對(duì)這一點(diǎn)進(jìn)行探索是一個(gè)很好的方向。

如果看得足夠長(zhǎng)遠(yuǎn)的話故觅,你會(huì)發(fā)現(xiàn)React唯一的目的就是將View分解為一系列(純粹的)函數(shù)和JSX語(yǔ)法:

它實(shí)際上與下面的格式并沒(méi)有什么差別:

V = f( M )

例如厂庇,我當(dāng)前正在從事項(xiàng)目的Web站點(diǎn), Gliiph输吏,就是使用這種函數(shù)構(gòu)建的:


圖1:用于生成站點(diǎn)Slider組件HTML的函數(shù)

這個(gè)函數(shù)需要使用Model來(lái)填充數(shù)據(jù):


圖2:支撐slider的Model

如果用簡(jiǎn)單的JavaScript函數(shù)就能完成任務(wù)权旷,我們?yōu)槭裁催€要用React呢?

虛擬DOM(virtual-dom)贯溅?如果你覺(jué)得需要這樣一種方案的話(我并不確定有很多的人需要這樣)拄氯,其實(shí)有這樣的可選方案躲查,我也期望開(kāi)發(fā)出更多的方案。

GraphQL坤邪?并不完全如此熙含。不要因?yàn)镕acebook大量使用它就對(duì)其產(chǎn)生誤解罚缕,認(rèn)為它一定是對(duì)你有好處的艇纺。GraphQL僅僅是以聲明的方式來(lái)創(chuàng)建視圖模型。強(qiáng)制要求Model匹配View會(huì)給你帶來(lái)麻煩邮弹,而不是解決方案黔衡。React團(tuán)隊(duì)可能會(huì)覺(jué)得使用“客戶端指定查詢(Client-specified queries)”是沒(méi)有問(wèn)題的(就像反應(yīng)型團(tuán)隊(duì)中那樣):

GraphQL完全是由View以及編寫(xiě)它們的前端工程師的需求所驅(qū)動(dòng)的。[…]另一方面腌乡,GraphQL查詢會(huì)精確返回客戶端請(qǐng)求的內(nèi)容盟劫,除此之外,也就沒(méi)什么了与纽。

GraphQL團(tuán)隊(duì)沒(méi)有關(guān)注到JSX語(yǔ)法背后的核心思想:用函數(shù)將Model與View分離侣签。與模板和“前端工程師所編寫(xiě)的查詢”不同,函數(shù)不需要Model來(lái)適配View急迂。

當(dāng)View是由函數(shù)創(chuàng)建的時(shí)候(而不是由模板或查詢所創(chuàng)建)影所,我們就可以按需轉(zhuǎn)換Model,使其按照最合適的形式來(lái)展現(xiàn)View僚碎,不必在Model的形式上添加人為的限制猴娩。

例如,如果View要展現(xiàn)一個(gè)值v勺阐,有一個(gè)圖形化的指示器會(huì)標(biāo)明這個(gè)值是優(yōu)秀卷中、良好還是很差,我們沒(méi)有理由將指示器的值放到Model中:函數(shù)應(yīng)該根據(jù)Model所提供的v值渊抽,來(lái)進(jìn)行簡(jiǎn)單的計(jì)算蟆豫,從而確定指示器的值。

現(xiàn)在懒闷,把這些計(jì)算直接嵌入到View中并不是什么好主意无埃,使View-Model成為一個(gè)純函數(shù)也并非難事,因此當(dāng)我們需要明確的View-Model時(shí)毛雇,就沒(méi)有特殊的理由再使用GraphQL了:

V = f( vm(M) )

作為深諳MDE之道的人嫉称,我相信你更善于編寫(xiě)代碼,而不是元數(shù)據(jù)灵疮,不管它是模板還是像GraphQL這樣的復(fù)雜查詢語(yǔ)言织阅。

這個(gè)函數(shù)式的方式能夠帶來(lái)多項(xiàng)好處。首先震捣,與React類似荔棉,它允許我們將View分解為組件闹炉。它們創(chuàng)建的較為自然的界面允許我們?yōu)閃eb應(yīng)用或Web站點(diǎn)設(shè)置“主題”,或者使用不同的技術(shù)來(lái)渲染View(如原生的方式)润樱。函數(shù)實(shí)現(xiàn)還有可能增強(qiáng)我們實(shí)現(xiàn)反應(yīng)型設(shè)計(jì)的方式渣触。

在接下來(lái)的幾個(gè)月中,可能會(huì)出現(xiàn)開(kāi)發(fā)者交付用JavaScript函數(shù)包裝的基于組件的HTML5主題的情況壹若。這也是最近這段時(shí)間嗅钻,在我的Web站點(diǎn)項(xiàng)目中,我所采用的方式店展,我會(huì)得到一個(gè)模板养篓,然后迅速地將其封裝為JavaScript函數(shù)。我不再使用WordPress赂蕴×基本上花同等的工夫(甚至更少),我就能實(shí)現(xiàn)HTML5和CSS的最佳效果概说。

這種方式也需要在設(shè)計(jì)師和開(kāi)發(fā)人員之間建立一種新型的關(guān)系碧注。任何人都可以編寫(xiě)這些JavaScript函數(shù),尤其是模板的設(shè)計(jì)人員糖赔。人們不需要學(xué)習(xí)綁定方法萍丐、JSX和Angular模板的語(yǔ)法,只掌握簡(jiǎn)單的JavaScript核心函數(shù)就足以讓這一切運(yùn)轉(zhuǎn)起來(lái)挂捻。

有意思的是碉纺,從反應(yīng)型流程的角度來(lái)說(shuō),這些函數(shù)可以部署在最合適的地方:在服務(wù)端或在客戶端均可刻撒。

但最為重要的是骨田,這種方式允許在View與Model之間建立最小的契約關(guān)系,讓Model來(lái)決定如何以最好的方式將其數(shù)據(jù)傳遞給View声怔。讓Model去處理諸如緩存态贤、懶加載、編配以及一致性的問(wèn)題醋火。與模板和GraphQL不同悠汽,這種方式不需要從View的角度來(lái)直接發(fā)送請(qǐng)求。

既然我們有了一種方式將Model與View進(jìn)行解耦芥驳,那么下一個(gè)問(wèn)題就是:在這里該如何創(chuàng)建完整的應(yīng)用模型呢柿冲?“控制器”該是什么樣子的?為了回答這個(gè)問(wèn)題兆旬,讓我們重新回到MVC上來(lái)假抄。

蘋(píng)果公司了解MVC的基本情況,因?yàn)樗麄冊(cè)谏鲜兰o(jì)80年代初,從Xerox PARC“偷來(lái)了”這一模式宿饱,從那時(shí)起熏瞄,他們就堅(jiān)定地實(shí)現(xiàn)這一模式:

圖3:MVC模式

Andre Medeiros曾經(jīng)清晰地指出,這里核心的缺點(diǎn)在于谬以, MVC模式是“交互式的(interactive)”(這與反應(yīng)型截然不同)强饮。在傳統(tǒng)的MVC之中,Action(Controller)將會(huì)調(diào)用Model上的更新方法为黎,在成功(或出錯(cuò))之時(shí)會(huì)確定如何更新View邮丰。他指出,其實(shí)并非必須如此碍舍,這里還有另外一種有效的柠座、反應(yīng)型的處理方式邑雅,我們只需這樣考慮片橡,Action只應(yīng)該將值傳遞給Model,不管輸出是什么淮野,也不必確定Model該如何進(jìn)行更新捧书。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號(hào):575745314 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring骤星,MyBatis经瓷,Netty源碼分析,高并發(fā)洞难、高性能舆吮、分布式、微服務(wù)架構(gòu)的原理队贱,JVM性能優(yōu)化色冀、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源柱嫌,目前受益良多

那核心問(wèn)題就變成了:該如何將Action集成到反應(yīng)型流程中呢锋恬?如果你想理解Action的基礎(chǔ)知識(shí)的話,那么你應(yīng)該看一下TLA+编丘。TLA代表的是“Action中的邏輯時(shí)序(Temporal Logic of Actions)”与学,這是由Dr. Lamport所提出的學(xué)說(shuō),他也因此獲得了圖靈獎(jiǎng)嘉抓。在TLA+中索守,Action是純函數(shù):

data’ = A (data)

我真的非常喜歡TLA+這個(gè)很棒的理念,因?yàn)樗鼜?qiáng)制函數(shù)只轉(zhuǎn)換給定的數(shù)據(jù)集抑片。

按照這種形式卵佛,反應(yīng)型MVC看起來(lái)可能就會(huì)如下所示:

V = f( M.present( A(data) ) )

這個(gè)表達(dá)式規(guī)定當(dāng)Action觸發(fā)的時(shí)候,它會(huì)根據(jù)一組輸入(例如用戶輸入)計(jì)算一個(gè)數(shù)據(jù)集,這個(gè)數(shù)據(jù)是提交到Model中的级遭,然后會(huì)確定是否需要以及如何對(duì)其自身進(jìn)行更新望拖。當(dāng)更新完成后,View會(huì)根據(jù)新的Model狀態(tài)進(jìn)行更新挫鸽。反應(yīng)型的環(huán)就閉合了说敏。Model持久化和獲取其數(shù)據(jù)的方式是與反應(yīng)型流程無(wú)關(guān)的,所以丢郊,它理所應(yīng)當(dāng)?shù)亍安粦?yīng)該由前端工程師來(lái)編寫(xiě)”盔沫。不必因此而感到歉意。

再次強(qiáng)調(diào)枫匾,Action是純函數(shù)架诞,沒(méi)有狀態(tài)和其他的副作用(例如,對(duì)于Model干茉,不會(huì)包含計(jì)數(shù)的日志)谴忧。

反應(yīng)型MVC模式很有意思,因?yàn)槌薓odel以外角虫,所有的事情都是純函數(shù)沾谓。公平來(lái)講,Redux實(shí)現(xiàn)了這種特殊的模式戳鹅,但是帶有React不必要的形式均驶,并且在reducer中,Model和Action之間存在一點(diǎn)不必要的耦合枫虏。Action和接口之間是純粹的消息傳遞妇穴。

這也就是說(shuō),反應(yīng)型MVC并不完整隶债,按照Dan喜歡的說(shuō)法腾它,它并沒(méi)有擴(kuò)展到現(xiàn)實(shí)的應(yīng)用之中。讓我們通過(guò)一個(gè)簡(jiǎn)單的樣例來(lái)闡述這是為什么燃异。

假設(shè)我們需要實(shí)現(xiàn)一個(gè)應(yīng)用來(lái)控制火箭的發(fā)射:一旦我們開(kāi)始倒計(jì)時(shí)携狭,系統(tǒng)將會(huì)遞減計(jì)數(shù)器(counter),當(dāng)它到達(dá)零的時(shí)候回俐,會(huì)將Model中所有未定的狀態(tài)設(shè)置為規(guī)定值逛腿,火箭的發(fā)射將會(huì)進(jìn)行初始化。

這個(gè)應(yīng)用有一個(gè)簡(jiǎn)單的狀態(tài)機(jī):

圖4:火箭發(fā)射的狀態(tài)機(jī)

其中decrement和launch都是“自動(dòng)”的Action仅颇,這意味著我們每次進(jìn)入(或重新進(jìn)入)counting狀態(tài)時(shí)单默,將會(huì)保證進(jìn)行轉(zhuǎn)換的評(píng)估,如果計(jì)數(shù)器的值大于零的話忘瓦,decrement Action將會(huì)繼續(xù)調(diào)用搁廓,如果值為零的話,將會(huì)調(diào)用launchAction。在任何的時(shí)間點(diǎn)都可以觸發(fā)abort Action境蜕,這樣的話蝙场,控制系統(tǒng)將會(huì)轉(zhuǎn)換到aborted狀態(tài)。

在MVC中粱年,這種類型的邏輯將會(huì)在控制器中實(shí)現(xiàn)售滤,并且可能會(huì)由View中的一個(gè)計(jì)時(shí)器來(lái)觸發(fā)。

這一段至關(guān)重要台诗,所以請(qǐng)仔細(xì)閱讀完箩。我們已經(jīng)看到,在TLA+中拉队,Action沒(méi)有副作用弊知,只是計(jì)算結(jié)果的狀態(tài),Model處理Action的輸出并對(duì)其自身進(jìn)行更新粱快。這是與傳統(tǒng)狀態(tài)機(jī)語(yǔ)義的基本區(qū)別秩彤,在傳統(tǒng)的狀態(tài)機(jī)中,Action會(huì)指定結(jié)果狀態(tài)皆尔,也就是說(shuō)呐舔,結(jié)果狀態(tài)是獨(dú)立于Model的币励。在TLA+中慷蠕,所啟用的Action能夠在狀態(tài)表述(也就是View)中進(jìn)行觸發(fā),這些Action不會(huì)直接與觸發(fā)狀態(tài)轉(zhuǎn)換的行為進(jìn)行關(guān)聯(lián)食呻。換句話說(shuō)流炕,狀態(tài)機(jī)不應(yīng)該由連接兩個(gè)狀態(tài)的元組(S1, A, S2)來(lái)進(jìn)行指定,傳統(tǒng)的狀態(tài)機(jī)是這樣做的仅胞,它們?cè)M的形式應(yīng)該是(Sk, Ak1, Ak2,…)每辟,這指定了所有啟用的Action,并給定了一個(gè)狀態(tài)Sk干旧,Action應(yīng)用于系統(tǒng)之后渠欺,將會(huì)計(jì)算出結(jié)果狀態(tài),Model將會(huì)處理更新椎眯。

當(dāng)我們引入“state”對(duì)象時(shí)挠将,TLA+提供了一種更優(yōu)秀的方式來(lái)對(duì)系統(tǒng)進(jìn)行概念化,它將Action和view(僅僅是一種狀態(tài)的表述)進(jìn)行了分離编整。

我們樣例中的Model如下所示:

model = {

counter: ,

started: ,

aborted: ,

launched:

}

系統(tǒng)中四個(gè)(控制)狀態(tài)分別對(duì)應(yīng)于Model中如下的值:

ready = {counter: 10, started: false, aborted: false, launched: false }

counting = {counter: [0..10], started: true, aborted: false, launched: false }

launched = {counter: 0, started: true, aborted: false, launched: true}

aborted = {counter: [0..10], started: true, aborted: true, launched: false}

這個(gè)Model是由系統(tǒng)的所有屬性及其可能的值所指定的舔稀,狀態(tài)則指定了所啟用的Action,它會(huì)給定一組值掌测。這種類型的業(yè)務(wù)邏輯必須要在某個(gè)地方進(jìn)行實(shí)現(xiàn)内贮。我們不能指望用戶能夠知道哪個(gè)Action是否可行。在這方面,沒(méi)有其他的方式夜郁。不過(guò)什燕,這種類型的業(yè)務(wù)邏輯很難編寫(xiě)、調(diào)試和維護(hù)竞端,在沒(méi)有語(yǔ)義對(duì)其進(jìn)行描述時(shí)秋冰,更是如此,比如在MVC中就是這樣婶熬。

讓我們?yōu)榛鸺l(fā)射的樣例編寫(xiě)一些代碼剑勾。從TLA+角度來(lái)講,next-action斷言在邏輯上會(huì)跟在狀態(tài)渲染之后赵颅。當(dāng)前狀態(tài)呈現(xiàn)之后虽另,下一步就是執(zhí)行next-action斷言,如果存在的話饺谬,將會(huì)計(jì)算并執(zhí)行下一個(gè)Action捂刺,這個(gè)Action會(huì)將其數(shù)據(jù)交給Model,Model將會(huì)初始化新?tīng)顟B(tài)的表述募寨,以此類推族展。

(點(diǎn)擊放大圖像)

圖5:火箭發(fā)射器的實(shí)現(xiàn)

需要注意的是,在客戶端/服務(wù)器架構(gòu)下拔鹰,當(dāng)自動(dòng)Action觸發(fā)之后仪缸,我們可能需要使用像WebSocket這樣的協(xié)議(或者在WebSocket不可用的時(shí)候,使用輪詢機(jī)制)來(lái)正確地渲染狀態(tài)表述列肢。

我曾經(jīng)使用Java和JavaScript編寫(xiě)過(guò)一個(gè)很輕量級(jí)的開(kāi)源庫(kù)恰画,它使用TLA+特有的語(yǔ)義來(lái)構(gòu)造狀態(tài)對(duì)象,并提供了樣例瓷马,這些樣例使用WebSocket拴还、輪詢和隊(duì)列實(shí)現(xiàn)瀏覽器/服務(wù)器交互。在火箭發(fā)射器的樣例中可以看到欧聘,我們并非必須要使用那個(gè)庫(kù)片林。一旦理解了如何編寫(xiě),狀態(tài)實(shí)現(xiàn)的編碼相對(duì)來(lái)講是很容易的怀骤。

對(duì)于要引入的新模式來(lái)說(shuō)费封,我相信我們已經(jīng)具備了所有的元素,這個(gè)新模式作為MVC的替代者晒喷,名為SAM模式(狀態(tài)-行為-模型孝偎,State-Action-Model),它具有反應(yīng)型和函數(shù)式的特性凉敲,靈感來(lái)源于React.js和TLA+衣盾。

SAM模式可以通過(guò)如下的表達(dá)式來(lái)進(jìn)行描述:

V = S( vm( M.present( A(data) ) ), nap(M))

它表明在應(yīng)用一個(gè)Action A之后寺旺,View V可以計(jì)算得出,Action會(huì)作為Model的純函數(shù)势决。

在SAM中阻塑,A(Action)、vm(視圖-模型果复,view-model)盼樟、nap(next-action斷言)以及S(狀態(tài)表述)必須都是純函數(shù)慢宗。在SAM中淑际,我們通常所說(shuō)的“狀態(tài)”(系統(tǒng)中屬性的值)要完全局限于Model之中辛燥,改變這些值的邏輯在Model本身之外是不可見(jiàn)的。

隨便提一下迈窟,next-action斷言私植,即nap()是一個(gè)回調(diào),它會(huì)在狀態(tài)表述創(chuàng)建完成车酣,并渲染給用戶時(shí)調(diào)用曲稼。

圖6:狀態(tài)-行為-模型(SAM)模式

模式本身是獨(dú)立于任何協(xié)議的(可以不費(fèi)什么力氣就能在HTTP上實(shí)現(xiàn))和客戶端/服務(wù)器拓?fù)浣Y(jié)構(gòu)的。

SAM并不意味著我們必須要使用狀態(tài)機(jī)的語(yǔ)義來(lái)獲取View的內(nèi)容湖员。如果Action是由View觸發(fā)的贫悄,那next-action斷言就是一個(gè)空函數(shù)。不過(guò)娘摔,這可能是一個(gè)很好的實(shí)踐窄坦,它清晰暴露了底層狀態(tài)機(jī)的控制狀態(tài),因?yàn)楦鶕?jù)(控制)狀態(tài)的不同晰筛,View看起來(lái)可能也是不同的嫡丙。

另一方面,如果你的狀態(tài)機(jī)涉及到自動(dòng)化的Action读第,那么Action和Model都不可能做到純粹的不包含next-action斷言:有些Action將會(huì)變得有狀態(tài),或者M(jìn)odel必須要觸發(fā)Action拥刻,而這本來(lái)并不是它的角色怜瞒。順便提一下,也許并不那么直觀般哼,狀態(tài)對(duì)象并沒(méi)有持有任何的“狀態(tài)”吴汪,它同樣也是純函數(shù),它會(huì)渲染View并計(jì)算next-action斷言蒸眠,這兩者都來(lái)源于Model的屬性值漾橙。

這種新模式的好處在于,它清晰地將CRUD操作從Action中分離了出來(lái)楞卡。Model負(fù)責(zé)它的持久化霜运,將會(huì)通過(guò)CRUD操作來(lái)實(shí)現(xiàn)脾歇,通過(guò)View是無(wú)法進(jìn)行訪問(wèn)的。尤其是淘捡,View永遠(yuǎn)不會(huì)處于“獲取”數(shù)據(jù)的位置藕各,View所能做的唯一的事情就是請(qǐng)求系統(tǒng)中當(dāng)前的狀態(tài)表述并通過(guò)觸發(fā)Action初始化一個(gè)反應(yīng)型流程。

Action僅僅代表了一種具有權(quán)限的通道焦除,以此來(lái)建議Model該怎樣進(jìn)行變更激况。它們本身(在Model方面)并沒(méi)有什么副作用。如果必要的話膘魄,Action會(huì)調(diào)用第三方的API(同樣乌逐,對(duì)Model沒(méi)有副作用),比如說(shuō)创葡,修改地址的Action可能會(huì)希望調(diào)用地址校驗(yàn)服務(wù)黔帕,并將服務(wù)返回的地址提交到Model中。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群蹈丸。交流學(xué)習(xí)群號(hào):575745314 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring成黄,MyBatis,Netty源碼分析逻杖,高并發(fā)奋岁、高性能、分布式荸百、微服務(wù)架構(gòu)的原理闻伶,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系够话。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源蓝翰,目前受益良多

如下就是“修改地址”Action該如何進(jìn)行實(shí)現(xiàn),它會(huì)調(diào)用地址校驗(yàn)的API:

(點(diǎn)擊放大圖像)

圖7:“修改地址”的實(shí)現(xiàn)

模式中的元素女嘲,包括Action和Model畜份,可以進(jìn)行自由地組合:

函數(shù)組合

data’ = A(B(data))

端組合(Peer)(相同的數(shù)據(jù)集可以提交給兩個(gè)Model)

M1.present(data’)

M2.present(data’)

父子組合(父Model控制的數(shù)據(jù)集提交給子Model)

M1.present(data’,M2)

function present(data, child) {

// 執(zhí)行更新

// 同步Model

child.present(c(data))

}

發(fā)布/訂閱組合

M1.on(“topic”, present )

M2.on(“topic”, present )

M1.on(“data”, present )

M2.on(“data”, present )

有些架構(gòu)師可能會(huì)考慮到System of Record和Systems of Engagement,這種模式有助于明確這兩層的接口(圖8)欣尼,Model會(huì)負(fù)責(zé)與systems of record的交互爆雹。

圖8:SAM組合模型

整個(gè)模式本身也是可以進(jìn)行組合的,我們可以實(shí)現(xiàn)運(yùn)行在瀏覽器中的SAM實(shí)例愕鼓,使其支持類似于向?qū)В╳izard)的行為(如ToDo應(yīng)用)钙态,它會(huì)與服務(wù)器端的SAM進(jìn)行交互:

圖9:SAM實(shí)例組合

請(qǐng)注意,里層的SAM實(shí)例是作為狀態(tài)表述的一部分進(jìn)行傳送的菇晃,這個(gè)狀態(tài)表述是由外層的實(shí)例所生成的册倒。

會(huì)話檢查應(yīng)該在Action觸發(fā)之前進(jìn)行(圖10)。SAM能夠啟用一項(xiàng)很有意思的組合磺送,在將數(shù)據(jù)提交給Model之前驻子,View可以調(diào)用一個(gè)第三方的Action灿意,并且要為其提供一個(gè)token和指向系統(tǒng)Action的回調(diào),這個(gè)第三方Action會(huì)進(jìn)行授權(quán)并校驗(yàn)該調(diào)用的合法性拴孤。

圖10:借助SAM實(shí)現(xiàn)會(huì)話管理

從CQRS的角度來(lái)講脾歧,這個(gè)模式?jīng)]有對(duì)查詢(Query)和命令(Command)做特殊的區(qū)分,但是底層的實(shí)現(xiàn)需要進(jìn)行這種區(qū)分演熟。搜索或查詢“Action”只是簡(jiǎn)單地傳遞一組參數(shù)到Model中鞭执。我們可以采用某種約定(如下劃線前綴)來(lái)區(qū)分查詢和命令,或者我們可以在Model上使用兩個(gè)不同的present方法:

{ _name : ‘/^[a]$/i’ } // 名字以A或a開(kāi)頭


{ _customerId: ‘123’ } // id=123的customer

{ _name : ‘/^[a]$/i’ } // 名字以A或a開(kāi)頭 { _customerId: ‘123’ } // id=123的customerModel將會(huì)執(zhí)行必要的操作以匹配查詢芒粹,更新其內(nèi)容并觸發(fā)View的渲染兄纺。類似的約定可以用于創(chuàng)建、更新或刪除Model中的元素化漆。在將Action的輸出傳遞給Model方面估脆,我們可以實(shí)現(xiàn)多種方式(數(shù)據(jù)集、事件座云、Action……)疙赠。每種方式都會(huì)有其優(yōu)勢(shì)和不足,最終這取決于個(gè)人偏好朦拖。我更喜歡數(shù)據(jù)集的方式圃阳。

在異常方面,與React類似璧帝,我們預(yù)期Model會(huì)以屬性值的形式保存異常信息(這些屬性值可能是由Action提交的捍岳,也可能是CRUD操作返回的)。在渲染狀態(tài)表述的時(shí)候睬隶,會(huì)用到屬性值锣夹,以展現(xiàn)異常信息。

在緩存方面苏潜,SAM在狀態(tài)表述層提供了緩存的選項(xiàng)银萍。直觀上來(lái)看,緩存這些狀態(tài)表述函數(shù)的結(jié)果能夠?qū)崿F(xiàn)更高的命中率窖贤,因?yàn)槲覀儸F(xiàn)在是在組件/狀態(tài)層觸發(fā)緩存砖顷,而不是在Action/響應(yīng)層。

該模式的反應(yīng)型和函數(shù)式結(jié)構(gòu)使得功能重放(replay)和單元測(cè)試變得非常容易赃梧。

SAM模式完全改變了前端架構(gòu)的范式,因?yàn)楦鶕?jù)TLA+的基礎(chǔ)理念豌熄,業(yè)務(wù)邏輯可以清晰地描述為:

Action是純函數(shù)

CRUD操作放在Model中

狀態(tài)控制自動(dòng)化的Action

作為API的設(shè)計(jì)者授嘀,從我的角度來(lái)講,這種模式將API設(shè)計(jì)的責(zé)任推到了服務(wù)器端锣险,在View和Model之間保持了最小的契約蹄皱。

Action作為純函數(shù)览闰,能夠跨Model重用,只要某個(gè)Model能夠接受Action所對(duì)應(yīng)的輸出即可巷折。我們可以期望Action庫(kù)压鉴、主題(狀態(tài)表述)甚至Model能夠繁榮發(fā)展起來(lái),因?yàn)樗鼈儸F(xiàn)在能夠獨(dú)立地進(jìn)行組合锻拘。

借助SAM模式油吭,微服務(wù)能夠非常自然地支撐Model。像Hivepod.io這樣的框架能夠插入進(jìn)來(lái)署拟,就像它本來(lái)就在這層似得婉宰。

最為重要的是,這種模式像React一樣推穷,不需要任何的數(shù)據(jù)綁定或模板心包。

隨著時(shí)間的推移,我希望能夠推動(dòng)瀏覽器永久添加虛擬DOM的特性馒铃,新的狀態(tài)表述能夠通過(guò)專有API直接進(jìn)行處理蟹腾。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號(hào):575745314 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring区宇,MyBatis娃殖,Netty源碼分析,高并發(fā)萧锉、高性能珊随、分布式、微服務(wù)架構(gòu)的原理柿隙,JVM性能優(yōu)化叶洞、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源禀崖,目前受益良多

我發(fā)現(xiàn)這個(gè)旅程將會(huì)帶來(lái)一定的革新性:在過(guò)去的幾十年中衩辟,面向?qū)ο笏坪鯚o(wú)處不在,但它已經(jīng)一去不返了波附。我現(xiàn)在只能按照反應(yīng)型和函數(shù)式來(lái)進(jìn)行思考艺晴。我借助SAM所構(gòu)建的東西及其構(gòu)建速度都是前所未有的。另外掸屡,我能夠關(guān)注于API和服務(wù)的設(shè)計(jì)封寞,它們不再遵循由前端決定的模式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仅财,一起剝皮案震驚了整個(gè)濱河市狈究,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盏求,老刑警劉巖抖锥,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亿眠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡磅废,警方通過(guò)查閱死者的電腦和手機(jī)纳像,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拯勉,“玉大人竟趾,你說(shuō)我怎么就攤上這事∶蘸埃” “怎么了潭兽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)斗遏。 經(jīng)常有香客問(wèn)我山卦,道長(zhǎng),這世上最難降的妖魔是什么诵次? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任账蓉,我火速辦了婚禮,結(jié)果婚禮上逾一,老公的妹妹穿的比我還像新娘铸本。我一直安慰自己,他們只是感情好遵堵,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布箱玷。 她就那樣靜靜地躺著,像睡著了一般陌宿。 火紅的嫁衣襯著肌膚如雪锡足。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天壳坪,我揣著相機(jī)與錄音舶得,去河邊找鬼。 笑死爽蝴,一個(gè)胖子當(dāng)著我的面吹牛沐批,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝎亚,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼九孩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了发框?” 一聲冷哼從身側(cè)響起捻撑,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缤底,沒(méi)想到半個(gè)月后顾患,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡个唧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年江解,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徙歼。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡犁河,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出魄梯,到底是詐尸還是另有隱情桨螺,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布酿秸,位于F島的核電站灭翔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏辣苏。R本人自食惡果不足惜肝箱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稀蟋。 院中可真熱鬧煌张,春花似錦、人聲如沸退客。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萌狂。三九已至档玻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粥脚,已是汗流浹背窃肠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刷允,地道東北人冤留。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像树灶,于是被迫代替她去往敵國(guó)和親纤怒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,860評(píng)論 25 707
  • 今天提前完成本月目標(biāo)天通,深水區(qū)游3圈泊窘,我跟著一個(gè)目標(biāo)老爺,結(jié)果很容易游了3圈,看來(lái)還得有目標(biāo)烘豹,繼續(xù)努力瓜贾,爭(zhēng)取一口氣游...
    影子3623253閱讀 95評(píng)論 0 0
  • 圖片發(fā)自簡(jiǎn)書(shū)App看不見(jiàn),卻不忘携悯。 思念祭芦,相思, 是一個(gè)人的苦痛憔鬼, 卻是兩個(gè)人的故事龟劲。
    燒火一條柴閱讀 227評(píng)論 14 3
  • 常常感慨為什么有的人談戀愛(ài)好容易啊,總是一不留神就跌進(jìn)了愛(ài)河轴或,在我獨(dú)善其身的日子里昌跌,對(duì)方身邊的人已經(jīng)換了又換,且每...
    kylin進(jìn)化論閱讀 152評(píng)論 0 0
  • 這是一段青澀的回憶照雁,一個(gè)女孩兒的仰慕蚕愤,一個(gè)教官的溫柔。而這真的僅有十三天的光陰嗎囊榜? “離我兩米開(kāi)外”王教官...
    牛佳音閱讀 149評(píng)論 0 1