小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

理想的應(yīng)用框架

 icecity1306 2015-06-24

背景

在過去對(duì)框架的設(shè)計(jì)中,我收到過的最有用的建議是:“不要一開始就根據(jù)現(xiàn)有的技術(shù)去整合和改進(jìn)。而是先搞清楚你覺得最理想的框架應(yīng)該是怎樣的,再根據(jù)現(xiàn)在的技術(shù)去評(píng)估,的確實(shí)現(xiàn)不了時(shí)再妥協(xié)。這樣才能做出真正有意義的框架。”
在這篇文章里,就讓我們按照這樣一條建議來探索一下現(xiàn)在的 web 框架最終可以進(jìn)化成的樣子,你絕對(duì)會(huì)被驚艷到。

前端,還是從前端說起。前端目前的現(xiàn)狀是,隨著早期的 Backbone,近期的 Angular、React 等框架的興起,前端在 模塊化、組件化 兩個(gè)方向上已經(jīng)形成了一定的行業(yè)共識(shí)。在此基礎(chǔ)上,React 的 FLUX、Relay 則是進(jìn)一步的對(duì)前端應(yīng)用架構(gòu)的探索。這些技術(shù)在目前國內(nèi)的大公司、大團(tuán)隊(duì)內(nèi)部實(shí)際上都落地得非常好,因?yàn)楹苋菀缀凸緝?nèi)部已有的后端技術(shù)棧結(jié)合。而且這些純前端框架的配套技術(shù)方案一般比較成熟,例如在支付寶確定使用 React,其實(shí)有一部分原因是它兼容 IE8,并且有服務(wù)器端渲染方案來加速首屏。

相比之下,像 Meteor 這類從前到后包辦的框架就較難落地。雖然能極大地提高開發(fā)效率,整體架構(gòu)非常先進(jìn),但架構(gòu)的每一個(gè)層級(jí)往往不容易達(dá)到行業(yè)內(nèi)的頂尖標(biāo)準(zhǔn)。特別是在服務(wù)器端,對(duì)大公司來說,通常都有適合自己業(yè)務(wù)的服務(wù)器集群、數(shù)據(jù)庫方案,并且經(jīng)受過考驗(yàn)。因此當(dāng)一個(gè)團(tuán)隊(duì)一上手就要做面向十萬級(jí)、甚至百萬級(jí)用戶的產(chǎn)品時(shí),是不太愿意冒風(fēng)險(xiǎn)去嘗試的。反而是個(gè)人開發(fā)者、創(chuàng)業(yè)型的團(tuán)隊(duì)會(huì)愿意去用,因?yàn)榇_實(shí)能在短時(shí)間內(nèi)高效地開發(fā)出可用的產(chǎn)品出來。包括像 Leancloud 提出的這類型的服務(wù),也是非常受歡迎的。

這種現(xiàn)狀,就是理想和現(xiàn)實(shí)的一個(gè)爭論。Meteor 的方式能滿足我對(duì)開發(fā)效率的理想,而團(tuán)隊(duì)已有的技術(shù)方案能保障穩(wěn)定。能否整合其中的優(yōu)勢(shì),不妨讓我們進(jìn)一步來細(xì)化一下對(duì)框架的希望:

- 有強(qiáng)大的前后端一致的數(shù)據(jù)模型層
  - 代碼可以可以復(fù)用。例如我有一個(gè) User 模型,當(dāng)我創(chuàng)建一個(gè)新的 user 時(shí),user 上的字段驗(yàn)證等方法是前后端通用的,由框架自動(dòng)幫我區(qū)別前后端環(huán)境。
  - 數(shù)據(jù)模型和前端框架沒有耦合,但可以輕松結(jié)合。這樣在前端渲染型的框架進(jìn)一步升級(jí)時(shí),不影響我的業(yè)務(wù)邏輯代碼。
  - 由數(shù)據(jù)模型層提供自動(dòng)的數(shù)據(jù)更新機(jī)制。例如我在前端要獲取 id 為 1 的用戶,并且如果服務(wù)器端數(shù)據(jù)有更新的話,就自動(dòng)幫我更新,不需要我自己去實(shí)現(xiàn)輪詢。我希望的代碼寫法是:

 

var user = new User({id:1});
user.pull();
user.watch();


實(shí)際上,Meteor已經(jīng)能實(shí)現(xiàn)絕大部分上述功能。但這不是軟文。我要強(qiáng)調(diào)兩點(diǎn)我不希望的:

  - 我不希望這個(gè)數(shù)據(jù)模型層去包含業(yè)務(wù)邏輯,也就是我創(chuàng)建的user對(duì)象,我不希望它提供 login、logout 等 api。
  - 我也不希望數(shù)據(jù)模型層自動(dòng)和任何ORM框架綁定,提供任何 SQL 或 NoSQL 的數(shù)據(jù)支持。

看到這兩點(diǎn)你可能心中大打問號(hào),這兩點(diǎn)不正是高效的精髓嗎?前后端邏輯復(fù)用,屏蔽數(shù)據(jù)庫細(xì)節(jié)。別急,讓我們重新用“理想的方式”來思考一下“邏輯”和“數(shù)據(jù)持久化”這兩件事。

數(shù)據(jù)與邏輯

我們以這樣一個(gè)問題開頭:任何一個(gè)應(yīng)用,我們的代碼最少能少到什么程度?

這算半個(gè)哲學(xué)問題。任何人想一想都會(huì)得到同一個(gè)答案:最少也就少到和應(yīng)用本身的描述一一對(duì)應(yīng)而已了。什么是應(yīng)用描述?或者說什么是應(yīng)用?我們會(huì)這樣描述一個(gè)博客:“用戶可以登錄、退出。用戶登錄后可以發(fā)表文章。發(fā)表文章時(shí)可以添加相應(yīng)的標(biāo)簽?!?

抽象一下描述,答案很簡單:數(shù)據(jù),和邏輯。

如果你在一個(gè)流程要求嚴(yán)格的公司,應(yīng)用描述就是prd或系分文檔。應(yīng)用的數(shù)據(jù)就是數(shù)據(jù)字典,應(yīng)用的邏輯就是流程圖的總和:

 

 

流程圖

 

那么代碼最少能怎么寫呢?數(shù)據(jù)很簡單,參照數(shù)據(jù)字典,我們來用一種即使是產(chǎn)品經(jīng)理都能掌握的偽碼來寫:

 

復(fù)制代碼
//描述字段
User : {
name : string
}

Post : {
title : string,
content : text
}

Tag : {
name : string
}

//描述關(guān)系
User -[created]-> Post
Post -[has]-> Tag
復(fù)制代碼

 

 

這里為了進(jìn)一步幫助讀者從已有的技術(shù)思維中跳出來,我想指出這段偽碼和數(shù)據(jù)庫字段描述有一個(gè)很大的區(qū)別,那就是:我不關(guān)心 User 和 Post 中間的關(guān)聯(lián)關(guān)系到底是在兩者的字段中都創(chuàng)建一個(gè)字段來保存對(duì)方的id,還是建立一個(gè)中間表。我只關(guān)心我描述它時(shí)的邏輯就夠了。數(shù)據(jù)描述的代碼,最簡也就簡單到這個(gè)程度了。

那么邏輯呢?我們先用按常規(guī)方式試試?

 

復(fù)制代碼
class User{
    createPost( content, tags=[] ){
        var post = new Post({content:content})    
        post.setTags( tags.map(tagName=>{ return new Tag(tagName)} ) )
        return post    
    }
}        
復(fù)制代碼

 

好像還不錯(cuò),如果今天產(chǎn)品經(jīng)理說我們?cè)黾右粋€(gè) @ 功能,如果文章里 @ 某個(gè)用戶,那么我們就發(fā)個(gè)站內(nèi)信給他。

復(fù)制代碼
class User{
    createPost( content, tags=[] ){
        var post = new Post({content:content})    
        post.setTags( tags.map(tagName=>{ return new Tag(tagName)} ) )

        if( at.scan(content) ){
            at.getUser(content).forEach( atUser =>{
                system.mail( atUser )
            })
        }

        return post    
    }
}
復(fù)制代碼

 

你應(yīng)該意識(shí)到我要說什么了,像互聯(lián)網(wǎng)這種可以快到一天一個(gè)迭代的開發(fā)速度,如果沒有一個(gè)好的模式,可能用不了多久,新加的功能就把你的 createPost 搞成了800行。當(dāng)然,我也并不是要講設(shè)計(jì)模式。代碼中的設(shè)計(jì)模式,完全依賴于程序員本人,我們要思考的是從框架層面提供最簡單的寫法。

讓我們?cè)倩氐秸軐W(xué)角度去分析一下業(yè)務(wù)邏輯。
我們所謂的邏輯,其實(shí)就是對(duì)一個(gè) 具體過程的描述 。在上面這個(gè)例子里,過程無非就是添加標(biāo)簽,全文掃描。描述一個(gè)過程,有兩個(gè)必備點(diǎn):

  - 干什么
  - 順序

順序?yàn)槭裁词潜貍涞??某天上面發(fā)了文件說標(biāo)題里帶 XXX 的文章都不能發(fā),于是你不得不在函數(shù)一開始時(shí)就進(jìn)行檢測,這時(shí)就必須指定順序。

如果我們用左右表示會(huì)互相影響的順序,從上下表示互不相干的順序,把上面的最初的流程圖重畫一下:


這是一棵樹。如果我們?cè)偌觽€(gè)功能,添加的標(biāo)簽如果是某個(gè)熱門標(biāo)簽,那么我們就把這篇文章放到網(wǎng)站的熱門推薦里。這棵樹會(huì)變成什么樣子呢:


是的,事實(shí)上人類思維中的任何過程,都可以畫成一棵樹。有條件的循環(huán)可以拆解成遞歸,最終也是一棵樹。但重點(diǎn)并不是樹本身,重點(diǎn)是上面這個(gè)例子演化的過程,從一開始最簡單的需求,到加上一點(diǎn)新功能,再到加上一些惡心的特殊情況,這恰恰就是真實(shí)世界中 web 開發(fā)的縮影。真實(shí)世界中的變化更加頻繁可怕。其中最可怕的是,很多時(shí)候我們的程序結(jié)構(gòu)、用到的設(shè)計(jì)模式,都是適用于當(dāng)前的業(yè)務(wù)模型的。而某天業(yè)務(wù)模型變化了,代碼質(zhì)量又不夠好的話,就可能遇到牽一發(fā)動(dòng)全身,大廈將傾的噩夢(mèng)。幾乎每個(gè)大公司都有一個(gè)“運(yùn)行時(shí)間長,維護(hù)的工程師換了一批又一批”的項(xiàng)目。Amazon曾經(jīng)有個(gè)工程師描述維護(hù)這種項(xiàng)目的感覺:“climb the shit mountain”。

回到之前的話題,在邏輯處理上,我們的理想是寫出的代碼即短,又具有極高的可維護(hù)性和可擴(kuò)展性。

更具體一點(diǎn),可維護(hù)性,就是代碼和代碼結(jié)構(gòu),能最大程度地反映業(yè)務(wù)邏輯。最好我的代碼結(jié)構(gòu)在某種程度上看來和我們流程圖中的樹一樣。這樣我讀代碼,就幾乎能理解業(yè)務(wù)邏輯。而可擴(kuò)展性,就是當(dāng)出現(xiàn)變化時(shí),我能在完成變化時(shí),能盡量少地去修改之前的代碼。同樣的,如果我們能保障代碼和代碼結(jié)構(gòu)能和流程圖盡量一致,那么在修改時(shí),圖上怎么改,我們代碼就怎么改。這也就是理論上能達(dá)到的最小修改度了。綜上,我們用什么樣的系統(tǒng)模型能把代碼變得像樹形結(jié)構(gòu)一樣?

很簡單,事件系統(tǒng)就可以做到。我們把都一個(gè)業(yè)務(wù)邏輯當(dāng)做事件來觸發(fā),而具體需要執(zhí)行的操作單做監(jiān)聽器,那么上面的代碼就可以寫成:

 

復(fù)制代碼
// emitter 是事件中心

emitter.on("post.create", function savePost(){...})

emitter.on("post.create", function createTags(){...}, {before:"savePost"})

emitter.on("post.create", function scanSensitiveWords( post ){

    if( system.scanSensitiveWords( post ) ){
        return new Error("you have sensitive words in post.")
    }

}, {block:all})

emitter.on("post.create", function scanPopTags(){...})
復(fù)制代碼

 

//執(zhí)行創(chuàng)建文章操作
emitter.fire("post.create", {...args})

 

這樣看來,每個(gè)操作的代碼變得職責(zé)單一,整體結(jié)構(gòu)也非常工整。值得注意的是,在這段偽碼里,我們用了 `{before:"savePost"}` 這樣的參數(shù)來表示操作的順序,看起來也和邏輯本身的描述一致。

讓我們回到可維護(hù)性和可擴(kuò)展性來檢查這種寫法。首先在可維護(hù)性上,代碼職責(zé)變得很清晰,并且與流程描述一致。不過也有一個(gè)問題,就是操作的執(zhí)行順序已經(jīng)無法給人宏觀上的印象,必須把每個(gè)監(jiān)聽器的順序參數(shù)拼起來,才能得到整體的順序。

在可擴(kuò)展性上,無路是新增還是刪除操作,對(duì)應(yīng)到代碼上都是刪除或新增相應(yīng)的一段,不會(huì)影響到其他操作代碼。我們甚至可以把這些代碼拆分到不同的文件中,當(dāng)做不同的模塊。這樣在增減功能時(shí),就能通過增刪文件來實(shí)現(xiàn),這也為實(shí)現(xiàn)一個(gè)文件級(jí)的模塊管理器提供了基礎(chǔ)技術(shù)。

至此,除了無法在執(zhí)行順序上有一個(gè)宏觀印象這個(gè)問題,似乎我們得到了理想的描述邏輯的方式。那我們現(xiàn)在來攻克這最后一個(gè)問題。拿目前的這段偽碼和之前的比較,不難發(fā)現(xiàn),之前代碼需要被執(zhí)行一遍才能較好地得到其中函數(shù)的執(zhí)行順序,才能拿到一個(gè)調(diào)用棧。而現(xiàn)在的這段代碼,我只要實(shí)現(xiàn)一個(gè)簡單的 emitter,將代碼執(zhí)行一遍,就已經(jīng)能得到所有的監(jiān)聽器信息了。這樣我就能通過簡單的工具來得到這個(gè)宏觀的執(zhí)行順序,甚至以圖形化的方式展現(xiàn)出來。得到的這張圖,不就是我們一模一樣的流程圖嗎?!

不知道你有沒有意識(shí)到,我們已經(jīng)打開了一扇之前不能打開的門!在之前的代碼中,我們是通過函數(shù)間的調(diào)用來組織邏輯的,這和我們現(xiàn)在的方式有一個(gè)很大的區(qū)別,那就是:用來封裝業(yè)務(wù)邏輯的函數(shù),和系統(tǒng)本身提供的其他函數(shù),沒有任何可以很好利用的區(qū)別,即使我們能得到函數(shù)的調(diào)用棧,這個(gè)調(diào)用棧用圖形化的方式打印出來也沒有意義,因?yàn)槠渲袝?huì)參雜太多的無用函數(shù)信息,特別是當(dāng)我們還用了一些第三方類庫時(shí)。打印的結(jié)果可能是這樣:



而現(xiàn)在,我們用來表述業(yè)務(wù)的某個(gè)邏輯,就是事件。而相應(yīng)的操作,就是監(jiān)聽器。監(jiān)聽器無論是觸發(fā)還是注冊(cè),都是通過 emitter 提供的函數(shù),那么我們只需要利用 emitter,就能打印出只有監(jiān)聽器的調(diào)用棧。而監(jiān)聽器的調(diào)用棧,就是我們的流程圖。

 

代碼結(jié)構(gòu)可圖形化,并且是有意義的可圖形化,這扇大門一旦打開,門后的財(cái)富是取之不盡的。我們從 開發(fā)、測試、監(jiān)控 三個(gè)方面來看我們能從中獲得什么。

在開發(fā)階段,我們可以通過調(diào)用棧生成圖,那通過圖來生成代碼還會(huì)難嗎?對(duì)于任何一份流程圖,我們都能輕易地直接生成代碼。然后填空就夠了。在調(diào)試時(shí)、我們可以制作工具實(shí)時(shí)地打印出調(diào)用棧,甚至可以將調(diào)用時(shí)保存的傳入傳出值拿出來直接查看。這樣一旦出現(xiàn)問題,你就可以直接根據(jù)當(dāng)前保存的調(diào)用棧信息排查問題,而再無需去重現(xiàn)它。同理,繁瑣的斷點(diǎn),四處打印的日志都可以告別了。

測試階段,既然能生成代碼,再自動(dòng)生成測試用例也非常容易。我們可以通過工具直接檢測調(diào)用棧是否正確,也可以更細(xì)致地給定輸入值,然后檢測各個(gè)監(jiān)聽器的傳入傳出值是否正確。

同樣很容想到監(jiān)控,我們可以默認(rèn)將調(diào)用棧的數(shù)據(jù)建構(gòu)作為日志保留,再用系統(tǒng)的工具去掃描、對(duì)邊,就能自動(dòng)實(shí)現(xiàn)對(duì)業(yè)務(wù)邏輯本身的監(jiān)控。

總結(jié)一下上述,用事件系統(tǒng)去描述邏輯、流程,使得我們代碼結(jié)構(gòu)和邏輯,能達(dá)到一個(gè)非常理想的對(duì)應(yīng)程度。這個(gè)對(duì)應(yīng)程度使得代碼里的調(diào)用棧信息就能表述邏輯。而這個(gè)調(diào)用棧所能產(chǎn)生的巨大價(jià)值,一方面在于可圖形化,另一方面則在于能實(shí)現(xiàn)測試、監(jiān)控等一系列工程領(lǐng)域的自動(dòng)化。

到這里,我們已經(jīng)得到了兩種理想的表達(dá)方式來分別表述數(shù)據(jù)和邏輯。下面真正激動(dòng)人心的時(shí)刻到了,我們來關(guān)注現(xiàn)實(shí)中的技術(shù),看是否真的能夠做出一個(gè)框架,讓我們能用一種革命性的方式來寫應(yīng)用?

理想到現(xiàn)實(shí)

首先來看數(shù)據(jù)描述語言和和數(shù)據(jù)持久化。你可能早已一眼看出 `User -[create]-> Post` 這樣的偽碼是來自圖數(shù)據(jù)庫 Neo4j 的查詢語言 cypher 。在這里我對(duì)不熟悉的讀者科普一下。Neo4j 是用 java 寫的開源圖數(shù)據(jù)庫。圖數(shù)據(jù)本身是以圖的方式去存儲(chǔ)數(shù)據(jù)。

例如同樣對(duì)于 User 這樣一個(gè)模型,在 關(guān)系型數(shù)據(jù)庫中就是一張表,每一行是一個(gè) user 的數(shù)據(jù)。在圖數(shù)據(jù)庫中就是一堆節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)是一個(gè) user。當(dāng)我們又有了 Post 這個(gè)模型時(shí),如果要表示用戶創(chuàng)建了 Post 這樣一個(gè)關(guān)系的話,在關(guān)系型數(shù)據(jù)庫里通常會(huì)建立一個(gè)中間表,存上相應(yīng) user 和 post 的 id。也或者直接在 user 或 post 表里增加一個(gè)字段,存上相應(yīng)的id。不同的方案適用于不同的場景。而 在圖數(shù)據(jù)庫中要表達(dá) user 和 post 的關(guān)系,就只有一種方式,那就是創(chuàng)建一個(gè) user 到 post 的名為 CREATED 的 關(guān)系。這個(gè)關(guān)系還可以有屬性,比如 {createdAt:2016,client:"web"} 等。

你可以看出圖數(shù)據(jù)和關(guān)系型數(shù)據(jù)庫在使用上最大的區(qū)別是,它讓你完全根據(jù)真實(shí)的邏輯去關(guān)聯(lián)兩個(gè)數(shù)據(jù)。而關(guān)系型數(shù)據(jù)庫則通常在使用時(shí)就已經(jīng)要根據(jù)使用場景、性能等因素做出不同的選擇。

我們?cè)倏床樵冋Z言,在 SQL 中,我們是以`SELECT ... FROM` 這樣一種命令式地方式告訴數(shù)據(jù)怎樣給我我要的數(shù)據(jù)。語句的內(nèi)容和存數(shù)據(jù)的表結(jié)構(gòu)是耦合的。例如我要找出某個(gè) user 創(chuàng)建的所有 post。表結(jié)構(gòu)設(shè)計(jì)得不同,那么查詢語句就不同。而在 Neo4js 的查詢語句 cypher 中,是以 `(User) -[CREATED] ->(Post)` 這樣的 模式匹配 的語句來進(jìn)行查詢的。這意味著,只要你能以人類語言描述自己想要的數(shù)據(jù),你就能自己翻譯成 cypher 進(jìn)行查詢。

除此之外,圖數(shù)據(jù)當(dāng)然還有很多高級(jí)特性。但對(duì)開發(fā)者來說,模式匹配式的查詢語句,才是真正革命性的技術(shù)。熟悉數(shù)據(jù)庫的讀者肯定有這樣的疑問:

其實(shí)很多 ORM 就能實(shí)現(xiàn) cypher 現(xiàn)在這樣的表達(dá)形式,但在很多大公司里,你會(huì)發(fā)現(xiàn)研發(fā)團(tuán)隊(duì)仍然堅(jiān)持手寫 SQL 語句,而堅(jiān)決不用 ORM。理由是,手寫 SQL 無論在排查問題還是優(yōu)化性能時(shí),都是最快速的。特別是對(duì)于大產(chǎn)品來說,一個(gè) SQL 就有可能節(jié)約或者損失巨額資產(chǎn)。所以寧愿用 “多人力、低效率” 去換 “性能和穩(wěn)定”,也不考慮 ORM。那么 cypher 如何面對(duì)這個(gè)問題?

確實(shí),cypher 可以在某種程度上理解成數(shù)據(jù)庫自帶的 ORM。它很難通過優(yōu)化查詢語句來提升性能,但可以通過其他方式。例如對(duì)耗時(shí)長的大查詢做數(shù)據(jù)緩存。或者把存儲(chǔ)分層,圖數(shù)據(jù)庫變成最底層,中間針對(duì)某些應(yīng)用場景來使用其他的數(shù)據(jù)庫做中間層。對(duì)有實(shí)力的團(tuán)隊(duì)來說,這個(gè)中間層甚至可以用類似于智能數(shù)據(jù)庫的方式來對(duì)線上查詢自動(dòng)分析,自動(dòng)實(shí)現(xiàn)中間層。事實(shí)上,這些中間技術(shù)早就已經(jīng)成熟,結(jié)合上圖數(shù)據(jù)庫和cypher,是可以把傳統(tǒng)的“人力密集型開發(fā)”轉(zhuǎn)變?yōu)椤凹夹g(shù)密集型開發(fā)”的。

扯得略遠(yuǎn)了,我們重新回到模式匹配型的查詢語句上,為什么說它是革命性的,因?yàn)樗鼊偤脻M足了我們之前對(duì)數(shù)據(jù)描述的需求。任何一個(gè)開發(fā)者,只要把數(shù)據(jù)字典做出來。關(guān)于數(shù)據(jù)的工作就已經(jīng)完成了。或者換個(gè)角度來說,在任何一個(gè)已有數(shù)據(jù)的系統(tǒng)中,只要我能在前端或者移動(dòng)端中描述我想要的數(shù)據(jù),就能開發(fā)出應(yīng)用,不再需要寫任何服務(wù)器端數(shù)據(jù)接口。Facebook 在 React Conf 上放出的前端 Relay 框架和 GraphQL 幾乎就已經(jīng)是這樣的實(shí)現(xiàn)。

再來看邏輯部分,無論在瀏覽器端還是服務(wù)器端,用什么語言,實(shí)現(xiàn)一個(gè)事件系統(tǒng)都再簡單不過。這里我們倒是可以進(jìn)一步探索,除了之前所說的圖形界面調(diào)試,測試、監(jiān)控自動(dòng)化,我們還能做什么?對(duì)前端來說,如果前后端事件系統(tǒng)可以直接打通,并且出錯(cuò)時(shí)通過圖形化的調(diào)試工具能無需回滾直接排查,那就最好了。
例如:在創(chuàng)建 post 的前端組件中

 

復(fù)制代碼
//觸發(fā)前端的 post.create 事件
var post = {title: "test", content: "test"}
emitter.fire("post.create").then(function(){
    alert("創(chuàng)建成功")
}).catch(function(){
    alert("創(chuàng)建失敗")
})
復(fù)制代碼

 

在處理邏輯的文件中:

復(fù)制代碼
//可以增加前端專屬的邏輯
emitter.on("post.create", function checkTest(post){
    if( post.title === "test"){
        console.log("this is a test blog.")
    }
})

//通過 server: 這樣的命名空間來觸發(fā)服務(wù)器端的事件
emitter.on("post.create", function communicateWithServer(post){
    console.log("communicating with server")
    return emitter.fire("server:post.create", post)
})
復(fù)制代碼

 

得到的事件棧

在瀏覽器端可以打通和服務(wù)器端的事件系統(tǒng),那么在服務(wù)器端呢?剛剛提到我們我們其實(shí)可以用任何自己熟悉的語言去實(shí)現(xiàn)事件系統(tǒng),那是不是也意味著,只要事件調(diào)用棧的數(shù)據(jù)格式一致,我們就可以做一個(gè)跨語言的架構(gòu)?

例如我們可以用nodejs的web框架作為服務(wù)器端入口,然后用python,用go去寫子系統(tǒng)。只要約定好系統(tǒng)間通信機(jī)制,以及事件調(diào)用棧的數(shù)據(jù)格式,那么就能實(shí)現(xiàn)跨語言的事件系統(tǒng)融合。這意味你未來看到的調(diào)用棧圖可能是:

跨語言的實(shí)現(xiàn),本身也是一筆巨大財(cái)富。例如當(dāng)我們未來想要找人一起協(xié)同完成某一個(gè)web應(yīng)用時(shí),再也不必局限于某一種語言的實(shí)現(xiàn)。甚至利用docker等容器技術(shù),執(zhí)行環(huán)境也不再是限制。再例如,當(dāng)系統(tǒng)負(fù)載增大,逐漸出現(xiàn)瓶頸時(shí)。我們可以輕松地使用更高效的語言或者執(zhí)行環(huán)境去替換掉某個(gè)業(yè)務(wù)邏輯的監(jiān)聽器實(shí)現(xiàn)。

更多的例子,舉再多也舉不完。當(dāng)你真正自己想清楚這套架構(gòu)之后,你會(huì)發(fā)現(xiàn)未來已經(jīng)在你眼前。

到這里,對(duì)“理想”的想象和對(duì)實(shí)現(xiàn)技術(shù)的思考終于可以劃上句號(hào)了。對(duì)熟悉架構(gòu)的人來說,其實(shí)已經(jīng)圓滿了。但我也不想放棄來“求干貨”的觀眾們。下面演示的,就是在框架原型下開發(fā)的簡單應(yīng)用。這是一個(gè)多人的todo應(yīng)用。

前端基于react,后端基于koa。

 

 

目錄結(jié)構(gòu)

 

 

前端數(shù)據(jù)(todo 列表) /public/data/todos.js

 

前端邏輯(todo 基本邏輯) /public/events/todo.js

 

 

前端邏輯(輸入@時(shí)展示用戶列表) /public/events/mention.js

 

 

后端邏輯(通知被@用戶) /modules/mention.js

 

 

通過調(diào)試工具得到的創(chuàng)建時(shí)的調(diào)用棧和輸入@符號(hào)時(shí)的調(diào)用棧

這只是一個(gè)引子,目的是為了讓你宏觀的感受將應(yīng)用拆解為“數(shù)據(jù)+邏輯”以后能有多簡單。目前這套框架已完成 50% ,實(shí)現(xiàn)了數(shù)據(jù)部分的設(shè)計(jì)、前后端事件融合,還有跨語言等方案正在開發(fā)中。未來將開源,期待讀者關(guān)注。


后記

終于寫完了??蚣苤皇羌軜?gòu)的實(shí)現(xiàn)。這套架構(gòu)幾乎孕育了近兩年,這其中已經(jīng)開發(fā)出一款實(shí)現(xiàn)了部分功能,基于nodejs的服務(wù)器端原型框架。完整的框架開發(fā)目前也已經(jīng)四個(gè)月了。雖然從它落地的這些前端技術(shù)、數(shù)據(jù)技術(shù)看起來,它其實(shí)是有技術(shù)基礎(chǔ)的,應(yīng)該是積累的產(chǎn)物。但實(shí)際上,最早的關(guān)于數(shù)據(jù)和邏輯的思路,卻是在我讀研時(shí)對(duì)一個(gè)“很虛”的問題的思考:什么樣的系統(tǒng)是最靈活的系統(tǒng)?在很長一段時(shí)間內(nèi),對(duì)各種架構(gòu)的學(xué)習(xí)中我都沒有找到想要的答案,直到后來在學(xué)認(rèn)知心理學(xué)和神經(jīng)學(xué)的時(shí)候,我想到了人。人是目前可以理解的最具備適應(yīng)性,最靈活的系統(tǒng)。人是怎么運(yùn)作的?生理基礎(chǔ)是什么?

認(rèn)知心理學(xué)里提到曾經(jīng)有一個(gè)學(xué)派認(rèn)為人的任何行為都不過是對(duì)某種刺激的反射,這種刺激可以是來自內(nèi)部也可以是外部。來自內(nèi)部的刺激有兩個(gè)重要來源,一是生理上,例如饑餓,疲憊。二則是記憶。例如,你每天起床要去工作,是因?yàn)槟愕倪^去的記憶告訴你你需要錢,或者你喜歡工作的內(nèi)容。這對(duì)人來說也是一種刺激,所以你產(chǎn)生了去工作的動(dòng)機(jī)。外部刺激就更簡單,例如生理上的被火燙了,心理上被嘲諷、被表揚(yáng)等等。而人的反應(yīng),就是對(duì)這些刺激而產(chǎn)生的多種反射的集合。例如早上起床,你的一部分反射是產(chǎn)生上班的動(dòng)機(jī),但是如果你生病了,你的身體和記憶就會(huì)刺激你去休息。最終你會(huì)在這兩種刺激下達(dá)到一個(gè)平衡,做出反應(yīng)。值得注意的是,大部分時(shí)候,人在不同時(shí)間面臨相同的刺激,卻做出不同的反應(yīng)。并不是因?yàn)楹髞砟承┓瓷浔粍h除了,而是因?yàn)楹髞硇纬闪烁鼜?qiáng)的反射區(qū)壓制住了之前的反射。它的生理基礎(chǔ)就是神經(jīng)學(xué)中的神經(jīng)遞質(zhì)可以互相壓制。

如果我們把要打造的系統(tǒng)看做一個(gè)有機(jī)體,把迭代看做生長,把用戶的使用看做不斷的刺激。那我們是不是就能模擬人的反射過程來打造系統(tǒng),從而期待系統(tǒng)得到像人一樣的適應(yīng)力?而恰恰你會(huì)發(fā)現(xiàn)科幻作品中的人工智能產(chǎn)品通常都以人的形態(tài)出現(xiàn)。因?yàn)槲覀兿M覀兯褂玫漠a(chǎn)品,就像人一樣通情達(dá)理,具有人一樣的領(lǐng)悟能力。而要達(dá)到這樣的效果,或許就是不斷給給他添加人對(duì)刺激的反射規(guī)則。

思考到這一步的時(shí)候,我對(duì)應(yīng)用架構(gòu)的設(shè)計(jì)哲學(xué)已經(jīng)基本定型。后來驗(yàn)證出來的,這樣的系統(tǒng)能夠極大地提高研發(fā)效率,都只是這段哲學(xué)的附加價(jià)值。其實(shí)提高研發(fā)效率的原理很簡單,無論系統(tǒng)的需求再怎么擴(kuò)展、再怎么變更,它也是遵循人本身的思維邏輯的。因此,你始終可以使用本身就模擬人類認(rèn)知的系統(tǒng)去適應(yīng)它。并且,它怎么變化,你就怎么變化。

架構(gòu)這種東西,最終仍然關(guān)注在使用者身上的。所以與其和我討論確定的技術(shù)問題,不如討論這些更有意義。對(duì)思考架構(gòu)的人來說,我認(rèn)為眼界和哲學(xué)高度,最重要。

 

 

討論記錄

尤小右:感覺其實(shí)就是 flux 啊,但是 string-based global event bus 規(guī)模大了還是會(huì)有點(diǎn)坑爹的。一個(gè)事件觸發(fā)的后果遍及全棧,不好 track。

答:和flux的區(qū)別在于flux的數(shù)據(jù)對(duì)象本身和對(duì)數(shù)據(jù)的操作是合在store里的。事件系統(tǒng)規(guī)模的問題通過兩個(gè)方式控制:一是命名空間。二是事件只應(yīng)用在業(yè)務(wù)邏輯個(gè)程度就夠了,像“存入數(shù)據(jù)庫”這種操作就不要再用事件觸發(fā)。這樣系統(tǒng)就不會(huì)亂掉,因?yàn)樗环从硺I(yè)務(wù)邏輯。

 

玉伯也叫黑俠:認(rèn)識(shí)心理學(xué)那段很有趣。很關(guān)注如何讓業(yè)務(wù)代碼隨著時(shí)間流逝不會(huì)腐化而會(huì)趨良?比如事件fire點(diǎn),怎么才能可控又夠用,而不會(huì)隨著業(yè)務(wù)復(fù)雜而爆發(fā)式增長?(簡單如seajs, 隨著插件的多樣化事件點(diǎn)都經(jīng)常不夠用)。還有如何讓事件間彼此解耦?經(jīng)常一個(gè)需求要添加多個(gè)監(jiān)聽,做得不好還可能影響其他功能點(diǎn)。

答:用事件去反映業(yè)務(wù)邏輯,而不是技術(shù)實(shí)現(xiàn)的邏輯”不只是這套架構(gòu)對(duì)于防止事件濫用的一個(gè)建議,更是它的哲學(xué)理論的重要部分。遵守它,這套框架就能把高可擴(kuò)展性和高可維護(hù)性發(fā)揮到極致。我們用一個(gè)常見的例子來說明這一點(diǎn)。有時(shí)候面臨需求變更,我們會(huì)覺得難搞,會(huì)對(duì)產(chǎn)品經(jīng)理說:“你這個(gè)變更影響很大,因?yàn)槲业拇a中xxx不是這樣設(shè)計(jì)的”。而產(chǎn)品經(jīng)理有可能不理解,因?yàn)閷?duì)他來說,變更的需求可能只是一個(gè)很簡單的邏輯,加上一點(diǎn)特殊情況而已。產(chǎn)生這種矛盾的關(guān)鍵就在于,沒有找到一種能準(zhǔn)確描述業(yè)務(wù)邏輯的方式去組織代碼。如果組織代碼的方式和描述業(yè)務(wù)邏輯的方式一致,那么業(yè)務(wù)邏輯上覺得改動(dòng)點(diǎn)很簡單,代碼上就也會(huì)很簡單。這套架構(gòu)中的事件系統(tǒng)、包括事件擁有的順序控制等特性,都是為了提供一種盡可能合適的方式去描述業(yè)務(wù)邏輯。只有這樣,才能實(shí)現(xiàn)代碼最少、最可讀、最可擴(kuò)展。它本身是為描述業(yè)務(wù)邏輯而不是技術(shù)實(shí)現(xiàn)邏輯而生。所以只有遵守這個(gè)規(guī)則,才能得到它帶來的財(cái)富。

 

玉伯也叫黑俠:嗯,看明白了。感覺是將代碼階段的復(fù)雜性,前移到了業(yè)務(wù)系分階段,如果系分階段做得好,那么代碼就會(huì)很優(yōu)雅。反之,則很難說。進(jìn)一步提一個(gè)無恥要求:怎么保證系分階段的良好性呢?不少時(shí)候,寫代碼的過程,就是梳理業(yè)務(wù)邏輯的過程,寫完后,才明白某個(gè)需求真正該怎么實(shí)現(xiàn)。

答:不太認(rèn)同寫代碼的過程是梳理業(yè)務(wù)邏輯的過程??梢哉f寫代碼的過程是梳理具體技術(shù)實(shí)現(xiàn)的過程。如果一開始寫代碼的人連業(yè)務(wù)邏輯都不清楚,再好的技術(shù)和框架也無法防止他寫出爛代碼?;谑录募軜?gòu)其實(shí)不是對(duì)系分的要求提高了,反而是降低了。因?yàn)橹灰竽憷砬宄壿嫞唧w的實(shí)現(xiàn)寫得再爛,之后都可以依賴事件系統(tǒng)架構(gòu)本身的靈活性去完善的。就例如“發(fā)表文章后給所有被@的人發(fā)站內(nèi)信”這樣的邏輯,你可能一開始沒有考慮發(fā)站內(nèi)信的時(shí)候最好用個(gè)隊(duì)列,防止請(qǐng)求被卡住。但只要你做到了最基礎(chǔ)的把“發(fā)送站內(nèi)”這個(gè)監(jiān)聽器注冊(cè)到“發(fā)表文章”的事件上。未來就能在不影響任何其他代碼的情況下去優(yōu)化。實(shí)際上沒有任何框架能幫你寫好代碼,即使DDD社區(qū)也是強(qiáng)調(diào)不斷重構(gòu),只可能“降低讓你寫好代碼的門檻”。這套架構(gòu)就是屏蔽很多技術(shù)上的概念,用事件的方式讓你只關(guān)注邏輯。

 

玉伯也叫黑俠:有沒有一種讓代碼趨良的架構(gòu)?可能剛開始寫得亂糟糟,但隨著做的需求越多,寫的代碼越多,整體可維護(hù)性反而會(huì)變得越好?比如前后端分層,讓后端專注業(yè)務(wù)模型,一般來說,業(yè)務(wù)模型會(huì)逐步趨于完善和穩(wěn)定,前端代碼也會(huì)逐步變好。用一些約束,推動(dòng)代碼的良性循環(huán)。這些約束,是否就是理想應(yīng)用架構(gòu)的精髓?這些約束是什么?可能是某種要求比如測試覆蓋率,也可能是某種強(qiáng)制約束比如必須通過數(shù)據(jù)改動(dòng)來更新界面。roof的約束是用事件去反映業(yè)務(wù)邏輯,但這個(gè)約束更多是「道德」層面,而不是「法律」,比如如何防止「大事件」(一個(gè)事件里,一坨技術(shù)實(shí)現(xiàn)的邏輯代碼)?如何讓人羞于去寫出糟糕的代碼?

答:即使前后端分離,業(yè)務(wù)模型趨于穩(wěn)定,也是靠開發(fā)者自身不斷重構(gòu)去實(shí)現(xiàn)的,要不然怎么會(huì)“趨于”穩(wěn)定呢。架構(gòu)只可能讓人站到更好地平臺(tái)上,用更好地方式去寫好代碼,不可能主動(dòng)幫人把代碼變好。文中架構(gòu)就是通過屏蔽技術(shù)細(xì)節(jié),讓你關(guān)注業(yè)務(wù)邏輯的方式,讓代碼易理解,也讓你能不影響業(yè)務(wù)地去升級(jí)技術(shù)。這套架構(gòu)因?yàn)橛幸粋€(gè)清晰的事件調(diào)用棧數(shù)據(jù)結(jié)構(gòu),所以能很容易地做出相應(yīng)的測試、監(jiān)控工具保障代碼質(zhì)量。但要實(shí)現(xiàn)“法律”是不可能的。即使是Java、即使是領(lǐng)域驅(qū)動(dòng)編程,也可以在它好的架構(gòu)下寫出各種糟糕的代碼。畢竟編程仍然是一件需要?jiǎng)?chuàng)造力的工作。這就像硬幣的兩面,如果要實(shí)現(xiàn)法律,那工作本身必須是無需創(chuàng)造,完全可以按照流程由機(jī)器人生產(chǎn)。如果要?jiǎng)?chuàng)造力,就必然會(huì)有因人而異的品質(zhì)差異。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多