DDD 能給前端帶來了什么?
大廠技術(shù)??高級前端??Node進(jìn)階
點(diǎn)擊上方?程序員成長指北,關(guān)注公眾號
回復(fù)1,加入高級Node交流群
大家好,我是考拉??。為什么需要DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))?
????在回答這個(gè)問題之前,我們先看下大部分軟件都會(huì)經(jīng)歷的發(fā)展過程:頻繁的變更帶來軟件質(zhì)量的下降

而這又是軟件發(fā)展的規(guī)律導(dǎo)致的:
軟件是對真實(shí)世界的模擬,真實(shí)世界往往十分復(fù)雜
人在認(rèn)識真實(shí)世界的時(shí)候總有一個(gè)從簡單到復(fù)雜的過程
因此需求的變更是一種必然,并且總是由簡單到復(fù)雜演變
軟件初期的業(yè)務(wù)邏輯非常清晰明了,慢慢變得越來越復(fù)雜
????可以看到需求的不斷變更和迭代導(dǎo)致了項(xiàng)目變得越來越復(fù)雜,那么問題來了,項(xiàng)目復(fù)雜性提高的根本原因是需求變更引起的嗎?
????根本原因其實(shí)是因?yàn)樵谛枨笞兏^程中沒有及時(shí)的進(jìn)行解耦和擴(kuò)展。

????那么在需求變更的過程中如何進(jìn)行解耦和擴(kuò)展呢?DDD發(fā)揮作用的時(shí)候來了。
什么是DDD
DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))的概念見維基百科:
https://zh.wikipedia.org/wiki/%E9%A0%98%E5%9F%9F%E9%A9%85%E5%8B%95%E8%A8%AD%E8%A8%88
????可以看到領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(domin-driven design,DDD)不同于傳統(tǒng)的針對數(shù)據(jù)庫表結(jié)構(gòu)的設(shè)計(jì),領(lǐng)域模型驅(qū)動(dòng)設(shè)計(jì)自然是以提煉和轉(zhuǎn)換業(yè)務(wù)需求中的領(lǐng)域知識為設(shè)計(jì)的起點(diǎn)。在提煉領(lǐng)域知識時(shí),沒有數(shù)據(jù)庫的概念,亦沒有服務(wù)的概念,一切圍繞著業(yè)務(wù)需求而來,即:
現(xiàn)實(shí)世界有什么事物 ?-> ?模型中就有什么對象
現(xiàn)實(shí)世界有什么行為 ?-> ?模型中就有什么方法
現(xiàn)實(shí)世界有什么關(guān)系 ?-> 模型中就有什么關(guān)聯(lián)
????在DDD中按照什么樣的原則進(jìn)行領(lǐng)域建模呢?
????單一職責(zé)原則(Single responsibility principle)即SRP:軟件系統(tǒng)中每個(gè)元素只完成自己職責(zé)內(nèi)的事,將其他的事交給別人去做。
????上面這句話有沒有什么哪里不清晰的?有,那就是“職責(zé)”兩個(gè)字。職責(zé)該怎么理解?如何限定該元素的職責(zé)范圍呢?這就引出了“限界上下文”的概念。
??? Eric Evans 用細(xì)胞來形容限界上下文,因?yàn)椤凹?xì)胞之所以能夠存在,是因?yàn)榧?xì)胞膜限定了什么在細(xì)胞內(nèi),什么在細(xì)胞外,并且確定了什么物質(zhì)可以通過細(xì)胞膜。”這里,細(xì)胞代表上下文,而細(xì)胞膜代表了包裹上下文的邊界。
????我們需要根據(jù)業(yè)務(wù)相關(guān)性、耦合的強(qiáng)弱程度、分離的關(guān)注點(diǎn)對這些活動(dòng)進(jìn)行歸類,找到不同類別之間存在的邊界,這就是限界上下文的含義。上下文(Context)是業(yè)務(wù)目標(biāo),限界(Bounded)則是保護(hù)和隔離上下文的邊界,避免業(yè)務(wù)目標(biāo)的不單一而帶來的混亂與概念的不一致。
如何DDD
DDD的大體流程如下:
建立統(tǒng)一語言
????統(tǒng)一語言是提煉領(lǐng)域知識的產(chǎn)出物,獲得統(tǒng)一語言就是需求分析的過程,也是團(tuán)隊(duì)中各個(gè)角色就系統(tǒng)目標(biāo)、范圍與具體功能達(dá)成一致的過程。
????使用統(tǒng)一語言可以幫助我們將參與討論的客戶、領(lǐng)域?qū)<遗c開發(fā)團(tuán)隊(duì)拉到同一個(gè)維度空間進(jìn)行討論,若沒有達(dá)成這種一致性,那就是雞同鴨講,毫無溝通效率,相反還可能造成誤解。因此,在溝通需求時(shí),團(tuán)隊(duì)中的每個(gè)人都應(yīng)使用統(tǒng)一語言進(jìn)行交流。
????一旦確定了統(tǒng)一語言,無論是與領(lǐng)域?qū)<业挠懻摚€是最終的實(shí)現(xiàn)代碼,都可以通過使用相同的術(shù)語,清晰準(zhǔn)確地定義領(lǐng)域知識。重要的是,當(dāng)我們建立了符合整個(gè)團(tuán)隊(duì)皆認(rèn)同的一套統(tǒng)一語言后,就可以在此基礎(chǔ)上尋找正確的領(lǐng)域概念,為建立領(lǐng)域模型提供重要參考。
????舉個(gè)例子,不同玩家對于英雄聯(lián)盟(league of legends)的稱呼不盡相同;國外玩家一般叫“League”,國內(nèi)玩家有的稱呼“擼啊擼”,有的稱呼“LOL”等等。那么如果要開發(fā)相關(guān)產(chǎn)品,開發(fā)人員和客戶首先需要統(tǒng)一對“英雄聯(lián)盟”的語言模型。
事件風(fēng)暴(Event Storming)
????事件風(fēng)暴會(huì)議是一種基于工作坊的實(shí)踐方法,它可以快速發(fā)現(xiàn)業(yè)務(wù)領(lǐng)域中正在發(fā)生的事件,指導(dǎo)領(lǐng)域建模及程序開發(fā)。它是Alberto Brandolini發(fā)明的一種領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)踐方法,被廣泛應(yīng)用于業(yè)務(wù)流程建模和需求工程,基本思想是將軟件開發(fā)人員和領(lǐng)域?qū)<揖奂谝黄穑嗷W(xué)習(xí),類似頭腦風(fēng)暴。
????會(huì)議一般以探討領(lǐng)域事件開始,從前向后梳理,以確保所有的領(lǐng)域事件都能被覆蓋。
????什么是領(lǐng)域事件呢?
????領(lǐng)域事件是領(lǐng)域模型中非常重要的一部分,用來表示領(lǐng)域中發(fā)生的事件。一個(gè)領(lǐng)域事件將導(dǎo)致進(jìn)一步的業(yè)務(wù)操作,在實(shí)現(xiàn)業(yè)務(wù)解耦的同時(shí),還有助于形成完整的業(yè)務(wù)閉環(huán)。
領(lǐng)域事件可以是業(yè)務(wù)流程的一個(gè)步驟,比如投保業(yè)務(wù)繳費(fèi)完成后,觸發(fā)投保單轉(zhuǎn)保單的動(dòng)作;也可能是定時(shí)批處理過程中發(fā)生的事件,比如批處理生成季繳保費(fèi)通知單,觸發(fā)發(fā)送繳費(fèi)郵件通知操作;或者一個(gè)事件發(fā)生后觸發(fā)的后續(xù)動(dòng)作,比如密碼連續(xù)輸錯(cuò)三次,觸發(fā)鎖定賬戶的動(dòng)作。
進(jìn)行領(lǐng)域建模,將各個(gè)模型分配到各個(gè)限界上下文中,構(gòu)建上下文地圖。
????領(lǐng)域建模時(shí),我們會(huì)根據(jù)場景分析過程中產(chǎn)生的領(lǐng)域?qū)ο螅热缑睢⑹录戎g關(guān)系,找出產(chǎn)生命令的實(shí)體,分析實(shí)體之間的依賴關(guān)系組成聚合,為聚合劃定限界上下文,建立領(lǐng)域模型以及模型之間的依賴。
????上面我們大體了解了DDD的作用,概念和一般的流程,雖然前端和后端的DDD不盡相同,但是我們?nèi)匀豢梢詫⑦@種思想應(yīng)用于我們的項(xiàng)目中。
DDD能給前端項(xiàng)目帶來什么
通過領(lǐng)域模型 (feature)組織項(xiàng)目結(jié)構(gòu),降低耦合度
????很多通過react腳手架生成的項(xiàng)目組織結(jié)構(gòu)是這樣的:
-components??component1??component2??-actions.ts...allActions-reducers.ts...allReducers
首先從功能角度對項(xiàng)目進(jìn)行拆分。將業(yè)務(wù)邏輯拆分成高內(nèi)聚松耦合的模塊。從而對 feature 進(jìn)行新增,重構(gòu),刪除,重命名等變得簡單 ,不會(huì)影響到其他的feature,使項(xiàng)目可擴(kuò)展和可維護(hù)。 
再從技術(shù)角度進(jìn)行拆分,可以看到componet, routing,reducer 都來自等多個(gè)功能模塊
技術(shù)上的代碼按照功能的方式組織在feature下面,而不是單純通過技術(shù)角度進(jìn)行區(qū)分。 通常是由一個(gè)文件來管理所有的路由,隨著項(xiàng)目的迭代,這個(gè)路由文件也會(huì)變得復(fù)雜。那么可以把路由分散在feature中,由每個(gè)feature 來管理自己的路由。

如何組織 componet,action,reducer
按feature組織組件,action 和 reducer 組件和樣式文件在同一級 Redux放在單獨(dú)的文件
每個(gè)feature下面分為 redux文件夾 和 組件文件

redux文件夾下面的 action.js 只是充當(dāng)loader的作用,負(fù)責(zé)將各個(gè)action引入,而沒有具體的邏輯。reducer 同理

項(xiàng)目的根節(jié)點(diǎn)還需要一個(gè) root loader 來加載 feature 下的資源

如何組織 router
每個(gè)feature都有自己專屬的路由配置 頂層路由(頁面級別的路由)通過JSON配置1,然后解析JSON到React Router


import?{?App?}?from?'../features/home';import?{?PageNotFound?}?from?'../features/common';import?homeRoute?from?'../features/home/route';import?commonRoute?from?'../features/common/route';import examplesRoute from '../features/examples/route';const?childRoutes?=?[homeRoute,commonRoute,examplesRoute,];const?routes?=?[{path:?'/',componet:?App,childRoutes:?[...?childRoutes,{?path:'*',?name:?'Page?not?found',?component:?PageNotFound?},].filter(?r?=>?r.componet?||?(r.childRoutes?&&?r.childRoutes.length?>?0))??}]??export default routes
import React from 'react';import { Switch, Route } from 'react-router-dom';import { ConnectedRouter } from 'connected-react-router';import routeConfig from './common/routeConfig';function renderRouteConfig(routes, path) {const children = [] // children component listconst renderRoute = (item, routeContextPath) => {let newContextPath;if (/^\//.test(item.path)) {newContextPath = item.path;} else {newContextPath = `${routeContextPath}/${item.path}`;}newContextPath = newContextPath.replace(/\/+/g, '/');if (item.component && item.childRoutes) {const childRoutes = renderRouteConfigV3(item.childRoutes, newContextPath);children.push(key={newContextPath}render={props =>{childRoutes} }path={newContextPath}/>,);} else if (item.component) {children.push(, );} else if (item.childRoutes) {item.childRoutes.forEach(r => renderRoute(r, newContextPath));}};routes.forEach(item => renderRoute(item,path))return <Switch>childrenSwitch>}function Root() {const children = renderRouteConfig(routeConfig, '/');return ({children} );}
Node 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個(gè)小忙:
1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章 2. 訂閱官方博客?www.inode.club?讓我們一起成長 點(diǎn)贊和在看就是最大的支持??
