作者 |陸晨 致遠(yuǎn) 陳琦
1. 前言
在本地生活服務(wù)領(lǐng)域,面向C端的信息展示類功能存在著類生物系統(tǒng)的復(fù)雜性,具體體現(xiàn)在以下三個(gè)方面:功能多,為了幫助用戶高效找店、找服務(wù),信息會(huì)在盡可能多的地方展示;差異大,同樣的信息,在不同客戶端、不同頁面及模塊下的展示邏輯會(huì)存在一些差異;功能易變,產(chǎn)品邏輯經(jīng)常調(diào)整。以上三個(gè)方面的特點(diǎn)給研發(fā)同學(xué)帶來了很大的挑戰(zhàn),比如當(dāng)我們面臨數(shù)千個(gè)功能模塊,數(shù)十個(gè)行業(yè)產(chǎn)品的持續(xù)需求時(shí),如何快速響應(yīng)呢?
進(jìn)入互聯(lián)網(wǎng)“下半場(chǎng)”,靠“堆人力”的研發(fā)方式已經(jīng)不再具備競(jìng)爭(zhēng)力了,真正可行且有效的方式是讓系統(tǒng)能力變得可沉淀、可組合復(fù)用、可靈活應(yīng)對(duì)各種變化。在多業(yè)態(tài)、大規(guī)模定制需求的背景下,本文分享了如何通過組裝式開發(fā)的方法來提升業(yè)務(wù)的競(jìng)爭(zhēng)力。
2. 背景與問題
2.1 業(yè)務(wù)背景
先來講一下我們的業(yè)務(wù)和產(chǎn)品,美團(tuán)到店是一個(gè)生活服務(wù)平臺(tái),通過“信息”連接消費(fèi)者和商家,幫助用戶降低交易成本,這是信息產(chǎn)品功能的業(yè)務(wù)價(jià)值。當(dāng)我們打開美團(tuán)/點(diǎn)評(píng)App,搜索“美發(fā)”,就可以看到一個(gè)搜索結(jié)果頁,展示著基于關(guān)鍵詞召回的美發(fā)商戶(如下圖左所示)。商戶下面掛著當(dāng)前門店所提供的團(tuán)購、會(huì)員卡概要信息,我們選擇一家門店進(jìn)入商戶詳情頁,自上而下滑動(dòng),可以看到商戶的地址模塊、營業(yè)信息模塊等基礎(chǔ)模塊(如下圖右所示)。繼續(xù)往下還能看到商品貨架模塊、會(huì)員卡模塊、發(fā)型師信息等等,以上就是信息展示產(chǎn)品的具體形態(tài)。
圖1 產(chǎn)品功能形態(tài)前文我們提到過本地生活服務(wù)行業(yè)信息類產(chǎn)品功能的核心特點(diǎn)是功能多、差異大、功能易變,為了幫助讀者更好地了解相關(guān)的業(yè)務(wù)背景,針對(duì)這幾個(gè)特點(diǎn)我們進(jìn)一步補(bǔ)充:
- 功能多:在多業(yè)態(tài)背景下,信息展示功能總體上表現(xiàn)為功能模塊非常多。主要是因?yàn)橥瑯拥膬?nèi)容會(huì)在多個(gè)地方展示,比如某個(gè)行業(yè)的商品信息會(huì)在App的首頁、搜索結(jié)果頁、頻道頁、詳情頁、訂單頁、運(yùn)營頁等多個(gè)頁面進(jìn)行展示。并且當(dāng)新行業(yè)新內(nèi)容出現(xiàn)的時(shí)候,又會(huì)全面鋪開,進(jìn)而導(dǎo)致增加更多的功能。截至目前,我們已有上千個(gè)展示功能,呈規(guī)?;瘎?shì)態(tài)。
- 差異大:差異化主要體現(xiàn)在相同的內(nèi)容,在不同行業(yè)、不同客戶端、不同模塊、不同版本甚至是不同用戶條件下,都會(huì)有不同展示邏輯。比如商戶詳情頁貨架的商品標(biāo)題這個(gè)字段,有的行業(yè)展示的規(guī)則是“服務(wù)類型+商品名字”,如“[玻璃貼膜]龍膜全車車窗隔熱膜套餐”。有的行業(yè)的展示規(guī)則是“服務(wù)特性+商品名字”,如“[洗吹]單人明星洗吹+造型”。再比如跳轉(zhuǎn)鏈接這個(gè)字段,H5、小程序和App內(nèi)的跳轉(zhuǎn)鏈接的拼接規(guī)則都不一樣。諸如此類的差異幾乎貫穿所有的功能。
- 功能易變:主要體現(xiàn)在產(chǎn)品邏輯會(huì)經(jīng)常發(fā)生迭代。分析變化原因來自多個(gè)方面,首先是這類信息產(chǎn)品面向海量互聯(lián)網(wǎng)用戶,用戶體驗(yàn)敏感度高,細(xì)微的展示規(guī)則差別都可能會(huì)導(dǎo)致不同的轉(zhuǎn)化效果,到底是哪個(gè)展示規(guī)則效果比較好,產(chǎn)品只能通過不斷的調(diào)整來進(jìn)行驗(yàn)證。其次,本地生活服務(wù)標(biāo)準(zhǔn)化程度低,內(nèi)容本身的結(jié)構(gòu)也在不斷迭代,內(nèi)容變更同時(shí)也決定了展示功能要跟著變。最后一點(diǎn),互聯(lián)網(wǎng)行業(yè)中產(chǎn)品的職責(zé)也會(huì)經(jīng)常進(jìn)行調(diào)整,不同的產(chǎn)品對(duì)功能的理解是不一樣的,這也是導(dǎo)致功能更迭的原因之一。
以上是生活服務(wù)行業(yè)信息產(chǎn)品的特點(diǎn),面對(duì)大規(guī)模、差異化的信息展示類功能的挑戰(zhàn),產(chǎn)品在持續(xù)迭代,研發(fā)同學(xué)又面臨怎樣的問題和挑戰(zhàn)呢?
2.2 研發(fā)挑戰(zhàn)
在分享技術(shù)挑戰(zhàn)之前,可以先看看研發(fā)同學(xué)的日常。這里有兩個(gè)小場(chǎng)景:
- 場(chǎng)景一,由B端(商家/運(yùn)營)直接生產(chǎn)出來的信息,不能直接展示給用戶。B端主要關(guān)注信息能否高效錄入,錄入的信息不適合直接展示給用戶,需要經(jīng)過一些邏輯加工,同一份B端錄入的信息可能會(huì)有多種加工展示規(guī)則。
- 場(chǎng)景二,由于B/C端業(yè)務(wù)領(lǐng)域問題差別較大,為降低開發(fā)難度,B/C端一般會(huì)做精細(xì)化分工,一撥人專注B端的信息錄入能力建設(shè),一撥人專注C端的信息展示。
而我們就是專注信息展示的這撥人。這類系統(tǒng)業(yè)界也有一些標(biāo)準(zhǔn)的術(shù)語,叫BFF(Backend For Frontend)。BFF的主要職責(zé)是組合使用底層數(shù)據(jù),額外處理C端展示邏輯。綜上所述,我們研發(fā)同學(xué)具體的工作通常是:通過外部數(shù)據(jù)源將原始數(shù)據(jù)查到,然后按照產(chǎn)品的要求,把查到的原始信息加工成可以展示給用戶的信息,最后發(fā)送給客戶端使用。如下圖所示,這部分工作主要由中間的BFF API服務(wù)負(fù)責(zé):
圖2 研發(fā)的主要工作
看到這里,我們猜你可能會(huì)這么想:就這么簡(jiǎn)單的工作,能有啥技術(shù)挑戰(zhàn)?確實(shí),如果是站在純編碼的角度,代碼“擼上”就完事了,確實(shí)沒什么挑戰(zhàn)。但,這不是一個(gè)簡(jiǎn)單的敲代碼問題,而是一個(gè)工程問題。當(dāng)有上千個(gè)這樣的功能,產(chǎn)品需求持續(xù)涌入時(shí),如何用有限的研發(fā)資源滿足無限的業(yè)務(wù)需求,同時(shí)能夠控制系統(tǒng)的復(fù)雜性及運(yùn)維成本,還要考慮人的成長問題,這才是我們面臨的關(guān)鍵挑戰(zhàn)。
- 問題1 - 效率:天下武功,唯快不破。產(chǎn)品功能追求快速上線似乎是個(gè)永恒的命題,在互聯(lián)網(wǎng)行業(yè)C端海量用戶的背景下,這個(gè)命題尤為突出。此外,很小一個(gè)功能點(diǎn)的改動(dòng)就會(huì)對(duì)用戶體驗(yàn)產(chǎn)生非常大的影響。在人力有限的情況下,如何滿足大量產(chǎn)品需求快速上線是研發(fā)團(tuán)隊(duì)面臨的首要挑戰(zhàn)。
- 問題2 - 復(fù)雜性:稍微有點(diǎn)追求的工程師都會(huì)考慮代碼復(fù)用的問題,不管代碼寫得優(yōu)不優(yōu)雅,絕對(duì)不能允許重復(fù),于是代碼中就充斥著各種if…else…,因?yàn)橛械倪壿嬛挥挟?dāng)某些特定情況下才會(huì)運(yùn)行。但是,復(fù)用需要良好的建模,嵌入過多的邏輯,只會(huì)讓系統(tǒng)的復(fù)雜性變得越來越高,進(jìn)而讓系統(tǒng)難以進(jìn)行下一步的演進(jìn),這通常會(huì)消耗大量的隱性成本。如何控制好系統(tǒng)的復(fù)雜性,讓系統(tǒng)長期保持可理解、可修改,是我們面臨的又一挑戰(zhàn)。
- 問題3 - 運(yùn)維成本:如果缺少抽象的過程式開發(fā),還會(huì)導(dǎo)致系統(tǒng)功能變得非常繁多,接口就好幾百個(gè),平時(shí)這些接口的運(yùn)維工作也要耗費(fèi)大量的人力。如果功能的開發(fā)缺少統(tǒng)一的章法,代碼邏輯復(fù)雜交錯(cuò),就會(huì)給運(yùn)維工作帶來非常大的挑戰(zhàn)。
- 問題4 - 成就感:根據(jù)馬斯洛需求層次的理論,頂層是“自我實(shí)現(xiàn)”的需求。如果落實(shí)到工程師的日常,大家也需要在工作中找到成就感??蓡栴}在于,如果每天都“過程式”地編寫數(shù)據(jù)的查詢、加工和組裝的業(yè)務(wù),對(duì)大家來說,很難產(chǎn)生什么成就感。
導(dǎo)致這些問題的根本原因是什么呢?這里想借用美團(tuán)聯(lián)合創(chuàng)始人王慧文在知乎上說的一句話,以科學(xué)和工程追求真理。真理是什么難以定義,但我們一定要運(yùn)用科學(xué)的、工程的方法。下面將會(huì)進(jìn)一步介紹對(duì)這個(gè)問題的思考以及我們的解決思路。
3. 問題分析與解決思路
3.1 問題思考
常規(guī)編程的基本工作,總是基于某種編程范式展開的,比如面向?qū)ο?、過程式、函數(shù)式。我們很容易就想到,如果解決問題的范式不能很好地和問題相匹配,那么就會(huì)引起矛盾。所以歷史上有很多使用匯編語言難以完成大規(guī)模項(xiàng)目、使用結(jié)構(gòu)化編程難以應(yīng)對(duì)超大規(guī)模項(xiàng)目的故事。
那么,前文提到的問題,是不是因?yàn)殚_發(fā)方式和業(yè)務(wù)問題不匹配而導(dǎo)致的呢?舉個(gè)通俗的例子,好比我們現(xiàn)在要做的是一道西紅柿炒蛋,但是拿出的工具卻是電烤箱,后果可想而知。當(dāng)然,有人說可能會(huì)說“真正的高手是可以的”,但畢竟絕世高手是極少數(shù)。而如果我們拿的出是平底鍋,肯定會(huì)更容易一些。所以解決問題不能一味地追求成為“絕世高手”,降低解決問題的門檻才是真正行之有效的方式。
靜心反思,我們認(rèn)為真正原因在于:我們所使用的開發(fā)范式與業(yè)務(wù)問題不匹配。換句話來講,我們對(duì)問題缺少針對(duì)性的建模,缺少針對(duì)性的方法論。比如在業(yè)務(wù)層面,我們的訴求是能夠快速交付,能夠滿足需求的多樣性,并且能夠快速響應(yīng)產(chǎn)品功能的靈活變化。在技術(shù)層面,我們的訴求是在人力有限的背景下,讓系統(tǒng)復(fù)雜度可控,且代碼復(fù)雜度不會(huì)與業(yè)務(wù)邏輯呈現(xiàn)“乘積式”關(guān)系的增加。
此外,還要保證運(yùn)維成本可控,系統(tǒng)數(shù)不會(huì)和功能數(shù)呈現(xiàn)“線性關(guān)系”的增加。而我們當(dāng)前的開發(fā)方式卻是“過程式”的,這種“過程式”體現(xiàn)為面向產(chǎn)品需求文檔的直譯式編程。但這種開發(fā)模式和我們的訴求并不匹配。因此,我們需要取尋找適合我們自己的開發(fā)范式。
3.2 標(biāo)準(zhǔn)化及組裝式思想
John Ousterhout曾說過:復(fù)雜性是由模糊性和依賴性引起的。模糊性主要源于對(duì)事物缺少清晰的概念描述,因此復(fù)雜性通常會(huì)通過應(yīng)用很多關(guān)鍵概念來解決,這些概念通過抽象、分解、迭代和細(xì)化這樣的方法來進(jìn)行表達(dá),建立明確的概念是消除模糊性的關(guān)鍵,也是我們解決復(fù)雜問題的常規(guī)思維方法。
在這個(gè)過程中,分解指的是把一個(gè)較大的問題分解成較小的、可管理的單元,每個(gè)單元都可以單獨(dú)處理,這些單元被稱為模塊、包或組件。這個(gè)思路可以追溯到哲學(xué)上的“還原論”,目前已成為軟件工程的很多方法論的核心。依賴性指的是模塊之間依賴關(guān)系的多少以及強(qiáng)弱程度,比如一個(gè)模塊是否依賴另一個(gè)模塊的實(shí)現(xiàn),模塊之間的依賴是否遵循統(tǒng)一的契約,這些都會(huì)影響到系統(tǒng)的復(fù)雜性。
組裝式開發(fā)指的是將系統(tǒng)分解為標(biāo)準(zhǔn)組件,再由標(biāo)準(zhǔn)組件組裝成系統(tǒng),以此形成的架構(gòu)被稱為組裝式架構(gòu)。跟傳統(tǒng)代碼復(fù)用技術(shù)關(guān)鍵的不同點(diǎn)在于組件的含義。組件是高度標(biāo)準(zhǔn)化的單元,具備可復(fù)用性、標(biāo)準(zhǔn)化、可替換性、可包裝及獨(dú)立自治這些特征。組裝式開發(fā)背后的核心邏輯是基于標(biāo)準(zhǔn)化思想的代碼復(fù)用。
關(guān)于標(biāo)準(zhǔn)化,歷史上還有這樣的一個(gè)故事:18世紀(jì)末,美國剛建立不久,由于國內(nèi)外戰(zhàn)火尚未停息,政府擔(dān)心會(huì)與法國作戰(zhàn),急需準(zhǔn)備大量的軍火。但是,當(dāng)時(shí)的傳統(tǒng)制槍方式是依靠熟練的工匠采用磨、削、錘等工序制成一個(gè)個(gè)非標(biāo)準(zhǔn)的槍機(jī)零件,然后將它們組裝成槍支,這種制作方法即使全部都是“能工巧匠”,生產(chǎn)效率也非常低下。于是,在政府的敦促下,伊萊·惠特尼(美國發(fā)明家、機(jī)械工程師和企業(yè)家,發(fā)明了軋花機(jī)、銑床)把整個(gè)工序分成若干工序,并把一個(gè)零件都比照標(biāo)準(zhǔn)來福搶的樣品紡制成通用的零件,由此在軍火生產(chǎn)中成功地引進(jìn)了零件可替換性的原理,這是工業(yè)標(biāo)準(zhǔn)化劃時(shí)代的開端。此后,標(biāo)準(zhǔn)化的互換性原理促進(jìn)了工業(yè)的迅速發(fā)展,惠特尼也被譽(yù)為現(xiàn)代工業(yè)的“標(biāo)準(zhǔn)化之父”。
圖3 “標(biāo)準(zhǔn)化原理”的應(yīng)用
再舉一個(gè)例子,樂高積木玩具我們都知道,玩具廠商制造了一批標(biāo)準(zhǔn)的積木,孩童可以基于這些積木組裝成不同款式的玩具,喜歡飛機(jī),就組裝飛機(jī),喜歡坦克,就組裝坦克,這也是利用了標(biāo)準(zhǔn)化的可換性原理。再回到軟件行業(yè),基于組件實(shí)現(xiàn)大規(guī)模的軟件復(fù)用這個(gè)概念,最早來源于Doug McIlroy在1968年的一次軟件工程學(xué)會(huì)上的演講,演講名為“大規(guī)模生產(chǎn)的軟件組件”,之后這被公認(rèn)為軟件復(fù)用的起源。基于這個(gè)思想,如果我們能夠引入組裝式開發(fā)的思想,將業(yè)務(wù)代碼分解成標(biāo)準(zhǔn)化的組件,然后再基于這些組件組裝成不同的功能,進(jìn)而滿足不同場(chǎng)景下的業(yè)務(wù)需求,這不就是符合我們需求的開發(fā)方式嗎?
但知易行難,因?yàn)樗雎粤撕芏喱F(xiàn)實(shí)的細(xì)節(jié),我們?cè)趯?shí)際應(yīng)用的時(shí)候總是要面臨各種現(xiàn)實(shí)問題的挑戰(zhàn)。柏拉圖曾對(duì)人生的終極問題做了定義:我是誰?我來自哪里?我將要到哪里去?這些問題延續(xù)至今,一直困擾著人們?nèi)祟悺6浖こ桃蚕蚬こ處熼T提出了軟件設(shè)計(jì)的終極問題:什么是抽象層次?什么顆粒度?以及如何應(yīng)對(duì)變化?
所以,組裝式開發(fā)的歷史坎坷崎嶇,更難的是在每個(gè)技術(shù)領(lǐng)域這些問題的答案還都不一樣。比如在顆粒度的問題上,組件的顆粒度到底要多大,顆粒度越大,被修改的風(fēng)險(xiǎn)也就越大,而過小的顆粒度可以讓組件更穩(wěn)定,但是會(huì)帶來組裝的復(fù)雜性。再比如在應(yīng)對(duì)變化這個(gè)問題上,這段邏輯到底是通用的還是個(gè)性的,非常難以辨別,但是我們的系統(tǒng)設(shè)計(jì)又強(qiáng)依賴于這個(gè)判斷,如果最初的判斷失誤,就有可能導(dǎo)致系統(tǒng)最終的失敗。我們就遇到過這種情況,有一次自信滿滿地封裝了一個(gè)組件,感覺應(yīng)該可以滿足各種復(fù)雜的情況,剛好就在下一個(gè)需求來臨時(shí),發(fā)現(xiàn)不太匹配。
3.3 我們的解決思路
前文講,組裝式開發(fā)看起來正是我們所需要的開發(fā)模式,然后我們也討論了組裝式開發(fā)面臨的關(guān)鍵挑戰(zhàn)。那么,在我們的實(shí)際業(yè)務(wù)中,組裝式開發(fā)真的可行嗎?如果可行,我們又怎樣應(yīng)對(duì)隨之而來的挑戰(zhàn)?特別是本地生活行業(yè)細(xì)分行業(yè)多、功能多。當(dāng)然在業(yè)務(wù)給技術(shù)帶來了挑戰(zhàn)的同時(shí)也蘊(yùn)含著巨大的機(jī)會(huì)。因?yàn)樯婕靶袠I(yè)越多,系統(tǒng)功能越多,代碼復(fù)用的機(jī)會(huì)也會(huì)越高(如下圖左所示)。雖說不同的功能在感官上給人的感覺差別很大,但是有很多功能的底層存在著太多的共性。我們認(rèn)為,解決問題的關(guān)鍵在于對(duì)顆粒度的把控,以及對(duì)可變性的處理。
圖4 組裝式開發(fā)可行性以及關(guān)鍵思路
然后,我們主要從粒度及可變性兩個(gè)方面著手。在粒度問題上,我們引入多層次多顆粒度的組件體系設(shè)計(jì);在可變性問題上,我們通過可變性建模讓整個(gè)功能具備靈活應(yīng)對(duì)變化的能力,最終形成了如上右圖所示的概念模型。除此之外,我們還通過可視化組裝的方式來簡(jiǎn)化組裝過程,總體思路如下:
- 產(chǎn)品功能歸類之系列化:福特T型車生產(chǎn)的案例告訴我們,單一的產(chǎn)品無法滿足市場(chǎng)化的需求,多樣性更具備市場(chǎng)競(jìng)爭(zhēng)力。但是高效的多樣性需要范圍的約束,所以現(xiàn)代汽車生產(chǎn)商會(huì)對(duì)產(chǎn)品進(jìn)行系列化,比如寶馬轎車有7系、5系、3系,系列化是廠商在效率和多樣性之間追求平衡的產(chǎn)物。在軟件開發(fā)領(lǐng)域亦然,組件的復(fù)用應(yīng)該是有范圍的,我們首先將產(chǎn)品功能進(jìn)行系列化歸類,進(jìn)一步降低了系統(tǒng)整體的復(fù)雜性及設(shè)計(jì)難度。
- 功能組件提取與預(yù)組裝:組件標(biāo)準(zhǔn)化是組裝式開發(fā)的前提,所以我們需要先將組件提取出來。在最終組裝功能的時(shí)候,為了降低組裝的復(fù)雜性,我們引入了“預(yù)組裝”的概念。針對(duì)每個(gè)系列的產(chǎn)品功能,我們將通用部分提前組裝好,將定制部分提前預(yù)留出來,定制部分包括功能和特性的定制,這部分可延遲到需要的時(shí)候再組裝。
- 變化應(yīng)對(duì)之可變性建模:在電動(dòng)車出現(xiàn)以前,所有的汽車幾乎都需要燃油來發(fā)動(dòng),因此燃油機(jī)屬于共性功能的需求。但是我們發(fā)現(xiàn),有的車主是新手,對(duì)車子非常愛惜,因此需要全景的倒車儀。有的車主是老司機(jī),他們根本不需要全景的倒車儀,所以全景倒車儀就屬于變化功能的需求。不僅如此,我們還發(fā)現(xiàn)對(duì)于同樣有全景倒車儀訴求的車主,他們也會(huì)選擇具有不同特性的設(shè)備功能,這部分屬于更細(xì)粒度的定制需求。所以,變化本身是復(fù)雜的,是有層次的,變化需要被單獨(dú)建模,才能夠被有效管理。
- 可視化組裝與配置填充:細(xì)顆粒度的組件帶來了一定的組裝復(fù)雜性,為簡(jiǎn)化組裝過程,我們將可用組件呈現(xiàn)在用戶的界面上,開發(fā)同學(xué)通過點(diǎn)擊鼠標(biāo)即可完成組件的組裝,而對(duì)于功能特性組件的配置填充,也是在用戶界面上直接完成的。
通過以上幾個(gè)策略,我們將信息展示場(chǎng)景的研發(fā)模式打造成一個(gè)多系列產(chǎn)品的生產(chǎn)線,每個(gè)生產(chǎn)線都支持組裝式生產(chǎn)一個(gè)系列的定制功能。下一章節(jié)我們將介紹更多的技術(shù)細(xì)節(jié)。
4. 標(biāo)準(zhǔn)化思想及組裝式架構(gòu)在后端BFF中的實(shí)踐
4.1 產(chǎn)品功能歸類之系列化
1)產(chǎn)品系列化
在官方語言里,系列化指的是“對(duì)同一類產(chǎn)品的結(jié)構(gòu)形式和主要參數(shù)規(guī)格進(jìn)行科學(xué)規(guī)劃的一種標(biāo)準(zhǔn)化形式”。在我們這里,系列化指的是對(duì)信息類產(chǎn)品功能進(jìn)行歸類,目的包含兩個(gè)方面,一方面是為了降低系統(tǒng)整體的復(fù)雜性,另一方面也為建設(shè)組裝這些功能的“生產(chǎn)線”做準(zhǔn)備,一個(gè)系列的功能由一個(gè)“生產(chǎn)線”來組裝,組件可以在不同范圍內(nèi)進(jìn)行復(fù)用。
信息類產(chǎn)品展示的內(nèi)容通常來自多個(gè)領(lǐng)域,比如一個(gè)商品展示模塊,可能要聚合門店的信息,很難直接通過領(lǐng)域來進(jìn)行劃分,那么怎么劃分系列呢?顯性的差異我們能直接看出來,主要包括兩方面,首先是展示內(nèi)容方面,我們能夠比較容易地發(fā)現(xiàn)每個(gè)展示模塊都有主要的展示內(nèi)容的差別,比如主要內(nèi)容分別是門店信息、評(píng)價(jià)信息、內(nèi)容信息、商品信息等內(nèi)容,其他信息往往附屬于主要內(nèi)容。其次展示樣式方面,有的展示樣式差別很明顯,比如商品詳情頁和商品貨架模塊;有的展示樣式差別沒那么大,比如同樣是商品貨架模塊,只是個(gè)別字段有差異。隱性的差別主要是內(nèi)部的實(shí)現(xiàn),因?yàn)檫@些實(shí)現(xiàn)直觀上是看不出來的。
圖5 信息類產(chǎn)品功能歸類思路我們主要從展示內(nèi)容、展示樣式及展示邏輯實(shí)現(xiàn)等幾個(gè)方面來對(duì)功能進(jìn)行歸類合并:
- 基于內(nèi)容:一個(gè)模塊展示的內(nèi)容通常分為主要內(nèi)容和附屬內(nèi)容,不同展示模塊最大的差別來源于展示主要內(nèi)容的差別。比如以展示門店為主的模塊和以展示商品為主的模塊,不管是系統(tǒng)實(shí)現(xiàn),還是功能形態(tài),都存在比較明顯的差別。因此我們首先把展示內(nèi)容的主體是誰作為歸類依據(jù),將不同內(nèi)容的展示功能區(qū)分開來。
- 基于樣式:功能展示的樣式差別往往會(huì)決定展示數(shù)據(jù)的差別,如果展示數(shù)據(jù)差別較大,那么接口則不容易做標(biāo)準(zhǔn)化。比如商品詳情頁和商品貨架模塊,商品詳情只展示一個(gè)商品,同時(shí)會(huì)展示更多的商品信息。商品貨架模塊會(huì)展示多個(gè)商品,但是每個(gè)商品只展示少部分的信息,商品貨架模塊還會(huì)展示篩選項(xiàng)。顯然它們的接口很難統(tǒng)一,因此展示形式是一個(gè)重要的歸類維度。
- 基于實(shí)現(xiàn):最后要從實(shí)現(xiàn)層面看好不好抽象,另外兩個(gè)維度的抽象已經(jīng)為這個(gè)維度打好了良好的基礎(chǔ)。怎么抽象呢?比如有兩個(gè)功能的步驟和依賴功能的組合能夠抽象的大致相似,那么這兩個(gè)功能可以歸為一個(gè)系列。
以上維度并不是絕對(duì)的優(yōu)先級(jí)關(guān)系,但能解決絕大多數(shù)的問題。也存在例外情況,比如有的功能也可能同時(shí)展示多種信息,但找不到主要展示的對(duì)象,那么我們可以基于實(shí)現(xiàn)這個(gè)因素來進(jìn)行選擇。經(jīng)過上面一波操作,我們基本可以得到產(chǎn)品功能的系列化全景,上千個(gè)功能經(jīng)過系列化之后,也就僅僅只有幾個(gè)系列。以上只是業(yè)務(wù)層面的劃分,那么系統(tǒng)對(duì)應(yīng)有怎樣的設(shè)計(jì)呢?
2)接口標(biāo)準(zhǔn)化
在產(chǎn)品功能系列化歸類之后,同一系列內(nèi)的產(chǎn)品功能之間仍然會(huì)存在邏輯差異,這些差異主要體現(xiàn)在展示模型以及內(nèi)部實(shí)現(xiàn)上。展示模型是后端吐給前端的數(shù)據(jù)結(jié)構(gòu),主要的職責(zé)是承載展示數(shù)據(jù),不同功能存在字段上的差別,所以導(dǎo)致模型會(huì)有差別。比如一個(gè)簡(jiǎn)單的例子,有的功能有標(biāo)簽字段,有的沒有,那在標(biāo)簽這個(gè)字段上就形成了差異。內(nèi)部實(shí)現(xiàn)主要包括數(shù)據(jù)的查詢邏輯、加工邏輯等。針對(duì)這兩方面差異,我們的核心思路是接口模型標(biāo)準(zhǔn)化及統(tǒng)一業(yè)務(wù)身份來串聯(lián)差異化邏輯。
圖6 標(biāo)準(zhǔn)接口+多租戶模式
系列化本身更有利于接口模型做統(tǒng)一抽象,因?yàn)橥幌盗袃?nèi)部的功能在結(jié)構(gòu)上的差異不會(huì)太大,我們只需要稍微做一些抽象,大多數(shù)字段都可以收斂。對(duì)于極少的個(gè)別情況,比如某個(gè)字段就個(gè)別功能才有,我們就通過K-V結(jié)構(gòu)來進(jìn)行應(yīng)對(duì)。
模型設(shè)計(jì)的具體細(xì)節(jié)在這里不過多展開,重點(diǎn)是接口統(tǒng)一化、標(biāo)準(zhǔn)化之后有明顯的好處。前后端的協(xié)作效率提高了,前端和后端不再需要在每次需求變更的時(shí)候都當(dāng)面溝通一次接口。不僅如此,系統(tǒng)層面接口的標(biāo)準(zhǔn)化也能夠讓前端的代碼和后端接口的集成關(guān)系變得更加穩(wěn)定。
在搞定接口之后,內(nèi)部的差異怎么辦?下文會(huì)介紹我們?cè)谶@部分的組裝式思路。在這個(gè)思路下,內(nèi)部實(shí)現(xiàn)方面的差異最終會(huì)表現(xiàn)為一系列不同顆粒度組件的集合差異,所以這里核心要解決的問題是如何能夠識(shí)別功能差異化組裝組件的問題。針對(duì)這個(gè)問題,我們的思路是引入“業(yè)務(wù)身份”這個(gè)概念,這個(gè)概念目前應(yīng)用得也比較廣泛,我們通過業(yè)務(wù)身份來串聯(lián)不同業(yè)務(wù)場(chǎng)景的數(shù)據(jù)組裝組件,從而實(shí)現(xiàn)差異化邏輯的處理。
4.2 功能組件提取與預(yù)組裝
1)功能組件提取
系統(tǒng)劃分方法在業(yè)界應(yīng)用的比較多的是領(lǐng)域驅(qū)動(dòng)方法(DDD),基于領(lǐng)域驅(qū)動(dòng)方法,我們一般會(huì)按照實(shí)體或者聚合根來劃分子系統(tǒng)或模塊,但是對(duì)于信息展示類的系統(tǒng)來說,很難應(yīng)用領(lǐng)域驅(qū)動(dòng)的方法。因?yàn)槲覀冮_發(fā)的不是一個(gè)單領(lǐng)域的小系統(tǒng),而是一類跨多個(gè)領(lǐng)域的、屬于由幾千人共同開發(fā)的復(fù)雜分布式系統(tǒng)之上的一個(gè)子系統(tǒng)。這個(gè)系統(tǒng)負(fù)責(zé)查詢和組裝由底層系統(tǒng)提供的數(shù)據(jù),然后將數(shù)據(jù)加工、裁剪、組裝展示模型給到前端。這類系統(tǒng)距離業(yè)務(wù)實(shí)體很遠(yuǎn),因此對(duì)于這類系統(tǒng)的組件化分解,不能應(yīng)用傳統(tǒng)領(lǐng)域驅(qū)動(dòng)的方法,而需要使用一種特殊的方法。我們的基本的思路是梳理現(xiàn)有流程步驟,將現(xiàn)有功能按相關(guān)性歸類,同一類功能封裝成一個(gè)功能組件,然后再由多個(gè)組件組合成一個(gè)系列功能。這里舉個(gè)例子:
圖7 組件提取案例
左圖所示的是一個(gè)商品貨架的場(chǎng)景,右圖展示的是要生產(chǎn)這個(gè)貨架所需展示數(shù)據(jù)需要經(jīng)歷的流程步驟。通過上圖我們可以看到,貨架的展示數(shù)據(jù)的生產(chǎn)過程主要包括以下幾個(gè)步驟:
- 根據(jù)查詢條件查詢商品ID,比如查詢門店下可售賣的團(tuán)單ID;
- 聚合商品維度信息,這些信息包括商品的標(biāo)題、價(jià)格、服務(wù)流程、優(yōu)惠促銷、標(biāo)簽等等;
- 查詢貨架的篩選數(shù)據(jù)及商品的擺放規(guī)則,比如推薦、熱銷、新品等標(biāo)簽數(shù),以及哪些商品放在哪些標(biāo)簽下面的擺放規(guī)則;
- 組裝結(jié)果展示模型,涉及聚合數(shù)據(jù)的再加工,如標(biāo)題、標(biāo)簽的拼接。
以上流程步驟相對(duì)比較清晰,并且能夠適應(yīng)一類場(chǎng)景,只是不同場(chǎng)景在步驟內(nèi)存在部分差異而已。所以我們可以將這幾個(gè)步驟抽取出來,每個(gè)步驟分別封裝成單獨(dú)組件負(fù)責(zé)解決一類問題,同時(shí)組件可以組合復(fù)用。值得強(qiáng)調(diào)的是,這些組件的封裝都是基于標(biāo)準(zhǔn)的接口實(shí)現(xiàn)。
2)功能組件預(yù)組裝
傳統(tǒng)的業(yè)務(wù)流程編排適合于流程類業(yè)務(wù)場(chǎng)景,比如OA辦公審核系統(tǒng),基于業(yè)務(wù)流程引擎的好處,一方面是容易實(shí)現(xiàn)能力的復(fù)用,復(fù)用現(xiàn)有能力編排出新的流程。另一方面是更容易應(yīng)對(duì)流程的變化,因此特別適合流程類且流程易變化的場(chǎng)景。組件類似業(yè)務(wù)流程編排類系統(tǒng)中的“能力”,如果通過業(yè)務(wù)流程引擎,也可以實(shí)現(xiàn)類似的效果。但是實(shí)際上,信息展示場(chǎng)景的業(yè)務(wù)流程更像是一個(gè)圖,我們姑且稱之為“活動(dòng)圖”,而不是一條長長的管道流程,如下圖所示:
圖8 有篩選和沒篩選之間的差別
流程引擎適合應(yīng)對(duì)流程的變化,而我們業(yè)務(wù)場(chǎng)景中,變化之處不在于流程本身,而在于在這個(gè)活動(dòng)圖上執(zhí)行的步驟集合。如上圖的示例,左側(cè)是有篩選的貨架,需要查詢篩選標(biāo)簽數(shù)據(jù),所以執(zhí)行的是整個(gè)活動(dòng)圖。右側(cè)是沒有篩選的情況,不需要查篩選標(biāo)簽數(shù)據(jù),所以執(zhí)行的是活動(dòng)圖的子圖,實(shí)際業(yè)務(wù)場(chǎng)景更復(fù)雜一點(diǎn)的活動(dòng)圖也有,這里只是舉個(gè)簡(jiǎn)單的例子。
我們會(huì)發(fā)現(xiàn),不同情況的不同之處在于遍歷這個(gè)活動(dòng)圖的節(jié)點(diǎn)的集合不同,總體類似在一個(gè)完整的圖中選取一個(gè)子圖。因此,我們選擇以圖的方式組織我們的組件,而不是傳統(tǒng)的流程,這樣更貼合我們的實(shí)際情況,也更容易理解。
另外,為了提高組件組裝的效率,我們將一個(gè)系列功能使用到的所有組件提前組裝好,得到一張全景圖。那么在需要的時(shí)候,我們只需要對(duì)著這個(gè)完整的圖選擇子圖即可,然后再基于子圖組裝定制部分的組件。預(yù)組裝不僅能夠提升組裝的效率,同時(shí)也能夠避免錯(cuò)誤,讓系統(tǒng)變得更穩(wěn)定。傳統(tǒng)業(yè)務(wù)流程編排中,能力的應(yīng)用上下文其實(shí)是有限制的,雖然流程引擎足夠靈活,但是實(shí)際上在編排能力時(shí)仍然需要人工對(duì)能力做檢測(cè),確認(rèn)是不是能夠滿足當(dāng)前的流程。而預(yù)組裝可以從根本上避免這個(gè)問題,因?yàn)橹灰谴嬖诘穆窂?,都是可以?zhí)行的。
4.3 變化應(yīng)對(duì)之可變性建模
通過將功能組件組織成一個(gè)個(gè)活動(dòng)圖,每個(gè)活動(dòng)圖負(fù)責(zé)解決一個(gè)系列的產(chǎn)品功能展示信息組裝問題,此時(shí)的組件顆粒度還是較大的。組件顆粒度大的問題在于,容易不穩(wěn)定,從軟件設(shè)計(jì)的角度來看,是因?yàn)樽兓囊蛩靥?。比如在組裝展示模型環(huán)節(jié),展示模型組裝組件負(fù)責(zé)將數(shù)據(jù)組裝成發(fā)給前端的展示模型,實(shí)際業(yè)務(wù)場(chǎng)景中不同情況對(duì)于同一個(gè)展示字段的組裝存在不同的拼接策略,我們拿開篇的例子來講解:
圖9 不同情況組裝邏輯不一樣
左側(cè)麗人行業(yè)商品標(biāo)題的組裝規(guī)則是“服務(wù)類型+商品名字”,右圖養(yǎng)車/用車行業(yè)商品標(biāo)題組裝規(guī)則是“服務(wù)特性+商品名字”。其實(shí)這個(gè)組件的顆粒度剛剛好,因?yàn)樗屛覀兊幕顒?dòng)圖看起來不至于太復(fù)雜,但怎么應(yīng)對(duì)這種變化呢?作為有經(jīng)驗(yàn)的程序員,可能自然會(huì)想到條件分支語句if…else…,而且實(shí)際中很多項(xiàng)目針對(duì)這種問題的處理方式都是if…else…。
對(duì)于簡(jiǎn)單的情況,使用這種方式無可厚非,我們這里討論更復(fù)雜的情況,過多的使用條件分支至少存在兩個(gè)方面的問題。一方面是代碼將會(huì)變得非常復(fù)雜,就像密密麻麻纏繞在一起的電線,這樣的代碼難以理解和維護(hù)。另一方面,這種模式本身會(huì)讓共享組件變得極其不穩(wěn)定。如果我們的系統(tǒng)建立在經(jīng)常有變動(dòng)的根基上,那么我們很難保證系統(tǒng)的穩(wěn)定性,每一次共享組件的變更都面臨著故障風(fēng)險(xiǎn),為了讓變化可管控,我們要對(duì)變化進(jìn)行建模。
1)可變性建模
這些年,軟件工程在如何應(yīng)對(duì)“變化”這個(gè)問題上,最具革命性的創(chuàng)新是將共性和變化分離,分離的變化通過使用擴(kuò)展點(diǎn)代碼或配置化變量的方式實(shí)現(xiàn)。這些經(jīng)典思想真是太棒了。對(duì)于我們也很有啟發(fā),如果將容易變化的邏輯和變化的邏輯分離開來,同時(shí)引入配置化能力,那么我們的組件將會(huì)很容易應(yīng)對(duì)變化。因此,對(duì)于可變性的管理,我們通過標(biāo)準(zhǔn)功能組件引入了可變性分離這個(gè)思想來解決。如下圖所示,組件本身具備變化點(diǎn)和配置項(xiàng)這兩種應(yīng)變能力:
圖10 易變功能通過變化點(diǎn)-可選項(xiàng)-配置項(xiàng)建模
變化點(diǎn)這個(gè)概念是我們對(duì)擴(kuò)展點(diǎn)代碼的具象化,寓意容易變化的地方;配置項(xiàng)用來承接變量的抽離。變化點(diǎn)和配置項(xiàng)都是應(yīng)對(duì)變化的實(shí)現(xiàn)方式,那么實(shí)際應(yīng)用時(shí)怎么選擇?針對(duì)可枚舉的變化,可以提取變量配置化。針對(duì)復(fù)雜度多變性,可以通過變化點(diǎn)來擴(kuò)展。變化點(diǎn)的具體形式是個(gè)接口,將變化點(diǎn)的具體實(shí)現(xiàn)放到組件之外,組件內(nèi)部公共邏輯部分只調(diào)用變化點(diǎn)接口。這樣的話,不管變化點(diǎn)的地方怎么變,即使有一百種變體,都不會(huì)影響組件的公共部分。
針對(duì)具體案例,比如查詢商品ID這個(gè)組件,我們實(shí)際上有多個(gè)查詢索引,比如商品推薦索引、商品篩選索引,還有一些強(qiáng)實(shí)時(shí)索引,但總體還是相對(duì)穩(wěn)定可枚舉的,所以我們將不同的查詢索引建模為查詢渠道配置項(xiàng),使用的時(shí)候只要填寫查詢渠道配置項(xiàng)即可。再比如展示模型組裝這個(gè)組件,因?yàn)闃?biāo)題的拼接規(guī)則會(huì)有所變化,而且較為不可枚舉,產(chǎn)品規(guī)則變化多,因此我們?cè)跇?biāo)題這個(gè)地方設(shè)定一個(gè)變化點(diǎn),不同標(biāo)題組裝的邏輯作為變化點(diǎn)的不同實(shí)現(xiàn),這樣更能夠應(yīng)對(duì)變化。
不同變化點(diǎn)的實(shí)現(xiàn)可以認(rèn)為是組件提供的多種功能特性,因此我們又抽象出可選項(xiàng)這個(gè)概念來描述變化點(diǎn)實(shí)現(xiàn)。一個(gè)變化點(diǎn)有多個(gè)選項(xiàng),選項(xiàng)是可復(fù)用的,貼合現(xiàn)實(shí)世界,更容易理解。在組裝的時(shí)候只需要“選擇需要的特性選項(xiàng)”即可滿足定制需求。以上通過變化點(diǎn)-可選項(xiàng)-配置項(xiàng)這組概念解決了組件的靈活應(yīng)變的問題,同時(shí)能夠讓組件本身變得更為穩(wěn)定。
2)可選項(xiàng)爆炸問題
我們發(fā)現(xiàn)如果每個(gè)選項(xiàng)都通過硬編碼實(shí)現(xiàn)的話,有的變化點(diǎn)可能會(huì)存在非常多的可選項(xiàng)。比如標(biāo)簽這個(gè)字段,標(biāo)簽往往來源于擴(kuò)展屬性,不同商品的模型和擴(kuò)展屬性不一樣,造成這個(gè)標(biāo)簽的來源差異很大。如果我們都是通過硬編碼實(shí)現(xiàn)選項(xiàng),那么由于標(biāo)簽來源有所差異所導(dǎo)致的選項(xiàng)擴(kuò)散問題就會(huì)很明顯,可能有多少種商品,就會(huì)有多少種選項(xiàng)。選項(xiàng)本質(zhì)上是一種更細(xì)粒度的組件,這個(gè)組件內(nèi)部本身也需要有應(yīng)對(duì)變化的能力。因此,我們的解決思路是為選項(xiàng)這個(gè)細(xì)粒度的組件增加可配置能力,每個(gè)選項(xiàng)可以設(shè)計(jì)自己的配置項(xiàng),將易變規(guī)則通過配置來實(shí)現(xiàn),這樣選項(xiàng)就有了一定程度應(yīng)對(duì)變化的能力,從而得到了收束。
4.4 可視化組裝與配置填充
在以前,當(dāng)我們需要開發(fā)一個(gè)展示功能的時(shí)候,我們需要做以下幾件事情:
- 編寫將外部數(shù)據(jù)粘合在一起的代碼,包括遠(yuǎn)程RPC的訪問代碼和數(shù)據(jù)的組裝代碼;
- 依照PRD編寫一遍展示加工邏輯;
- 將加工好的數(shù)據(jù)字段填充在和前端同學(xué)一起定義好的展示模型上;
- 構(gòu)建和集成代碼。
組裝式開發(fā)的基本要求是:將系統(tǒng)功能基于標(biāo)準(zhǔn)接口封裝成不同顆粒度的組件單元,這個(gè)要求為產(chǎn)品功能的生產(chǎn)過程帶來了革命性的轉(zhuǎn)變。功能的組裝不再需要手寫“膠水代碼”,而是通過系統(tǒng)就可以完成自動(dòng)化的組裝。大部分的功能是復(fù)用已有的組件,而不是重頭編寫,實(shí)際上經(jīng)過功能特性組件的不斷沉淀,我們已經(jīng)實(shí)現(xiàn)了80%以上的產(chǎn)品邏輯都是復(fù)用已有組件。
此時(shí),我們的研發(fā)過程整體上可分為組件開發(fā)和組裝集成兩個(gè)階段。組件開發(fā)環(huán)節(jié)關(guān)注功能的抽象和封裝,基于已有組件,組裝集成環(huán)節(jié)做的事情就是選擇需要的組件,填充配置,一旦組裝結(jié)果被保存之后,即完成系統(tǒng)的集成,不再需要構(gòu)件和部署。下圖展示的是選擇和集成組件的過程:
圖11 選功能-選特性-填配置
組件的選擇和組裝這個(gè)過程是圍繞活動(dòng)圖展開的,總體經(jīng)過選功能-選特性-填配置三個(gè)步驟,每個(gè)步驟所做的具體工作如下:
- 功能選擇:基于對(duì)產(chǎn)品需求功能點(diǎn)的捕捉,在預(yù)先組裝好的組件活動(dòng)圖上選擇當(dāng)前要開發(fā)的展示功能所需要的功能組件,這一步操作決定了大體的功能點(diǎn),比如這個(gè)貨架需要篩選功能,那么就把篩選組件選中,不需要就不選。
- 特性選擇:功能確定之后再確定更細(xì)粒度的功能特性,比如選擇展示模型組裝組件之后,這個(gè)組件要組裝多個(gè)字段,每個(gè)字段都有多種展示策略,再比如標(biāo)題這個(gè)字段,到底是要帶括號(hào)的還是不帶括號(hào)的,此時(shí)需要勾選。
- 配置填充:經(jīng)過以上兩個(gè)步驟,基本確定了當(dāng)前要組裝的展示功能所需的組件集合,但有的組件是有配置項(xiàng)的,比如前文舉的查詢商品ID這個(gè)組件,填充查詢渠道配置項(xiàng)就是在這一步完成的。
填充好配置就可以發(fā)布了,系統(tǒng)運(yùn)行時(shí),多個(gè)產(chǎn)品功能在運(yùn)行時(shí)候共享一套組件實(shí)例,差別在于執(zhí)行組件的組合和配置不同。這一點(diǎn)是通過前文提到過的業(yè)務(wù)身份來實(shí)現(xiàn)的,不同業(yè)務(wù)身份關(guān)聯(lián)不同的組件組裝DSL及組件用戶配置,最終實(shí)現(xiàn)差異化功能組裝和執(zhí)行。
5. 總結(jié)
組裝式開發(fā)實(shí)踐有沒有解決最初的問題?組件分為大顆粒度的功能組件和小顆粒度的特性組件,不同顆粒度的組件都具備復(fù)用性和應(yīng)變能力,因此在展示功能搭建方面的效率有了顯著提升。內(nèi)部數(shù)據(jù)顯示,我們組的開發(fā)效率至少提升50%以上。
其次,每個(gè)組件單元都是經(jīng)過良好設(shè)計(jì)的邏輯單元,單個(gè)組件的規(guī)模都有所控制,因此代碼的復(fù)雜度得到明顯的降低。實(shí)踐結(jié)果顯示,研發(fā)同學(xué)自然開發(fā)的業(yè)務(wù)組件代碼圈復(fù)雜度不超過10。另外,通過信息功能的系列化編制,整個(gè)信息展示系統(tǒng)也有所收束,接口數(shù)由上百個(gè)減少到了個(gè)位數(shù),大大降低了接口的維護(hù)成本。
最后,以前研發(fā)人員“過程式”地翻譯業(yè)務(wù)需求,現(xiàn)在則需要考慮組件怎么設(shè)計(jì)。因?yàn)榧軜?gòu)本身提供了這種條件,并且也有這種要求,研發(fā)同學(xué)在為系統(tǒng)“添磚加瓦”的過程中需要考慮封裝和抽象問題,以集成到系統(tǒng)中。封裝和抽象是基本的軟件工程思維,這就讓“體力活動(dòng)”變成了“腦力活動(dòng)”,現(xiàn)在研發(fā)同學(xué)更像是一個(gè)軟件工程師,工作上也更有成就感。所以,總體上我們?nèi)〉昧瞬诲e(cuò)的效果。
每個(gè)領(lǐng)域都有各自領(lǐng)域的復(fù)雜性,比如有的領(lǐng)域問題在于計(jì)算復(fù)雜,有的領(lǐng)域在于模型的存儲(chǔ)和維護(hù)復(fù)雜。由于軟件開發(fā)是一個(gè)工程問題,我們不能僅僅考慮技術(shù)的復(fù)雜性,同時(shí)還要考慮業(yè)務(wù)及人員的問題。科學(xué)的思維告訴我們,解決問題要講究范式,當(dāng)一個(gè)范式不滿足的時(shí)候,需要有敢于突破的勇氣。本文主要介紹了我們?cè)谛畔⒄故緢?chǎng)景下,如何通過新的開發(fā)范式來解決我們所面臨的問題,