RePractise終于又迎來(lái)了新的一篇,要知道上一篇可是在半年前呢——《Repractise前端篇: 前端演進(jìn)史
》。照RePractise慣例,這又是一篇超長(zhǎng)文以及個(gè)人的扯淡過(guò)程。
當(dāng)然這也是一個(gè)神奇的標(biāo)題,因?yàn)槲乙呀?jīng)想不到一個(gè)好的名字了,不過(guò)先這樣吧。這篇文章算是我最近兩三個(gè)月的一篇思考。在上一個(gè)項(xiàng)目的打雜生涯里,我開(kāi)始去學(xué)習(xí)架構(gòu)方面的知識(shí),開(kāi)始去接觸DDD的思想。從編碼到架構(gòu),再回到實(shí)際的編碼中,總會(huì)有很多的靈感閃現(xiàn)。
從真實(shí)世界到前后端
我們所寫(xiě)的代碼在某種程度上都反應(yīng)了真實(shí)世界的模型、行為等等。一個(gè)比較常見(jiàn)的模型就是:購(gòu)物模型。同時(shí), 這也是一個(gè)很好的展示前后端分離的模型。

(PS: 原諒我的畫(huà)工)
便利店與售貨員
對(duì)于一般的便利店來(lái)說(shuō),只有一個(gè)銷(xiāo)售員,ta負(fù)責(zé)整個(gè)商店的一系列事務(wù)。從某種意義上來(lái)說(shuō),ta就是整個(gè)系統(tǒng)的核心,負(fù)責(zé)了系統(tǒng)的業(yè)務(wù)和事件。
一般來(lái)說(shuō)在一個(gè)購(gòu)買(mǎi)流程里,會(huì)有三個(gè)主要的人或物:
- 售貨員。一般來(lái)說(shuō),ta只會(huì)在最后的結(jié)賬流程中出以及顧客詢(xún)問(wèn)時(shí)做出響應(yīng)。
- 貨物。沒(méi)啥可解釋的,就是一堆模型。0
- 顧客 。瀏覽商店、對(duì)比商店、blabla等等。
如果我們要構(gòu)建這樣一個(gè)系統(tǒng),我們只需要區(qū)分出系統(tǒng)的各個(gè)部分,那么剩下的事情就變得很簡(jiǎn)單了。

由于整個(gè)系統(tǒng)仍然是相當(dāng)復(fù)雜的,我們?cè)谶@里只關(guān)注于用戶(hù)購(gòu)買(mǎi)的過(guò)程。
模型、領(lǐng)域、抽象
從購(gòu)買(mǎi)過(guò)程來(lái)說(shuō),顧客所要做的事情就是:
- 瀏覽、對(duì)比商品
- 加到購(gòu)物車(chē)
- 結(jié)賬、付錢(qián)
對(duì)應(yīng)的也就是有模型、領(lǐng)域和抽象幾個(gè)概念。
模型
這些商品實(shí)現(xiàn)上就是相當(dāng)于一系列的模型及數(shù)據(jù)。在用戶(hù)購(gòu)買(mǎi)之前,我們只需要一個(gè)去獲取一個(gè)個(gè)的數(shù)據(jù)接口,并展示這些數(shù)據(jù)。
對(duì)應(yīng)于這些商品要建起Schema來(lái)是一件容易的事。作為一個(gè)商品,他們都擁有著一些共同的元素:price, name, description, location, manufacturer等等的信息。其中一些屬性,還會(huì)有復(fù)雜的對(duì)應(yīng)關(guān)系:

這些需要在我們建立數(shù)據(jù)庫(kù)的時(shí)候,盡可能地明確好這些關(guān)系。由于業(yè)務(wù)本身是難以預(yù)料的,你可能和我們之前的項(xiàng)目一樣需要一個(gè)addtionInfo的字段,來(lái)用JSON存儲(chǔ)一些額外的字段。當(dāng)然如果你使用的是NoSQL,那就再好不過(guò)了。
最好你還使用了讀寫(xiě)分離架構(gòu),一種比較常見(jiàn)的用法就是CMS網(wǎng)站,人們使用數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)內(nèi)容,使用靜態(tài)頁(yè)面來(lái)展示這些內(nèi)容。比較好的實(shí)踐還有CQRS(Command Query Responsibility Segregation, 命令查詢(xún)職責(zé)分離模式),用于CRUD(增、刪、改,當(dāng)然也可以查)的Command,以及Query的查詢(xún)分開(kāi)。簡(jiǎn)單的來(lái)說(shuō),就是有兩個(gè)不同的數(shù)據(jù)持久化中心:

這一點(diǎn)特別適合于那些查詢(xún)、搜索為主的網(wǎng)站,如淘寶。哈哈,我們離題有點(diǎn)遠(yuǎn)了,總之我們就是在這里提供了數(shù)據(jù)庫(kù)的靈氣,并對(duì)一些字段做一些簡(jiǎn)單的處理。聽(tīng)上去感覺(jué)GraphQL更適合做這樣的事。
領(lǐng)域
而顧客及售貨員在整個(gè)過(guò)程中做的事情就是領(lǐng)域(Domain,《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》)——即一個(gè)組織所做的事情以及其中所包含的一切。對(duì)于顧客和售貨員來(lái)說(shuō),他們?cè)诟髯缘念I(lǐng)域里做著不同的事。
對(duì)于顧客來(lái)說(shuō),其整個(gè)瀏覽過(guò)程,基本上都是在前端完成:
- 搜索、查找商品 -> 獲得商品列表
- 查找商品詳細(xì)
- 切換到下一個(gè)商品
這個(gè)場(chǎng)景下就特別適合于上面說(shuō)到的讀寫(xiě)分離架構(gòu)。在瀏覽過(guò)程中,對(duì)用戶(hù)的數(shù)據(jù)進(jìn)行監(jiān)控,以用于了解用戶(hù)的行為,改善用戶(hù)體驗(yàn)。這也是很常見(jiàn)的功能,或者說(shuō)他們是無(wú)處不在的模式:
- 結(jié)果頁(yè) / 列表頁(yè)
- 詳情頁(yè)
隨后的用戶(hù)收藏、添加到購(gòu)物車(chē)、購(gòu)買(mǎi)、交付等流程都需要與后臺(tái)進(jìn)行更緊密的交付。而這些都和售貨員都有緊密的關(guān)系,而這些就不是一種簡(jiǎn)單的事。
從用戶(hù)購(gòu)買(mǎi)完成以后,剩下的就是一堆瑣碎的事了,而這些都是由后端來(lái)完成的:
- 訂單子系統(tǒng)
- 物流系統(tǒng)
- 發(fā)票系統(tǒng)
- 支付系統(tǒng)
等等。
對(duì)于用戶(hù)來(lái)說(shuō),一種最簡(jiǎn)單的情況就是亞馬遜,你只需要按一下“一鍵下單”即可。不需要關(guān)心后面的操作了,同樣的這也適合于我們的業(yè)務(wù)場(chǎng)景。
抽象
抽象本來(lái)不打算寫(xiě)的,但是后來(lái)想了想還是寫(xiě)??偟膩?lái)說(shuō)整個(gè)過(guò)程還是需要相對(duì)比較好的抽象能力,要不我就很難講述清楚這里面的過(guò)程了。
抽象是很神奇的東西,也可以分為幾個(gè)不同的境界——但是我也不知道有幾個(gè)境界,簡(jiǎn)單的來(lái)說(shuō)就是不同的人看上去就有不同的東西。如有的人看到下面的畫(huà)就是一坨shit——還不如小學(xué)生畫(huà)的呢,有的人就會(huì)驚呼大師。

反正,我也很看不懂。這一點(diǎn)倒類(lèi)似于最初我對(duì)設(shè)計(jì)模型的理解一樣:
- 一開(kāi)始不以為然
- 然后發(fā)現(xiàn)很棒
- 接著使用過(guò)度
- 最后就和最好的編程器Emacs一樣

這些都在隨著編程生涯的展開(kāi)而發(fā)生一些變化,我們不斷地抽象出一些概念,以至于到了最后剛進(jìn)入這個(gè)行業(yè)的人都看不懂。但是,這些都是一點(diǎn)點(diǎn)在一層層抽象的基礎(chǔ)上產(chǎn)生的。

所以,我就把這一小小節(jié)扯完了,反正我是不想說(shuō)得太抽象了。接著,讓我們?cè)俪饵c(diǎn)技術(shù)性的話(huà)題。
前后臺(tái)分離:后臺(tái)
典型的Web應(yīng)用框架就是類(lèi)似于這樣的架構(gòu):

又或者是MVC架構(gòu),但是這已經(jīng)不重要了。我們都前后端分離了,是時(shí)候把V層去掉了。

我們繼續(xù)以上面的瀏覽來(lái)購(gòu)買(mǎi)流程來(lái)做扯淡,后臺(tái)除了提高上面的商品信息以外,在購(gòu)買(mǎi)的時(shí)候還需要對(duì)用戶(hù)進(jìn)行授權(quán)。當(dāng)然注冊(cè)就是另外一個(gè)話(huà)題了,另外一個(gè)很大的話(huà)題。
所有的這些我們都可以向前臺(tái)提供對(duì)應(yīng)的API即可。理想的情況下,我們對(duì)應(yīng)于不同的模塊可以有不同的服務(wù):

但是現(xiàn)實(shí)并不總是這么美好的,而在我們當(dāng)前情況下則可以——畢竟所有的用戶(hù)都應(yīng)該能瀏覽所有的商品,這時(shí)就不需要做特殊的處理了。
在這個(gè)過(guò)程中,我們還有一系列的操作需要在后臺(tái)來(lái)完成。不過(guò),這些都可以在內(nèi)部中完成的。而復(fù)雜的過(guò)程,實(shí)際上還存在于前端的邏輯當(dāng)中。
前后臺(tái)分離:前端
開(kāi)始時(shí),我們需要這樣做去獲取一個(gè)個(gè)的商品詳情。這些數(shù)據(jù)也可以在返回頁(yè)面模板的時(shí)候,直接將API數(shù)據(jù)填充到HTML中——帶后臺(tái)渲染的React都是這樣做的。然后在用戶(hù)瀏覽的過(guò)程中,我們還需要遵循這樣的數(shù)據(jù)流程:
- 獲取數(shù)據(jù)。無(wú)論是Ajax,還是新的Fetch API都可以做這樣的事。
- 處理數(shù)據(jù)。依據(jù)于業(yè)務(wù)的需要對(duì)數(shù)據(jù)進(jìn)行一些特殊的處理,如修改時(shí)間、價(jià)格的顯示格式,描述的長(zhǎng)度等等。
- 顯示數(shù)據(jù)。只需要一個(gè)簡(jiǎn)單的模板引擎,過(guò)去的JSP、Mustache、今天的React都在做同樣的事。
而在進(jìn)入這個(gè)頁(yè)面之前,我們還需要關(guān)注幾個(gè)基本的元素,雖然這些都是將由框架來(lái)解決的。
- 路由。無(wú)論你遵不遵循REST的實(shí)踐,我們只需要給對(duì)應(yīng)的頁(yè)面一個(gè)URL即可。相應(yīng)的如果我們需要SEO,那么我們也需要在后臺(tái)有一個(gè)對(duì)應(yīng)的URL。理想的情況下,他們應(yīng)該是由一個(gè)URL。
- 模塊化。從過(guò)去Require.js的火熱,到今天的各式各樣的框架內(nèi)建的模塊化框架,他們解決都是一個(gè)問(wèn)題:代碼度的問(wèn)題。這一點(diǎn)和后臺(tái)采用的微服務(wù)架構(gòu)的緣由好像是一樣。
- 控制器。我也想不起來(lái)為什么控制器非在這里不可,但是我想只有這樣,我才能快速地找到這個(gè)文件。
作為前端,我們不僅僅要負(fù)責(zé)的頁(yè)面的美觀(guān),還要對(duì)用戶(hù)的事件做出響應(yīng),并且做好用戶(hù)體驗(yàn)。
- 事件
這就是最近火熱的關(guān)于DOM的討論的原因,當(dāng)顧客想了解一下一些不一樣?xùn)|西的時(shí)候,我們就需要對(duì)DOM進(jìn)行操作。
我突然就有一個(gè)問(wèn)題了,你真的有那么多DOM需要操作么?你又不是Facebook,有那么多的Timeline和事件流。還有你的用戶(hù)體驗(yàn)是不是做得足夠好了?順便再補(bǔ)一刀,如果你使用了React,并且沒(méi)有進(jìn)行前后端分離,那就等下一個(gè)惡夢(mèng)。
最后,當(dāng)用戶(hù)買(mǎi)下東西的時(shí)候,我們也需要這樣的交互流程。
RePractise
因?yàn)樽罱覍?duì)DDD又有了一些想法,還在想著如何直接由真實(shí)世界來(lái)建模。順便整理了這些思路到一起,但是好似這樣的設(shè)計(jì)更簡(jiǎn)單。