復(fù)雜表單應(yīng)用解耦 淘寶機(jī)票訂單實(shí)踐
來源:網(wǎng)站推廣 2012-11-14
背景
在web應(yīng)用中,復(fù)雜表單這類web應(yīng)用富交互元素多,業(yè)務(wù)邏輯復(fù)雜,犬牙交錯(cuò),且需求變化頻繁。及容易成為晦澀和幽暗之地,也經(jīng)常是各種代碼壞味道的來源。針對這種典型的復(fù)雜應(yīng)用,本文以淘寶機(jī)票訂單為例提出一種架構(gòu)模式梳理和消化表單帶來的復(fù)雜性。
模塊和組件劃分
解決復(fù)雜表單的的第一步,劃分模塊。
概念上,為了復(fù)用和解耦方便,應(yīng)將模塊按照功能的內(nèi)聚程度進(jìn)行劃分。強(qiáng)相關(guān),頻繁溝通和交互的功能應(yīng)該歸為一個(gè)模塊。模塊間盡量不存在依賴關(guān)系。也就是常說的“高內(nèi)聚,低耦合”。
如下圖所示,淘寶機(jī)票訂單頁面主要有被分為7個(gè)主要模塊。
模塊劃分完畢,下一步確認(rèn)組成模塊的組件。
關(guān)于模塊和組件的區(qū)分。一般按照以下三個(gè)緯度考量。
是否有業(yè)務(wù)邏輯參與。
是否包含html。
是否具備一定獨(dú)立性。
“模塊”,定義為一個(gè)包含”html”、”css(圖片被認(rèn)為是css的一部分)“、”javascript”的代碼集。模塊的應(yīng)用方式多為通過web模板技術(shù)(如:velocity、freemarker、php)。因?yàn)榘薶tml,使得模塊必須通過服務(wù)端合并加載并且最終推送到用戶瀏覽器。此外,“模塊”還是具備一定獨(dú)立業(yè)務(wù)和交互的集合,最好可以被其他頁面引用。良好的獨(dú)立性也可以幫助協(xié)同開發(fā),在實(shí)際開發(fā)中可多人可以并行開發(fā)多個(gè)獨(dú)立模塊,提高效率。
“組件”,定義為一個(gè)僅包含”css”和”javascript”的代碼集。正因?yàn)椴话琱tml,所以組件可通過javascript異步加載。因?yàn)檫@種可異步加載的特性,組件在復(fù)用方面的容易性遠(yuǎn)超模塊。組件沒有業(yè)務(wù)邏輯或者僅有少部分公共業(yè)務(wù)邏輯。業(yè)務(wù)邏輯越多,組件的可復(fù)用性就越低。
模塊、組件間通訊
組件/模塊劃分的目的是將彼此間相對獨(dú)立的功能分離,前面通過模塊和組件的劃分解決了分離問題。實(shí)際中,模塊之間存在協(xié)作關(guān)系。模塊間應(yīng)以一種輕量的方式協(xié)作。一般的為了更好的分離和解耦,可以考慮用廣播的方式在模塊間溝通,考慮使用事件的方式在組件間通訊。
如下圖所示,淘寶機(jī)票訂單頁面的數(shù)據(jù)流向。
不同模塊在后期均有可能擴(kuò)展小功能。例如不定期的活動(dòng)優(yōu)惠等。事件廣播可以讓不同模塊/組件間新增功能影響面縮小。在淘寶機(jī)票訂單中應(yīng)用中,使用廣播組件通訊主要用來完成以下意圖。
1、知會。
知會的特性在于異步通訊。廣播發(fā)起方只需要放出事件,無需等待其他關(guān)注者完成處理。稱為異步廣播。例如表單模塊的內(nèi)容變更需要知會到顯示訂單金額的模塊,顯示訂單金額的模塊接受事件后需要更改金額。
基于這種方式的通訊,各模塊之需要做好自己的事情,外部關(guān)注的時(shí)間廣播出去即可。異步廣播還有一個(gè)好處是系統(tǒng)堅(jiān)固性比較強(qiáng),廣播發(fā)送者不會因?yàn)闀r(shí)間監(jiān)聽者的使用不當(dāng)而異常。
2、請求數(shù)據(jù)
例如,模塊6(負(fù)責(zé)提交)需要在被點(diǎn)擊后從模塊2(乘機(jī)人表單),模塊4(聯(lián)系地址)、模塊7(金額計(jì)算)。獲取具體數(shù)據(jù)提交。請求數(shù)據(jù)的場景特性在于,廣播發(fā)起者需要等待時(shí)間處理者完成處理后再繼續(xù)下一步行為。稱為同步廣播。同步廣播的應(yīng)用有些費(fèi)解。代碼說明原理和應(yīng)用。
基于此機(jī)制。提交模塊只需要負(fù)責(zé)綜合校驗(yàn),浮層,網(wǎng)絡(luò)請求及異常處理。而具體請求的內(nèi)容由其他模塊決定。對后續(xù)模塊的擴(kuò)充起到了很好的左右。
復(fù)雜組件拆分
模塊和組件劃分完畢后,可能會發(fā)現(xiàn)某些組件非常復(fù)雜,幾乎占據(jù)了整個(gè)web應(yīng)用一半以上的代碼。這部分組件由純js實(shí)現(xiàn),并且使用javascript模塊加載器加載。
同一個(gè)組件大量代碼糾結(jié)在一起,最終還是會導(dǎo)致架構(gòu)腐化。因此,復(fù)雜組件需要進(jìn)一步拆分。在淘寶機(jī)票訂單中,乘機(jī)人信息組件是一個(gè)復(fù)雜組件。如下圖所示:
拆分這類輸入型的復(fù)雜組件,一般來說有兩種思路方式。
縱切,組件樹型式。
將組件進(jìn)一步劃分為更細(xì)力度的輸入組件,將每個(gè)輸入域作為一個(gè)單獨(dú)組件。最終形成一個(gè)組件樹。
這樣的組織方式結(jié)構(gòu)嚴(yán)謹(jǐn)層級清晰,最大的優(yōu)點(diǎn)是很容易支持字段擴(kuò)展。
但考慮如下場景,為了盡量友好的提示用戶,需要在輸入域外的某處增加提示幫助。
這種場景下組件樹的組織方式每次在面對變化時(shí)就會略顯手忙腳亂。難道把每個(gè)地方出現(xiàn)的tip都座位獨(dú)立組件看待嗎?
字段級的適普性降低了適應(yīng)細(xì)節(jié)調(diào)整的能力,付出的代價(jià)在于界面體驗(yàn)。
橫切,AOP式。
將所有輸入域抽象的看待為同一個(gè)組件。按照組件的富應(yīng)用特性分層看待。在本例中,乘機(jī)人組件被按照從簡單到復(fù)雜分為3個(gè)切面。
切面1-基礎(chǔ)展現(xiàn)層只負(fù)責(zé)最基礎(chǔ)的可完成輸入的表單控件,及基礎(chǔ)dom管理。
切面2-富展現(xiàn)層負(fù)責(zé)修飾base層的基礎(chǔ)html控件,形成富輸入控件。
切面3-校驗(yàn)層負(fù)責(zé)對base層的輸入數(shù)據(jù)進(jìn)行業(yè)務(wù)級校驗(yàn)。
未來,如果新增tip或者其他業(yè)務(wù)邏輯,增加一個(gè)新切面即可,完全或者很少需要修改老代碼文件。
淘寶機(jī)票訂單采用了AOP這種方式,從最終代碼量上來看,可以看出復(fù)雜度被比較均衡的分布到不同文件中去。
同樣,這種方式也有局限,如果需要擴(kuò)展字段,那將是一個(gè)災(zāi)難,你有可能需要到每一個(gè)切面里面去做修改。
有句老話說的好,沒有最優(yōu)方案,只有最適合的解決方案,任何解決方案,都需要放到具體場景中去評判。事實(shí)上,對這個(gè)問題的進(jìn)一步研究,可以發(fā)現(xiàn)以下規(guī)律。
對于一個(gè)組件、模塊,同時(shí)追求簡單設(shè)計(jì)、適普性(字段級擴(kuò)充)、界面體驗(yàn)是不可能的。如果場景需要適應(yīng)字段靈活擴(kuò)展,那就采用縱切的模式。如果使用場景需字段確定,需要更多細(xì)節(jié)控制力度,那就橫切,AOP式。如果兩者都要兼顧,就需要引入復(fù)雜設(shè)計(jì),綜合運(yùn)用橫切和縱切。但是這樣形成的最終設(shè)計(jì)會很復(fù)雜,開發(fā)和可維護(hù)性上會有代價(jià)付出。
對于淘寶機(jī)票這類互聯(lián)網(wǎng)應(yīng)用,使用了橫切的方式來拆分組件,因?yàn)樵谶@個(gè)場景中,字段的數(shù)量是相對固定的,而圍繞固定數(shù)量字段的優(yōu)化需求是層出不窮的。然而在企業(yè)內(nèi)網(wǎng)應(yīng)用或者網(wǎng)站后臺web應(yīng)用中,字段的變化會比較頻繁。建議主要采用縱切的思路劃分。
表單校驗(yàn)
有表單的地方就有校驗(yàn)。項(xiàng)目初期,校驗(yàn)的功能總是不起眼。等待項(xiàng)目后期時(shí)候經(jīng)常會發(fā)現(xiàn)校驗(yàn)已經(jīng)占據(jù)了巨大工作量并且成為海量bug的源頭。因此校驗(yàn)是一種典型的容易被輕視單又蘊(yùn)含巨大工作量的事情,需要特別對待,專門設(shè)計(jì)。
一般來說,這根據(jù)校驗(yàn)根據(jù)其復(fù)雜度可以分為以下兩類:
格式校驗(yàn)
格式校驗(yàn)一般是校驗(yàn)用戶輸入的格式是否滿足要求,比如是否數(shù)字、電話號碼、郵箱等等。此類校驗(yàn)的特點(diǎn)是校驗(yàn)域單一,一般只對一個(gè)input或者某個(gè)組件的value進(jìn)行檢查。格式類校驗(yàn)應(yīng)與與用戶展現(xiàn)非常接近,一種非常好的做法是將此類校驗(yàn)信息直接描述在html標(biāo)簽屬性中。html5中input的pattern屬性就是一種基于這種思想的解決方案。
邏輯校驗(yàn)
邏輯校驗(yàn)是滿足格式校驗(yàn)后,繼續(xù)進(jìn)行的與業(yè)務(wù)相關(guān)的校驗(yàn),例如是否存在相同用戶名,輸入的生日是否和身份證號不符等等。此類校驗(yàn)的一般涉及多個(gè)輸入域,要綜合處用戶的輸入內(nèi)容一起校驗(yàn)。此類校驗(yàn)邏輯復(fù)雜,不適合寫在html中。
目前有很多流行的form校驗(yàn)框架解決校驗(yàn)問題,如何引入合適的校驗(yàn)框架,先從理解校驗(yàn)這件事的過程開始。
典型的一個(gè)校驗(yàn)過程如下,用戶在某個(gè)input處完成輸入,應(yīng)用在某個(gè)時(shí)刻被觸發(fā)校驗(yàn),可以是失去焦點(diǎn)或者keyup或者其他。被觸發(fā)的校驗(yàn)過程找到此處input所需要的校驗(yàn)規(guī)則(有時(shí)候這個(gè)規(guī)則被直接寫在html中)判單正確與否,如果正確,可能有提示,如果錯(cuò)誤,可能也有提示。
從以上場景的描述中,可以找到校驗(yàn)的幾個(gè)關(guān)鍵環(huán)節(jié)。這里局部采用一下管理學(xué)上經(jīng)典的5w1h問題分析方法來分析問題
who: 哪個(gè)輸入控件的內(nèi)容需要校驗(yàn)。這是框架是解決不了的。要對哪個(gè)輸入域做校驗(yàn)應(yīng)該是應(yīng)用傳遞進(jìn)入的。
when: 何時(shí)被觸發(fā)校驗(yàn)。比如說是“who”失去焦點(diǎn)時(shí)。變化太多,框架解決不了。只能被動(dòng)觸發(fā)。
what: 做什么校驗(yàn)。有時(shí)候這個(gè)”what”被寫在html中;旧,所有格式校驗(yàn)都是固定的,這個(gè)問題應(yīng)框架解決。但框架應(yīng)預(yù)留接口做更加復(fù)雜的業(yè)務(wù)校驗(yàn)。
how: 校驗(yàn)完畢后的動(dòng)作。框架不能決定做什么,但是在校驗(yàn)結(jié)果出來后,框架應(yīng)能知會到外部調(diào)用者。
在設(shè)計(jì)框架或者選擇已有框架時(shí),首先要區(qū)分框架的邊界,簡單來說,就是做什么和不做什么。框架應(yīng)實(shí)現(xiàn)相對固定的業(yè)務(wù)流程。同時(shí)對可變部分預(yù)留足夠的靈活性。
一個(gè)通用的校驗(yàn)框架一定是不含界面部分的。界面是多變和難以窮舉的,是用tip顯示錯(cuò)誤,還是在輸入域附近顯示,是否需要?jiǎng)赢?是否需要修改輸入域的視覺狀態(tài),這些可變化的部分應(yīng)為框架外部內(nèi)容,由更專業(yè)的tip組件或者popup來完成。框架只應(yīng)該負(fù)責(zé)在校驗(yàn)完成時(shí)候知會相關(guān)組件完成顯示錯(cuò)誤提示等若干事情。
基于以上的分析,校驗(yàn)框架應(yīng)該具備以下規(guī)格
1. 解決what問題。內(nèi)置了各種格式校驗(yàn)規(guī)則,如電話號碼、e-mail等.并且能夠靈活定義新的邏輯校驗(yàn)。
2. 解決who問題。說明如何根據(jù)輸入的字符真正找到who對應(yīng)的value。并且能夠?qū)τ谶@個(gè)who使用哪些校驗(yàn)規(guī)則
3. 解決when問題。提供一個(gè)觸發(fā)校驗(yàn)的方法。
4. 解決how問題。產(chǎn)生校驗(yàn)結(jié)果后能夠知會外部的功能框架。
在淘寶機(jī)票訂單應(yīng)用中,依據(jù)上述原則自行設(shè)計(jì)了一個(gè)Validator框架,接口定義如下,Validator是校驗(yàn)框架對象。
在構(gòu)造函數(shù)中提供表格化的校驗(yàn)邏輯定義型式。如下圖所示,傳遞如下結(jié)構(gòu),定義每個(gè)字段對應(yīng)的校驗(yàn)方式。在下圖中,定義每行為一個(gè)field,每個(gè)field有若干rule,每個(gè)rule可以是框架內(nèi)置的格式校驗(yàn),也可以是自定義的邏輯校驗(yàn),實(shí)際上是函數(shù)名。
Validator框架提供validate()方法,validate方法有兩個(gè)行為,如果不指定參數(shù),將依次執(zhí)行完所有field的校驗(yàn),并且將最終結(jié)果返回。如果執(zhí)行一個(gè)field name,框架將只校驗(yàn)field name對應(yīng)的輸入域。
一旦執(zhí)行validate()方法,無論校驗(yàn)結(jié)果如何,框架均向其觀察者發(fā)送事件’onValidate’。以便觸發(fā)后續(xù)動(dòng)作。
一些輔助參數(shù),需要提供一個(gè)從field name找到輸入域value的function。
總結(jié)
在處理復(fù)雜表單時(shí),首先通過合理模塊、組件劃分,將復(fù)雜度分散。然后利用詳細(xì)和廣播機(jī)制解決分散的模塊和組件間通問題。接著,過于復(fù)雜的組件要考慮進(jìn)一步拆分,具體拆分的方式有縱切和橫切兩種,根據(jù)具體使用場景決定。最后,不要小看了校驗(yàn),需要特別對待,專門設(shè)計(jì)。
文章編輯: 365webcall在線客服(www.365webcall.com)
我的評論
登錄賬號: | 密碼: | 快速注冊 | 找回密碼 |