<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我,前端,不想卷技術(shù)了……卷下整潔架構(gòu)

          共 18382字,需瀏覽 37分鐘

           ·

          2023-10-13 18:54

          # 關(guān)注并星標(biāo)騰訊云開(kāi)發(fā)者

          # 每周3 | 談?wù)勎以隍v訊的架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)

          # 第6期 | 賴(lài)文輝:詳解整潔架構(gòu)在前端的應(yīng)用實(shí)踐




          對(duì)于每個(gè)軟件系統(tǒng),我們都可以通過(guò)行為和架構(gòu)兩個(gè)維度來(lái)體現(xiàn)它的實(shí)際價(jià)值。


          行為是指系統(tǒng)實(shí)現(xiàn)的功能特性,一般是比較緊急的,需要按時(shí)上線。架構(gòu)就是指系統(tǒng)架構(gòu),是重要的,但是并不總是特別緊急。因此導(dǎo)致我們常常忽視系統(tǒng)的架構(gòu)價(jià)值,使得系統(tǒng)越來(lái)越難于理解、修改,導(dǎo)致系統(tǒng)功能迭代成本逐步上升,生產(chǎn)力逐步下降。


          如果你遇到了這個(gè)問(wèn)題,就應(yīng)該要了解架構(gòu)了,思考當(dāng)前系統(tǒng)架構(gòu)是否合理。

          那什么是架構(gòu)呢?

          架構(gòu)的本質(zhì)就是控制系統(tǒng)復(fù)雜度,其終極目標(biāo)用最小的人力成本來(lái)滿足構(gòu)建和維護(hù)系統(tǒng)需求,同時(shí)最小化系統(tǒng)的總運(yùn)營(yíng)成本,確保系統(tǒng)不會(huì)因?yàn)樵黾庸δ芏鴮?dǎo)致開(kāi)發(fā)成本上升。

          那如何來(lái)判斷架構(gòu)的優(yōu)劣?

          一個(gè)軟件架構(gòu)的優(yōu)劣,可以用它滿足用戶需求所需要的成本來(lái)衡量。如果該成本很低,系統(tǒng)理解成本低、易于修改、方便維護(hù),能輕松部署,并且在系統(tǒng)的整個(gè)生命周期內(nèi)一直都能維持這樣的低成本,那么這個(gè)系統(tǒng)的設(shè)計(jì)就是優(yōu)良的。反之,如果該系統(tǒng)的每次發(fā)布都會(huì)提升下一次變更的成本,那么這個(gè)設(shè)計(jì)就是不好的。

          這樣的架構(gòu)可以大大節(jié)省軟件項(xiàng)目構(gòu)建與維護(hù)的人力成本。讓每次變更都短小簡(jiǎn)單,易于實(shí)施,并且避免缺陷,用最小的成本,最大程度地滿足功能性和靈活性的要求。

          那么良好的架構(gòu)是怎么實(shí)現(xiàn)的呢?

          良好的架構(gòu)實(shí)現(xiàn)方式,一般都是通過(guò)模塊化解耦、分層解耦,實(shí)現(xiàn)關(guān)注點(diǎn)分離,并通過(guò)一定的規(guī)則組織好不同模塊、不同分層的關(guān)系,實(shí)現(xiàn)高內(nèi)聚低耦合,從而控制系統(tǒng)的復(fù)雜度。

          整潔架構(gòu)就是其中一種經(jīng)典架構(gòu),讓你我不再為每次功能迭代而膽戰(zhàn)心驚,那么接下來(lái)我們將介紹何為『整潔架構(gòu)』,為什么說(shuō)它是一個(gè)好的軟件架構(gòu)。


          整潔架構(gòu)(Clean Architecture)是一種軟件架構(gòu)設(shè)計(jì)原則,由羅伯特·C·馬丁(Robert C. Martin)提出,旨在使軟件系統(tǒng)更加靈活、可維護(hù)和可測(cè)試。

          主要特點(diǎn)如下:
          ?? 與框架無(wú)關(guān):無(wú)論是前端代碼還是服務(wù)端代碼,其邏輯本身都應(yīng)該是獨(dú)立的,不應(yīng)該依賴(lài)于某一個(gè)第三方框架或工具庫(kù)。比如不依賴(lài) Vue.js、React 等框架。
          ?? 可測(cè)試性:代碼中的業(yè)務(wù)邏輯可以在不依賴(lài) UI、數(shù)據(jù)庫(kù)、服務(wù)器的情況下進(jìn)行測(cè)試。
          ?? 和 UI 無(wú)關(guān):代碼中的業(yè)務(wù)邏輯不應(yīng)該和 UI 做強(qiáng)綁定。比如把一個(gè) web 應(yīng)用切換成桌面應(yīng)用,業(yè)務(wù)邏輯不應(yīng)該受到影響。
          ?? 和數(shù)據(jù)庫(kù)無(wú)關(guān):無(wú)論數(shù)據(jù)庫(kù)用的是 MySQL 還是 MongoDB,無(wú)論其怎么變,都不該影響到業(yè)務(wù)邏輯。
          ?? 和外部服務(wù)無(wú)關(guān):將業(yè)務(wù)邏輯置于系統(tǒng)的核心,無(wú)論外部服務(wù)怎么變,都不影響到使用該服務(wù)的業(yè)務(wù)邏輯。

          一個(gè)優(yōu)秀的軟件架構(gòu)師應(yīng)該致力于最大化架構(gòu)組件的可選項(xiàng)數(shù)量,可以低成本更換框架、數(shù)據(jù)庫(kù)、外部服務(wù)等,接下來(lái)我們具體看下整潔架構(gòu)的設(shè)計(jì)思想。


             3.1 整潔架構(gòu)的設(shè)計(jì)思想


          整潔架構(gòu)除了以下至少四層架構(gòu)外,在層與層之間還有一個(gè)非常明確的依賴(lài)關(guān)系,外層的邏輯依賴(lài)內(nèi)層的邏輯 (圖中黑色箭頭指向), 但是內(nèi)層的代碼不可以依賴(lài)外層。

          ?? 實(shí)體層:業(yè)務(wù)實(shí)體這一層中封裝的是整個(gè)系統(tǒng)的關(guān)鍵業(yè)務(wù)邏輯。這些實(shí)體既可以是帶方法的類(lèi),也可以是帶有一堆函數(shù)的結(jié)構(gòu)體。但它們必須是高度抽象的,封裝了該應(yīng)用中最通用、最高層的業(yè)務(wù)邏輯,只可以隨著核心業(yè)務(wù)規(guī)則變化,不可以隨著外層組件的變化而變化。例如,一個(gè)針對(duì)頁(yè)面導(dǎo)航方式或者安全問(wèn)題的修改不應(yīng)該觸及這些對(duì)象,一個(gè)針對(duì)應(yīng)用在運(yùn)行時(shí)的行為所做的變更也不應(yīng)該影響業(yè)務(wù)實(shí)體。該層一般采用 DDD 的理念進(jìn)行抽象、封裝。
          ?? 用例層:軟件的用例層中通常包含的是特定應(yīng)用場(chǎng)景下的業(yè)務(wù)邏輯,這里面封裝并實(shí)現(xiàn)了整個(gè)系統(tǒng)的所有用例。該層控制所有流向和流出實(shí)體層的數(shù)據(jù)流,并使用核心的實(shí)體及其業(yè)務(wù)規(guī)則來(lái)完成業(yè)務(wù)需求。此層的變更不會(huì)影響實(shí)體層,更外層的變更,比如開(kāi)發(fā)框架、數(shù)據(jù)庫(kù)、UI 等變化,也不會(huì)影響此層。
          ?? 適配器層:軟件的接口適配器層中通常是一組數(shù)據(jù)轉(zhuǎn)換器,它們負(fù)責(zé)將數(shù)據(jù)從對(duì)用例和業(yè)務(wù)實(shí)體而言最方便操作的格式,轉(zhuǎn)化成外部系統(tǒng)(譬如數(shù)據(jù)庫(kù)以及 Web)最方便操作的格式。反之,來(lái)自于外部服務(wù)的數(shù)據(jù)也會(huì)在這層轉(zhuǎn)換為內(nèi)層需要的結(jié)構(gòu),一般用于 UI 和接口的適配操作。
          ?? 框架和驅(qū)動(dòng)層:由最外層由各種框架和工具組成,比如 Web 框架、數(shù)據(jù)庫(kù)訪問(wèn)工具等。通常在這層不需要寫(xiě)太多代碼,大多是一些用來(lái)跟內(nèi)層通信的膠水代碼。

          了解了整潔結(jié)構(gòu)的設(shè)計(jì)思想,那么它和其他經(jīng)典的架構(gòu)有什么區(qū)別呢?

             3.2 整潔架構(gòu)和其他架構(gòu)對(duì)比


          我們先了解下最常見(jiàn)的六邊形架構(gòu)和 DDD 分層架構(gòu)。

             3.2.1 六邊形架構(gòu)


          本圖片來(lái)源《DDD 實(shí)戰(zhàn)課》

          其核心理念是:應(yīng)用是通過(guò)端口與外部進(jìn)行交互的 。也就是說(shuō),在上圖的六邊形架構(gòu)中,紅圈內(nèi)的核心業(yè)務(wù)邏輯(應(yīng)用程序和領(lǐng)域模型)與外部資源(包括 APP、Web 應(yīng)用以及數(shù)據(jù)庫(kù)資源等)完全隔離,僅通過(guò)適配器進(jìn)行交互。

          它解決了業(yè)務(wù)邏輯與用戶界面的代碼交錯(cuò)問(wèn)題,很好地實(shí)現(xiàn)了前后端分離。

             3.2.2 DDD 分層架構(gòu)



          ?? 核心理念:領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。領(lǐng)域模型準(zhǔn)確反映了業(yè)務(wù)語(yǔ)言,而傳統(tǒng)數(shù)據(jù)對(duì)象除了簡(jiǎn)單 setter/getter 方法外,沒(méi)有任何業(yè)務(wù)方法。
          ?? 用戶界面層:負(fù)責(zé)向用戶顯示信息和解釋用戶指令,也是我們常說(shuō)的 UI 層。
          ?? 應(yīng)用層:負(fù)責(zé)使用領(lǐng)域?qū)犹峁┑哪芰M(jìn)行業(yè)務(wù)流程編排,實(shí)現(xiàn)對(duì)應(yīng)功能。
          ?? 領(lǐng)域?qū)樱?/strong>領(lǐng)域模型/領(lǐng)域服務(wù)/和防腐層的接口定義,為應(yīng)用層提供能力。
          ?? 基礎(chǔ)設(shè)施層:為其它各層提供通用的技術(shù)和基礎(chǔ)服務(wù),如數(shù)據(jù)持久化、消息中間件等。

          具體實(shí)踐:一般第一步為自上而下結(jié)構(gòu)化分解,如圖:

          圖片來(lái)自于張建飛《基于 DDD 的應(yīng)用架構(gòu)設(shè)計(jì)和實(shí)踐》分享

          第二步為自下而上的領(lǐng)域建模,從而完成功能的實(shí)現(xiàn),如圖:

          圖片來(lái)自于張建飛《基于 DDD 的應(yīng)用架構(gòu)設(shè)計(jì)和實(shí)踐》分享

          總結(jié)起來(lái)就是先把業(yè)務(wù)邏輯按結(jié)構(gòu)化拆解,拆解為不同的步驟,然后調(diào)用領(lǐng)域?qū)拥哪芰M(jìn)行邏輯編排實(shí)現(xiàn)對(duì)應(yīng)功能。

          圖片來(lái)自于張建飛《基于 DDD 的應(yīng)用架構(gòu)設(shè)計(jì)和實(shí)踐》分享

             3.2.3 對(duì)比分析


          本圖片來(lái)源《DDD 實(shí)戰(zhàn)課》

          可以看到他們的共同點(diǎn)是:整潔架構(gòu)、DDD 分層架構(gòu)、六邊形架構(gòu)都是以領(lǐng)域模型為核心,實(shí)行分層架構(gòu),內(nèi)部核心業(yè)務(wù)邏輯與外部應(yīng)用、資源隔離并解耦。

          事實(shí)上整潔架構(gòu)恰恰是最后的集大成者,集合了 DDD 領(lǐng)域驅(qū)動(dòng)的思想 + 分層架構(gòu)的落地,具體可以如下架構(gòu)發(fā)展歷史圖:

          圖片來(lái)源于《領(lǐng)域驅(qū)動(dòng)架構(gòu)及其演變史(EBI、DDD、端口適配、洋蔥、整潔)》

          了解了整潔架構(gòu)的優(yōu)勢(shì),接下來(lái)我們重點(diǎn)介紹如何應(yīng)用整潔架構(gòu)。


          首先會(huì)借鑒 DDD 的思想進(jìn)行業(yè)務(wù)分析、建模,形成業(yè)務(wù)的領(lǐng)域模型。

             4.1 戰(zhàn)略階段:分析業(yè)務(wù),建立領(lǐng)域模型

           

             4.1.1 分析業(yè)務(wù)流程


          DDD 中一般采用用例分析、事件風(fēng)暴、四色建模等方法,盡可能全面不遺漏的分解業(yè)務(wù)領(lǐng)域,梳理業(yè)務(wù)過(guò)程中的用戶操作、事件以及依賴(lài)關(guān)系,再根據(jù)這些要素進(jìn)一步梳理出領(lǐng)域?qū)ο蠹八麄冎g的關(guān)系。

          DDD 里說(shuō)的這些業(yè)務(wù)分析方法在構(gòu)建大型項(xiàng)目時(shí)非常有用,但在日常需求中會(huì)顯得有點(diǎn)重。于是我們結(jié)合用例分析與事件風(fēng)暴,沉淀出一套適合日常需求分析的方法論,內(nèi)部稱(chēng)之為雙軸泳道分析法。

          下面以電商購(gòu)物需求為例,介紹一下實(shí)施步驟:

          • 識(shí)別業(yè)務(wù)參與者

          業(yè)務(wù)參與者,是指在業(yè)務(wù)流程中發(fā)起動(dòng)作,觸發(fā)狀態(tài)改變的個(gè)體。業(yè)務(wù)參與者可以某個(gè)角色、某個(gè)系統(tǒng)、或者某個(gè)綜合系統(tǒng)。

          例如電商網(wǎng)站購(gòu)物場(chǎng)景中,用戶選品、下單、支付,電商網(wǎng)頁(yè)負(fù)責(zé)呈現(xiàn)商品信息,提示用戶操作結(jié)果,電商后臺(tái)負(fù)責(zé)生成訂單、記賬。用戶(角色)、電商網(wǎng)頁(yè)(系統(tǒng))、電商后臺(tái)(系統(tǒng))都是業(yè)務(wù)參與者,其概念類(lèi)似用例分析里的 actor。

          • 分析參與者在不同階段發(fā)生的動(dòng)作及觸發(fā)的狀態(tài)

          動(dòng)作是指參與者發(fā)起的某個(gè)命令,比如創(chuàng)建訂單、抽獎(jiǎng)等,而狀態(tài)是指動(dòng)作發(fā)生后引起的狀態(tài)變更,比如訂單已創(chuàng)建,訂單創(chuàng)建失敗等,其概念類(lèi)似事件風(fēng)暴的命令和事件。

          對(duì)電商購(gòu)物場(chǎng)景分析結(jié)果如下:


          • 使用雙軸泳道圖描述業(yè)務(wù)流程

          泳道的橫軸是業(yè)務(wù)參與者,縱軸是業(yè)務(wù)流程的不同階段,通過(guò)雙軸泳道圖描述出各個(gè)參與者在不同階段發(fā)生的動(dòng)作、觸發(fā)的狀態(tài)。

          在業(yè)務(wù)流程中,有些屬于前端交互,有些屬于動(dòng)作,有些屬于狀態(tài)。動(dòng)作和狀態(tài)會(huì)用于后續(xù)的領(lǐng)域?qū)ο筇崛。覀冃枰獙⑺麄儤?biāo)注出來(lái)以便識(shí)別。


             4.1.2 提取領(lǐng)域?qū)ο?/span>


          經(jīng)過(guò)上述分析后,業(yè)務(wù)流程已經(jīng)非常清晰。第二步,就是要根據(jù)分析過(guò)程中產(chǎn)生的動(dòng)作和狀態(tài),提取出產(chǎn)生這些行為的對(duì)象,進(jìn)一步識(shí)別出實(shí)體、值對(duì)象、聚合根。

          • 實(shí)體

          業(yè)務(wù)形態(tài)上是包含業(yè)務(wù)規(guī)則的集合,具有唯一標(biāo)識(shí)字段(id)。代碼上通常以類(lèi)/對(duì)象的形式存在,包含屬性和方法。

          • 值對(duì)象

          業(yè)務(wù)形態(tài)上是干個(gè)屬性的集合,只有數(shù)據(jù)初始化操作和有限的不涉及修改數(shù)據(jù)的行為,不具有唯一標(biāo)識(shí)(id)。代碼上以類(lèi)/對(duì)象的形式被實(shí)體引用。

          • 聚合根

          聚合根是一個(gè)特殊實(shí)體,具備唯一標(biāo)識(shí)(id),有獨(dú)立的生命周期。聚合根是聚合的唯一入口點(diǎn),負(fù)責(zé)協(xié)調(diào)實(shí)體以及值對(duì)象完成業(yè)務(wù)邏輯。


          在以上例子中,將動(dòng)作、狀態(tài)歸類(lèi)后,可劃分為用戶、購(gòu)物車(chē)、商品、訂單、消費(fèi)流水五個(gè)實(shí)體,收貨地址可作為值對(duì)象。

             4.1.3 劃分界限,識(shí)別模塊


          根據(jù)上下文語(yǔ)義,尋找聚合根、劃定界限,將實(shí)體進(jìn)一步組合成聚合,一個(gè)聚合對(duì)應(yīng)一個(gè)模塊。

          在本例子中:
          ?? 用戶實(shí)體和購(gòu)物車(chē)實(shí)體與用戶強(qiáng)相關(guān),分別管理用戶基本信息和購(gòu)物車(chē)信息,可以用戶實(shí)體為聚合根,共同構(gòu)成用戶模塊;
          ?? 訂單實(shí)體、消費(fèi)流水實(shí)體與下單強(qiáng)相關(guān),可以訂單實(shí)體為聚合更,共同構(gòu)成下單模塊;
          ?? 商品模塊較獨(dú)立,可單獨(dú)構(gòu)成商品模塊。


             4.2 戰(zhàn)術(shù)階段:工程落地,搭建分層架構(gòu)


          通過(guò)戰(zhàn)略階段建立對(duì)應(yīng)的領(lǐng)域模型后,在對(duì)應(yīng)的工程實(shí)現(xiàn)上,應(yīng)如何劃分層呢?

             4.2.1 分層實(shí)現(xiàn)


          以前端工程為例,常規(guī)的 mvvm 前端工程的分層架構(gòu)如下圖,會(huì)在 store 層直接調(diào)用 api 層發(fā)起請(qǐng)求,然后再通過(guò) mvvm 更新視圖。


          ? 容易出現(xiàn)的問(wèn)題:
          ?? 業(yè)務(wù)邏輯和 UI 強(qiáng)耦合,如果要更換 UI,改動(dòng)成本大
          ?? 往往 store 層依賴(lài)框架的實(shí)現(xiàn),業(yè)務(wù)邏輯易和框架強(qiáng)耦合,如果切換框架或升級(jí)框架,重構(gòu)成本大。比如升級(jí) Vue 到 Vue2,或 Vue 切換到 React。

          根據(jù)整潔架構(gòu)思想,設(shè)計(jì)后的架構(gòu)如下:


          在原有基礎(chǔ)上拆分了實(shí)體層和用例層,并在用例層內(nèi)通過(guò)端口的方式定義了依賴(lài)的端口方法,用來(lái)解耦框架和第三方服務(wù)的依賴(lài)。

          目前很多前端實(shí)踐里實(shí)體層是比較薄,有的只有類(lèi)型定義,把邏輯封裝到了用例層,但用例層的邏輯不適合更細(xì)粒度的復(fù)用,導(dǎo)致復(fù)用比較麻煩,這也不符合整潔架構(gòu)對(duì)實(shí)體層的定義,整潔架構(gòu)中期望實(shí)體這一層中封裝的是整個(gè)系統(tǒng)的關(guān)鍵業(yè)務(wù)邏輯。

          個(gè)人覺(jué)得應(yīng)該視具體情況而定,邏輯簡(jiǎn)單的前端頁(yè)面,用例層和實(shí)體層都比較簡(jiǎn)單,可以使用貧血的實(shí)體層;如果邏輯復(fù)雜的一定要把邏輯抽取到實(shí)體層,用例層使用實(shí)體層提供的能力進(jìn)行功能串聯(lián),方便復(fù)用及后續(xù)維護(hù)。比如我們這邊的下載邏輯就比較重,需要把相關(guān)邏輯封裝在實(shí)體層。

          比如購(gòu)買(mǎi)這個(gè)用例里,需要判斷是否登錄,判斷是否有庫(kù)存,創(chuàng)建訂單,支付等流程,每個(gè)流程應(yīng)該使用的都是實(shí)體的能力,具體的邏輯封裝在實(shí)體里,用例層核心是實(shí)現(xiàn)流程的串聯(lián)。


          下面我們看下這樣分層后的代碼示例及數(shù)據(jù)流向是怎么樣的?

          以最常見(jiàn)的電商商品展示為示例,用戶登錄后查看商品詳情,根據(jù)用戶所在地展示商品庫(kù)存。整個(gè)流程是這樣的:進(jìn)到頁(yè)面 -> 檢查登錄態(tài) -> 發(fā)起請(qǐng)求 -> 組裝數(shù)據(jù) -> 頁(yè)面展示。

          實(shí)體層

          關(guān)于實(shí)體層的設(shè)計(jì)有兩個(gè)要點(diǎn):

          • 使用充血模型來(lái)描述實(shí)體

          充血模型指的是,實(shí)體內(nèi)包含數(shù)據(jù)及常用行為,符合面向?qū)ο蟮姆庋b性,是典型的面向?qū)ο缶幊田L(fēng)格。反之貧血模型指的是實(shí)體只包含數(shù)據(jù),行為不封裝在實(shí)體內(nèi),是一種面向過(guò)程的設(shè)計(jì)。

          • 結(jié)合具體場(chǎng)景,允許部分依賴(lài)實(shí)現(xiàn)

          眾所周知,DDD 中非常強(qiáng)調(diào)領(lǐng)域?qū)拥慕怦睿碚撋项I(lǐng)域?qū)討?yīng)該依賴(lài)抽象接口,不應(yīng)該依賴(lài)具體實(shí)現(xiàn)。這種徹底解耦的方式的確能解決后續(xù)依賴(lài)項(xiàng)變更的問(wèn)題,但在實(shí)際開(kāi)發(fā)中,很多依賴(lài)項(xiàng)是我們可控的,可預(yù)知后續(xù)是不會(huì)變更的,這種情況下如果對(duì)所有依賴(lài)都要抽象出接口,那將會(huì)大大增加我們的工作量。因此我們提倡結(jié)合具體的場(chǎng)景,只對(duì)后續(xù)可能變化的依賴(lài)進(jìn)行防腐,對(duì)于后續(xù)不會(huì)變化的依賴(lài)我們?cè)试S直接依賴(lài)實(shí)現(xiàn)。

          本例子中,可拆分成用戶、商品兩個(gè)實(shí)體。

          用戶實(shí)體,主要提供用戶常用的登錄、登出、查詢用戶所在城市等方法。用戶的登錄態(tài)一般依賴(lài) cookie,瀏覽器的 cookie 接口不大可能出現(xiàn)破壞性變更,因此在用戶實(shí)體中,我們?cè)试S直接依賴(lài) cookie 操作庫(kù),而查詢用戶城市依賴(lài)于用戶服務(wù)提供接口,為防止后端接口變更,需要對(duì)用戶服務(wù)進(jìn)行防腐。

               
               
          // 用戶實(shí)體 ./shared/domain/entities/user.ts
          import cookie from 'cookie';
          export interface IUserService { getCity(id: string): Promise<City>;}
          export class User { // 用戶Id public id: string; // 用戶服務(wù) private userService: IUserService; constructor(id: string, name: string, userService: IUserService) {} // 檢查用戶是否登錄 public isLogin(): boolean { if (cookie.get('openid') && cookie.get('access_token')) { return true; } return false; } // 登錄 public login(): Promise<void> { if (!this.isLogin()) { goToURL('https://www.xxx.com/login'); } } // 退出登錄 public logout(): Promise<void> { cookie.remove('openid'); cookie.remove('access_token'); goToURL('https://www.xxx.com/login'); } // 獲取用戶所在城市 public getCity(): Promise<City> { return this.userService.getCity(this.id); }}

          商品實(shí)體:提供查詢商品詳情方法,商品實(shí)體依賴(lài)后端的商品服務(wù),為防止后端接口變更,需要進(jìn)行防腐。

               
               
          // 商品實(shí)體 ./shared/domain/entities/product.ts
          export interface IProductService { getBaseInfoById(id: string): Promise<ProductBaseInfo>; getStockInfoByIdAndCity(id: string, city: City): Promise<ProductStockInfo>;}
          export class Product { // 商品Id public id: string; // 用戶服務(wù) private productService: ProductService; constructor(id: string, name: string; productService: IProductService) {} // 獲取商品詳情 public async getDetail() { // 獲取商品基本信息和庫(kù)存信息 const baseInfo = await this.productService.getBaseInfoById(this.id); const stockInfo = await this.productService.getStockInfoById(this.id, city); // 組合詳情數(shù)據(jù) const detail = { id: this.id, name: baseinfo.name, images: baseinfo.name, stockNum: stockInfo.num, }; return detail; }
          // 根據(jù)地區(qū)獲取庫(kù)存信息 public addToCart(num:number) { return this.productService.getStockInfoById(this.id, city); }};

          用例層

          用例層主要充當(dāng)“協(xié)調(diào)者”的角色,組合各個(gè)實(shí)體的操作,實(shí)現(xiàn)業(yè)務(wù)邏輯,這層的邏輯代碼會(huì)“面向過(guò)程”。

          本例子中,需要結(jié)合用戶實(shí)體和商品實(shí)體,實(shí)現(xiàn)根據(jù)用戶所在地獲取商品庫(kù)存信息。

               
               
          // 獲取商品詳情用例 ./shared/domain/usercases/get-product-detail.ts
          import { User } from './shared/domain/entities/user.ts';import { Product } from './shared/domain/entities/product.ts';// 用戶服務(wù)、產(chǎn)品服務(wù)的具體實(shí)現(xiàn),見(jiàn)適配器層import { UserService } from './server/services/user-service.ts';import { ProductService } from './server/services/product-service.ts';
          export async function getProductDetail(userId: string, productId: string) { // 示例化用戶實(shí)體和商品實(shí)體,省略部分代碼 const user = new User(userId, UserService); const product = new Product(productId, ProductService); // 獲取用戶所在城市 const city: City = await user.getCity(); // 獲取商品基本信息 const productBaseInfo = await product.getBaseInfo(); // 根據(jù)城市獲取商品庫(kù)存 const productStockInfo = await product.getStockInfo(city); return { baseInfo: productBaseInfo, stockInfo: productStockInfo, };}

          適配器層

          包含 UI 框架的代碼,及 store 相關(guān)的代碼,如 Vuex,通過(guò)更新 Vuex 的數(shù)據(jù)更新視圖。

          調(diào)用第三方服務(wù),并將其轉(zhuǎn)化成用例層的端口格式。

               
               
          // 用戶服務(wù)具體實(shí)現(xiàn) ./server/services/user-service.tsimport { IUserService } from './shared/domain/entities/user.ts';
          class UserService implements IUserService { getCity(userId: string): Promise<City> { // 通過(guò)后臺(tái)接口獲取用戶所在城市 const resp = get('https://api.xxx.com/queryUserCity', { userId }); if (resp.ret !== 0) { throw new Error('查詢用戶所在城市失敗'); } return resp.data.city as City; } }

               
               
          // 商品服務(wù)具體實(shí)現(xiàn) ./server/services/product-service.tsimport { IProductService } from './shared/domain/entities/product.ts';
          class ProductService implements IProductService { getBaseInfoById(id: string): Promise<ProductBaseInfo> { // 調(diào)用后臺(tái)商品服務(wù)接口,省略具體實(shí)現(xiàn) } getStockInfoByIdAndCity(id: string, city: City): Promise<ProductStockInfo> { // 調(diào)用后臺(tái)商品服務(wù)接口,省略具體實(shí)現(xiàn) }}

               
               
          // 商品詳情頁(yè) store ./client/store/product-store.tsimport { getProductDetial } from './shared/domain/usercases/get-product-detail.ts'
          export default new Vuex.Store({ state: { productDetail: ProductDetail, }, mutations: { async getProductDetail(state) { // 用例已包含具體業(yè)務(wù)邏輯,這里直接調(diào)用用例方法 state.productDetail = getProductDetial(userId, productId); }, },}

               
               
          // 商品詳情頁(yè) ./client/pages/product-detail.ts
          import { defineComponent, ref, onMounted } from 'vue';
          export defineComponent({ name: 'ProudctDetailPage', setup() { onMounted(() => { setLoading(true); await store.getProductDetail(); setLoading(false); });
          return () => ( <div> <p> {{ store.productDetail.baseInfo }}</p> <p> {{ store.productDetail.stockInfo }}</p> </div> ); },});

          框架和驅(qū)動(dòng)層

          這里是用到的第三方服務(wù)、框架,如 Vue、Svelte 等。

          如以下偽代碼:

               
               
          import vue from 'Vue'
          vue.render(App);

          整體數(shù)據(jù)流向圖如下


             4.3.3 架構(gòu)及目錄示例


          基于此,我們采用整潔架構(gòu)后目錄結(jié)構(gòu)如下,如下圖所示:


          單獨(dú)抽離領(lǐng)域?qū)?/span>(包括實(shí)體層、用例層、防腐層)目錄。

          將 utils 工具進(jìn)行拆解,無(wú)"副作用" 的 utils 移動(dòng)至 shared 目錄下,界面相關(guān)如 jump 存放到對(duì)應(yīng)的 client 的 utils 下。


          整潔架構(gòu)不是"銀彈",在實(shí)踐上存在以下優(yōu)缺點(diǎn):

          ?優(yōu)點(diǎn):
          ?? 業(yè)務(wù)領(lǐng)域?qū)舆壿嫺蓛簦瑯I(yè)務(wù)邏輯可適配到不同的 UI 框架、對(duì)于同構(gòu)的 SSR 服務(wù)也可以公用同一套業(yè)務(wù)邏輯。
          ?? 職責(zé)邊界更為明確,內(nèi)層的業(yè)務(wù)邏輯可覆蓋單元測(cè)試,UI 層則依賴(lài) e2e 端對(duì)端測(cè)試覆蓋。

          ?缺點(diǎn):
          ?? 構(gòu)建邊界的成本較大,由于核心業(yè)務(wù)層無(wú)法直接引用外層 UI 的 store 和 API,需額外聲明端口依賴(lài),開(kāi)發(fā)效率變低。

          所以說(shuō)沒(méi)有最好的架構(gòu),只有最適合自己團(tuán)隊(duì)和業(yè)務(wù)的架構(gòu)。對(duì)于是否使用整潔架構(gòu),我們應(yīng)考量項(xiàng)目復(fù)雜度、項(xiàng)目的生命周期,綜合來(lái)衡量。對(duì)于業(yè)務(wù)邏輯簡(jiǎn)單、業(yè)務(wù)生命周期較短的項(xiàng)目,直接使用照搬整潔架構(gòu),會(huì)導(dǎo)致開(kāi)發(fā)效率地變低;但是對(duì)于需要長(zhǎng)期維護(hù)的復(fù)雜項(xiàng)目,如騰訊文檔、VS Code 內(nèi)核、低代碼引擎等,就非常適合整潔架構(gòu),能大大降低系統(tǒng)的維護(hù)成本,并在前端技術(shù)快速變遷的情況下,非常方便后續(xù)對(duì) UI 庫(kù)、框架的升級(jí)改造。

          期望大家在日常工作中除了關(guān)注系統(tǒng)的行為,多一些對(duì)架構(gòu)的關(guān)注和思考,以提升系統(tǒng)的整潔性,讓每次變更都短小簡(jiǎn)單,易于實(shí)施,并且避免缺陷,用最小的成本,最大程度地滿足功能性和靈活性的要求。

          最后感謝您的閱讀,如果本文給你帶來(lái)了一些啟發(fā),歡迎轉(zhuǎn)發(fā)分享~

          -End-
          原創(chuàng)作者|賴(lài)文輝

          ??前端業(yè)務(wù)越來(lái)越復(fù)雜,你還有哪些降低業(yè)務(wù)復(fù)雜度的開(kāi)發(fā)方法?歡迎留言。我們將挑選一則最有意義的評(píng)論,為其留言者送出騰訊定制-手機(jī)支架1個(gè)(見(jiàn)下圖)。10月18日中午12點(diǎn)開(kāi)獎(jiǎng)。



          ????歡迎加入騰訊云開(kāi)發(fā)者社群,社群專(zhuān)享券、大咖交流圈、第一手活動(dòng)通知、限量鵝廠周邊等你來(lái)~


          (長(zhǎng)按圖片立即掃碼)





          關(guān)注并星標(biāo)騰訊云開(kāi)發(fā)者

          第一時(shí)間看鵝廠架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)



          瀏覽 2158
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩三级片电影网站 | 国产男人天堂 | 六月丁香九月婷婷 | 狠狠搞大香蕉 | 亚州操逼|