日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

原創: 張衛濱 譯

Jean-Jacques Dubray是一名資深工程師,他最近引入了一個新的模式:狀態-行為-模(State-Action-Model,SAM)。SAM是一個函數式反應型的編程模式,它致力于簡化數據Model和View之間的交互。它究竟有何優點值得作者棄用MVC呢?

話題起因

在我最近的工作中,最讓人抓狂的就是為前端開發人員設計API。我們之間的對話大致就是這樣的:

開發人員:這個頁面上有數據元素x,y,z…,你能不能為我創建一個API,響應格式為{x: , y:, z: }我:好吧

我甚至沒有進行進一步的爭論。項目結束時會積累大量的API,這些API與經常發生變化的頁面是關聯在一起的,按照“設計”,只要頁面改變,相應的API也要隨之變化,而在此之前,我們甚至對此毫不知情,最終,由于形成因素眾多且各平臺之間存在些許差異,必須創建非常多的API來滿足這些需求。

Sam Newman甚至將這種制度化的過程稱之為BFF模式,這種模式建議為每種設備、平臺當然還包含App版本開發特定的API。 Daniel Jacobson在接受InfoQ的采訪時曾指出,Netflix頗為勉強地將“體驗式API”與“臨時API(Ephemeral API)”劃上了等號。 唉……

幾個月前,我開始思考是什么造成了如今的這種現象,該做些什么來應對它,這個過程使我開始質疑應用架構中最強大的理念,也就是MVC,我感受到了函數式反應型編程(reactive)的強大威力,這個過程致力于流程的簡化,并試圖消除我們這個行業在生產率方面的膨脹情緒。我相信你會對我的發現感興趣的。

MVC的輝煌過去與現存問題

在每個用戶界面背后,我們都在使用MVC模式,也就是模型-視圖-控制器(Model-View-Controller)。MVC發明的時候,Web尚不存在,當時的軟件架構充其量是胖客戶端在原始網絡中直接與單一數據庫會話。但是,幾十年之后,MVC依然在使用,持續地用于OmniChannel應用的構建。

Angular 2正式版即將發布,在這個時間節點重估MVC模式及各種MVC框架為應用架構帶來的貢獻意義重大。

我第一次接觸到MVC是在1990年,當時NeXT剛剛發布Interface Builder(讓人驚訝的是,如今這款軟件依然發揮著重大的作用)。當時,我們感覺Interface Builder和MVC是一個很大的進步。在90年代末期,MVC模式用到了HTTP上的任務中(還記得Struts嗎?),如今,就各個方面來講,MVC是所有應用架構的基本原則。

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

當我去年開始使用React的時候,我感覺它在某些地方有著明顯的不同:你在某個地方修改一部分數據,不需要顯式地與View和Model進行交互,整個UI就能瞬間發生變化(不僅僅是域和表格中的值)。這也就是說,我很快就對React的編程模型感到了失望,在這方面,我顯然并不孤獨。我分享一下Andre Medeiros的觀點:

React在很多方面都讓我感到失望,它主要是通過設計不佳的API來引導程序員[…]將多項關注點混合到一個組件之中。

作為服務端的API設計者,我的結論是沒有特別好的方式將API調用組織到React前端中,這恰恰是因為React只關注View,在它的編程模型中根本不存在控制器。

到目前為止,Facebook一直致力于在框架層面彌合這一空白。React團隊起初引入了Flux模式,不過它依然令人失望,最近Dan Abramov又提倡另外一種模式,名為Redux,在一定程度上來講,它的方向是正確的,但是在將API關聯到前端方面,依然比不上我下面所介紹的方案。

google發布過GWT、Android SDK還有Angular,你可能認為他們的工程師熟知何為最好的前端架構,但是當你閱讀Angular 2設計考量的文章時,便會不以為然,即便在Google大家也達成這樣的共識,他們是這樣評價之前的工作成果的:

Angular 1并不是基于組件的理念構建的。相反,我們需要將控制器與頁面上各種[元素]進行關聯(attach),其中包含了我們的自定義邏輯。根據我們自定義的指令如何對其進行封裝(是否包含isolate scope?),scope會進行關聯或繼續往下傳遞。

基于組件的Angular 2看起來能簡單一點嗎?其實并沒有好多少。Angular 2的核心包本身就包含了180個語義(semantics),整個框架的語義已經接近500個,這是基于html5和css3的。誰有那么多時間學習和掌握這樣的框架來構建Web應用呢?當Angular 3出現的時候,情況又該是什么樣子呢?

在使用過React并了解了Angular 2將會是什么樣子之后,我感到有些沮喪:這些框架都系統性地強制我使用BFF“頁面可替換模式(Screen Scraping)”模式,按照這種模式,每個服務端的API要匹配頁面上的數據集,不管是輸入的還是輸出的。

棄用MVC之后怎么走?

此時,我決定“讓這一切見鬼去吧”。我構建了一個Web應用,沒有使用React、沒有使用Angular也沒有使用任何其他的MVC框架,通過這種方式,我看一下是否能夠找到一種在View和底層API之間進行更好協作的方式。

就React來講,我最喜歡的一點在于Model和View之間的關聯關系。React不是基于模板的,View本身沒有辦法請求數據(我們只能將數據傳遞給View),看起來,針對這一點進行探索是一個很好的方向。

如果看得足夠長遠的話,你會發現React唯一的目的就是將View分解為一系列(純粹的)函數和JSX語法:

<V params={M}/>

它實際上與下面的格式并沒有什么差別:

V = f( M )

例如,我當前正在從事項目的Web站點, Gliiph,就是使用這種函數構建的:

長文觀點丨為什么我不再使用MVC框架?

圖1:用于生成站點Slider組件HTML的函數

這個函數需要使用Model來填充數據:

長文觀點丨為什么我不再使用MVC框架?

圖2:支撐slider的Model

如果用簡單的JAVAScript函數就能完成任務,我們為什么還要用React呢?

虛擬DOM(virtual-dom)?如果你覺得需要這樣一種方案的話(我并不確定有很多的人需要這樣),其實有這樣的可選方案,我也期望開發出更多的方案。

GraphQL?并不完全如此。不要因為Facebook大量使用它就對其產生誤解,認為它一定是對你有好處的。GraphQL僅僅是以聲明的方式來創建視圖模型。強制要求Model匹配View會給你帶來麻煩,而不是解決方案。React團隊可能會覺得使用“客戶端指定查詢(Client-specified queries)”是沒有問題的(就像反應型團隊中那樣):

GraphQL完全是由View以及編寫它們的前端工程師的需求所驅動的。[…]另一方面,GraphQL查詢會精確返回客戶端請求的內容,除此之外,也就沒什么了。

GraphQL團隊沒有關注到JSX語法背后的核心思想:用函數將Model與View分離。與模板和“前端工程師所編寫的查詢”不同,函數不需要Model來適配View。

當View是由函數創建的時候(而不是由模板或查詢所創建),我們就可以按需轉換Model,使其按照最合適的形式來展現View,不必在Model的形式上添加人為的限制。

例如,如果View要展現一個值v,有一個圖形化的指示器會標明這個值是優秀、良好還是很差,我們沒有理由將指示器的值放到Model中:函數應該根據Model所提供的v值,來進行簡單的計算,從而確定指示器的值。

現在,把這些計算直接嵌入到View中并不是什么好主意,使View-Model成為一個純函數也并非難事,因此當我們需要明確的View-Model時,就沒有特殊的理由再使用GraphQL了:

V = f( vm(M) )

作為深諳MDE之道的人,我相信你更善于編寫代碼,而不是元數據,不管它是模板還是像GraphQL這樣的復雜查詢語言。

這個函數式的方式能夠帶來多項好處。首先,與React類似,它允許我們將View分解為組件。它們創建的較為自然的界面允許我們為Web應用或Web站點設置“主題”,或者使用不同的技術來渲染View(如原生的方式)。函數實現還有可能增強我們實現反應型設計的方式。

在接下來的幾個月中,可能會出現開發者交付用JavaScript函數包裝的基于組件的HTML5主題的情況。這也是最近這段時間,在我的Web站點項目中,我所采用的方式,我會得到一個模板,然后迅速地將其封裝為JavaScript函數。我不再使用wordPress/ target=_blank class=infotextkey>WordPress。基本上花同等的工夫(甚至更少),我就能實現HTML5和CSS的最佳效果。

這種方式也需要在設計師和開發人員之間建立一種新型的關系。任何人都可以編寫這些JavaScript函數,尤其是模板的設計人員。人們不需要學習綁定方法、JSX和Angular模板的語法,只掌握簡單的JavaScript核心函數就足以讓這一切運轉起來。

有意思的是,從反應型流程的角度來說,這些函數可以部署在最合適的地方:在服務端或在客戶端均可。

最為重要的是,這種方式允許在View與Model之間建立最小的契約關系,讓Model來決定如何以最好的方式將其數據傳遞給View。讓Model去處理諸如緩存、懶加載、編配以及一致性的問題。與模板和GraphQL不同,這種方式不需要從View的角度來直接發送請求。

既然我們有了一種方式將Model與View進行解耦,那么下一個問題就是:在這里該如何創建完整的應用模型呢?“控制器”該是什么樣子的?為了回答這個問題,讓我們重新回到MVC上來。

蘋果公司了解MVC的基本情況,因為他們在上世紀80年代初,從Xerox PARC“偷來了”這一模式,從那時起,他們就堅定地實現這一模式:

長文觀點丨為什么我不再使用MVC框架?

圖3:MVC模式

Andre Medeiros曾經清晰地指出,這里核心的缺點在于, MVC模式是“交互式的(interactive)”(這與反應型截然不同)。在傳統的MVC之中,Action(Controller)將會調用Model上的更新方法,在成功(或出錯)之時會確定如何更新View。他指出,其實并非必須如此,這里還有另外一種有效的、反應型的處理方式,我們只需這樣考慮,Action只應該將值傳遞給Model,不管輸出是什么,也不必確定Model該如何進行更新。

那核心問題就變成了:該如何將Action集成到反應型流程中呢?如果你想理解Action的基礎知識的話,那么你應該看一下TLA+。TLA代表的是“Action中的邏輯時序(Temporal Logic of Actions)”,這是由Dr. Lamport所提出的學說,他也因此獲得了圖靈獎。在TLA+中,Action是純函數:

data’ = A (data)

我真的非常喜歡TLA+這個很棒的理念,因為它強制函數只轉換給定的數據集。

按照這種形式,反應型MVC看起來可能就會如下所示:

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

這個表達式規定當Action觸發的時候,它會根據一組輸入(例如用戶輸入)計算一個數據集,這個數據是提交到Model中的,然后會確定是否需要以及如何對其自身進行更新。當更新完成后,View會根據新的Model狀態進行更新。反應型的環就閉合了。Model持久化和獲取其數據的方式是與反應型流程無關的,所以,它理所應當地“不應該由前端工程師來編寫”。不必因此而感到歉意。

再次強調,Action是純函數,沒有狀態和其他的副作用(例如,對于Model,不會包含計數的日志)。

反應型MVC模式很有意思,因為除了Model以外,所有的事情都是純函數。公平來講,Redux實現了這種特殊的模式,但是帶有React不必要的形式,并且在reducer中,Model和Action之間存在一點不必要的耦合。Action和接口之間是純粹的消息傳遞。

這也就是說,反應型MVC并不完整,按照Dan喜歡的說法,它并沒有擴展到現實的應用之中。讓我們通過一個簡單的樣例來闡述這是為什么。

假設我們需要實現一個應用來控制火箭的發射:一旦我們開始倒計時,系統將會遞減計數器(counter),當它到達零的時候,會將Model中所有未定的狀態設置為規定值,火箭的發射將會進行初始化。

長文觀點丨為什么我不再使用MVC框架?

這個應用有一個簡單的狀態機:

長文觀點丨為什么我不再使用MVC框架?

圖4:火箭發射的狀態機

其中decrement和launch都是“自動”的Action,這意味著我們每次進入(或重新進入)counting狀態時,將會保證進行轉換的評估,如果計數器的值大于零的話,decrement Action將會繼續調用,如果值為零的話,將會調用launchAction。在任何的時間點都可以觸發abort Action,這樣的話,控制系統將會轉換到aborted狀態。

在MVC中,這種類型的邏輯將會在控制器中實現,并且可能會由View中的一個計時器來觸發。

這一段至關重要,所以請仔細閱讀。我們已經看到,在TLA+中,Action沒有副作用,只是計算結果的狀態,Model處理Action的輸出并對其自身進行更新。這是與傳統狀態機語義的基本區別,在傳統的狀態機中,Action會指定結果狀態,也就是說,結果狀態是獨立于Model的。

在TLA+中,所啟用的Action能夠在狀態表述(也就是View)中進行觸發,這些Action不會直接與觸發狀態轉換的行為進行關聯。換句話說,狀態機不應該由連接兩個狀態的元組(S1, A, S2)來進行指定,傳統的狀態機是這樣做的,它們元組的形式應該是(Sk, Ak1, Ak2,…),這指定了所有啟用的Action,并給定了一個狀態Sk,Action應用于系統之后,將會計算出結果狀態,Model將會處理更新。

當我們引入“state”對象時,TLA+提供了一種更優秀的方式來對系統進行概念化,它將Action和view(僅僅是一種狀態的表述)進行了分離。

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

model = {

counter: ,

started: ,

aborted: ,

launched:

}

系統中四個(控制)狀態分別對應于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}

這個Model是由系統的所有屬性及其可能的值所指定的,狀態則指定了所啟用的Action,它會給定一組值。這種類型的業務邏輯必須要在某個地方進行實現。我們不能指望用戶能夠知道哪個Action是否可行。在這方面,沒有其他的方式。不過,這種類型的業務邏輯很難編寫、調試和維護,在沒有語義對其進行描述時,更是如此,比如在MVC中就是這樣。

讓我們為火箭發射的樣例編寫一些代碼。從TLA+角度來講,next-action斷言在邏輯上會跟在狀態渲染之后。當前狀態呈現之后,下一步就是執行next-action斷言,如果存在的話,將會計算并執行下一個Action,這個Action會將其數據交給Model,Model將會初始化新狀態的表述,以此類推。

長文觀點丨為什么我不再使用MVC框架?

圖5:火箭發射器的實現

需要注意的是,在客戶端/服務器架構下,當自動Action觸發之后,我們可能需要使用像WebSocket這樣的協議(或者在WebSocket不可用的時候,使用輪詢機制)來正確地渲染狀態表述。

我曾經使用Java和JavaScript編寫過一個很輕量級的開源庫,它使用TLA+特有的語義來構造狀態對象,并提供了樣例,這些樣例使用WebSocket、輪詢和隊列實現瀏覽器/服務器交互。在火箭發射器的樣例中可以看到,我們并非必須要使用那個庫。一旦理解了如何編寫,狀態實現的編碼相對來講是很容易的。

新模式——SAM模式

對于要引入的新模式來說,我相信我們已經具備了所有的元素,這個新模式作為MVC的替代者,名為SAM模式(狀態-行為-模型,State-Action-Model),它具有反應型和函數式的特性,靈感來源于React.js和TLA+。

SAM模式可以通過如下的表達式來進行描述:

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

它表明在應用一個Action A之后,View V可以計算得出,Action會作為Model的純函數。

在SAM中,A(Action)、vm(視圖-模型,view-model)、nap(next-action斷言)以及S(狀態表述)必須都是純函數。在SAM中,我們通常所說的“狀態”(系統中屬性的值)要完全局限于Model之中,改變這些值的邏輯在Model本身之外是不可見的。

隨便提一下,next-action斷言,即nap()是一個回調,它會在狀態表述創建完成,并渲染給用戶時調用。

長文觀點丨為什么我不再使用MVC框架?

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

模式本身是獨立于任何協議的(可以不費什么力氣就能在HTTP上實現)和客戶端/服務器拓撲結構的。

SAM并不意味著我們必須要使用狀態機的語義來獲取View的內容。如果Action是由View觸發的,那next-action斷言就是一個空函數。不過,這可能是一個很好的實踐,它清晰暴露了底層狀態機的控制狀態,因為根據(控制)狀態的不同,View看起來可能也是不同的。

另一方面,如果你的狀態機涉及到自動化的Action,那么Action和Model都不可能做到純粹的不包含next-action斷言:有些Action將會變得有狀態,或者Model必須要觸發Action,而這本來并不是它的角色。順便提一下,也許并不那么直觀,狀態對象并沒有持有任何的“狀態”,它同樣也是純函數,它會渲染View并計算next-action斷言,這兩者都來源于Model的屬性值。

這種新模式的好處在于,它清晰地將CRUD操作從Action中分離了出來。Model負責它的持久化,將會通過CRUD操作來實現,通過View是無法進行訪問的。尤其是,View永遠不會處于“獲取”數據的位置,View所能做的唯一的事情就是請求系統中當前的狀態表述并通過觸發Action初始化一個反應型流程。

Action僅僅代表了一種具有權限的通道,以此來建議Model該怎樣進行變更。它們本身(在Model方面)并沒有什么副作用。如果必要的話,Action會調用第三方的API(同樣,對Model沒有副作用),比如說,修改地址的Action可能會希望調用地址校驗服務,并將服務返回的地址提交到Model中。

如下就是“修改地址”Action該如何進行實現,它會調用地址校驗的API:

長文觀點丨為什么我不再使用MVC框架?

圖7:“修改地址”的實現

模式中的元素,包括Action和Model,可以進行自由地組合:

函數組合

data’ = A(B(data))

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

M1.present(data’)

M2.present(data’)

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

M1.present(data’,M2)

function present(data, child) {

// 執行更新

// 同步Model

child.present(c(data))

}

發布/訂閱組合

M1.on(“topic”, present )

M2.on(“topic”, present )

M1.on(“data”, present )

M2.on(“data”, present )

有些架構師可能會考慮到System of Record和Systems of Engagement,這種模式有助于明確這兩層的接口(圖8),Model會負責與systems of record的交互。

長文觀點丨為什么我不再使用MVC框架?

圖8:SAM組合模型

整個模式本身也是可以進行組合的,我們可以實現運行在瀏覽器中的SAM實例,使其支持類似于向導(wizard)的行為(如ToDo應用),它會與服務器端的SAM進行交互:

長文觀點丨為什么我不再使用MVC框架?

圖9:SAM實例組合

請注意,里層的SAM實例是作為狀態表述的一部分進行傳送的,這個狀態表述是由外層的實例所生成的。

會話檢查應該在Action觸發之前進行(圖10)。SAM能夠啟用一項很有意思的組合,在將數據提交給Model之前,View可以調用一個第三方的Action,并且要為其提供一個token和指向系統Action的回調,這個第三方Action會進行授權并校驗該調用的合法性。

長文觀點丨為什么我不再使用MVC框架?

圖10:借助SAM實現會話管理

從CQRS的角度來講,這個模式沒有對查詢(Query)和命令(Command)做特殊的區分,但是底層的實現需要進行這種區分。搜索或查詢“Action”只是簡單地傳遞一組參數到Model中。我們可以采用某種約定(如下劃線前綴)來區分查詢和命令,或者我們可以在Model上使用兩個不同的present方法:

{ _name : ‘/^[a]$/i’ } // 名字以A或a開頭
{ _customerId: ‘123’ } // id=123的customer

 

Model將會執行必要的操作以匹配查詢,更新其內容并觸發View的渲染。類似的約定可以用于創建、更新或刪除Model中的元素。在將Action的輸出傳遞給Model方面,我們可以實現多種方式(數據集、事件、Action……)。每種方式都會有其優勢和不足,最終這取決于個人偏好。我更喜歡數據集的方式。

在異常方面,與React類似,我們預期Model會以屬性值的形式保存異常信息(這些屬性值可能是由Action提交的,也可能是CRUD操作返回的)。在渲染狀態表述的時候,會用到屬性值,以展現異常信息。

在緩存方面,SAM在狀態表述層提供了緩存的選項。直觀上來看,緩存這些狀態表述函數的結果能夠實現更高的命中率,因為我們現在是在組件/狀態層觸發緩存,而不是在Action/響應層。

該模式的反應型和函數式結構使得功能重放(replay)和單元測試變得非常容易。

SAM模式完全改變了前端架構的范式,因為根據TLA+的基礎理念,業務邏輯可以清晰地描述為:

  • Action是純函數
  • CRUD操作放在Model中
  • 狀態控制自動化的Action

作為API的設計者,從我的角度來講,這種模式將API設計的責任推到了服務器端,在View和Model之間保持了最小的契約。

Action作為純函數,能夠跨Model重用,只要某個Model能夠接受Action所對應的輸出即可。我們可以期望Action庫、主題(狀態表述)甚至Model能夠繁榮發展起來,因為它們現在能夠獨立地進行組合。

借助SAM模式,微服務能夠非常自然地支撐Model。像Hivepod.io這樣的框架能夠插入進來,就像它本來就在這層似得。

最為重要的是,這種模式像React一樣,不需要任何的數據綁定或模板。

隨著時間的推移,我希望能夠推動瀏覽器永久添加虛擬DOM的特性,新的狀態表述能夠通過專有API直接進行處理。

我發現這個旅程將會帶來一定的革新性:在過去的幾十年中,面向對象似乎無處不在,但它已經一去不返了。我現在只能按照反應型和函數式來進行思考。我借助SAM所構建的東西及其構建速度都是前所未有的。另外,我能夠關注于API和服務的設計,它們不再遵循由前端決定的模式。

分享到:
標簽:框架 MVC
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定