<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>

          領域驅動設計(DDD)能給前端帶來什么

          共 6194字,需瀏覽 13分鐘

           ·

          2021-09-27 23:55

          感謝大家還沒取關我,畢竟這么久沒更新  最近在建設投資體系,這跟知識體系一樣是個系統(tǒng)工程,大家感興趣的話后續(xù)可以聊下這個話題。然后讀書系列會繼續(xù)進行下去~

          為什么需要DDD

              在回答這個問題之前,我們先看下大部分軟件都會經歷的發(fā)展過程:頻繁的變更帶來軟件質量的下降

          而這又是軟件發(fā)展的規(guī)律導致的:

          • 軟件是對真實世界的模擬,真實世界往往十分復雜

          • 人在認識真實世界的時候總有一個從簡單到復雜的過程

          • 因此需求的變更是一種必然,并且總是由簡單到復雜演變

          • 軟件初期的業(yè)務邏輯非常清晰明了,慢慢變得越來越復雜

              可以看到需求的不斷變更和迭代導致了項目變得越來越復雜,那么問題來了,項目復雜性提高的根本原因是需求變更引起的嗎?

              根本原因其實是因為在需求變更過程中沒有及時的進行解耦和擴展。


              那么在需求變更的過程中如何進行解耦和擴展呢?DDD發(fā)揮作用的時候來了。

          什么是DDD

          DDD(領域驅動設計)的概念見維基百科:

          https://zh.wikipedia.org/wiki/%E9%A0%98%E5%9F%9F%E9%A9%85%E5%8B%95%E8%A8%AD%E8%A8%88

              可以看到領域驅動設計(domin-driven design)不同于傳統(tǒng)的針對數(shù)據(jù)庫表結構的設計,領域模型驅動設計自然是以提煉和轉換業(yè)務需求中的領域知識為設計的起點。在提煉領域知識時,沒有數(shù)據(jù)庫的概念,亦沒有服務的概念,一切圍繞著業(yè)務需求而來,即:

          • 現(xiàn)實世界有什么事物  ->  模型中就有什么對象

          • 現(xiàn)實世界有什么行為  ->  模型中就有什么方法

          • 現(xiàn)實世界有什么關系  -> 模型中就有什么關聯(lián)

              在DDD中按照什么樣的原則進行領域建模呢?

              單一職責原則(Single responsibility principle)即SRP:軟件系統(tǒng)中每個元素只完成自己職責內的事,將其他的事交給別人去做。

              上面這句話有沒有什么哪里不清晰的?有,那就是“職責”兩個字。職責該怎么理解?如何限定該元素的職責范圍呢?這就引出了“限界上下文”的概念。

              Eric Evans 用細胞來形容限界上下文,因為“細胞之所以能夠存在,是因為細胞膜限定了什么在細胞內,什么在細胞外,并且確定了什么物質可以通過細胞膜。”這里,細胞代表上下文,而細胞膜代表了包裹上下文的邊界。

              我們需要根據(jù)業(yè)務相關性耦合的強弱程度分離的關注點對這些活動進行歸類,找到不同類別之間存在的邊界,這就是限界上下文的含義。上下文(Context)是業(yè)務目標,限界(Bounded)則是保護和隔離上下文的邊界,避免業(yè)務目標的不單一而帶來的混亂與概念的不一致。

          如何DDD

          DDD的大體流程如下:

          1. 建立統(tǒng)一語言

              統(tǒng)一語言是提煉領域知識的產出物,獲得統(tǒng)一語言就是需求分析的過程,也是團隊中各個角色就系統(tǒng)目標、范圍與具體功能達成一致的過程。

              使用統(tǒng)一語言可以幫助我們將參與討論的客戶、領域專家與開發(fā)團隊拉到同一個維度空間進行討論,若沒有達成這種一致性,那就是雞同鴨講,毫無溝通效率,相反還可能造成誤解。因此,在溝通需求時,團隊中的每個人都應使用統(tǒng)一語言進行交流。

              一旦確定了統(tǒng)一語言,無論是與領域專家的討論,還是最終的實現(xiàn)代碼,都可以通過使用相同的術語,清晰準確地定義領域知識。重要的是,當我們建立了符合整個團隊皆認同的一套統(tǒng)一語言后,就可以在此基礎上尋找正確的領域概念,為建立領域模型提供重要參考。

              舉個例子,不同玩家對于英雄聯(lián)盟(league of legends)的稱呼不盡相同;國外玩家一般叫“League”,國內玩家有的稱呼“擼啊擼”,有的稱呼“LOL”等等。那么如果要開發(fā)相關產品,開發(fā)人員和客戶首先需要統(tǒng)一對“英雄聯(lián)盟”的語言模型。

          1. 事件風暴(Event Storming)

              事件風暴會議是一種基于工作坊的實踐方法,它可以快速發(fā)現(xiàn)業(yè)務領域中正在發(fā)生的事件,指導領域建模及程序開發(fā)。它是Alberto Brandolini發(fā)明的一種領域驅動設計實踐方法,被廣泛應用于業(yè)務流程建模和需求工程,基本思想是將軟件開發(fā)人員和領域專家聚集在一起,相互學習,類似頭腦風暴。

              會議一般以探討領域事件開始,從前向后梳理,以確保所有的領域事件都能被覆蓋。

              什么是領域事件呢?

              領域事件是領域模型中非常重要的一部分,用來表示領域中發(fā)生的事件。一個領域事件將導致進一步的業(yè)務操作,在實現(xiàn)業(yè)務解耦的同時,還有助于形成完整的業(yè)務閉環(huán)。

          領域事件可以是業(yè)務流程的一個步驟,比如投保業(yè)務繳費完成后,觸發(fā)投保單轉保單的動作;也可能是定時批處理過程中發(fā)生的事件,比如批處理生成季繳保費通知單,觸發(fā)發(fā)送繳費郵件通知操作;或者一個事件發(fā)生后觸發(fā)的后續(xù)動作,比如密碼連續(xù)輸錯三次,觸發(fā)鎖定賬戶的動作。

          1. 進行領域建模,將各個模型分配到各個限界上下文中,構建上下文地圖。

              領域建模時,我們會根據(jù)場景分析過程中產生的領域對象,比如命令、事件等之間關系,找出產生命令的實體,分析實體之間的依賴關系組成聚合,為聚合劃定限界上下文,建立領域模型以及模型之間的依賴。

              上面我們大體了解了DDD的作用,概念和一般的流程,雖然前端和后端的DDD不盡相同,但是我們仍然可以將這種思想應用于我們的項目中。

          DDD能給前端項目帶來什么

          通過領域模型 (feature)組織項目結構,降低耦合度

              很多通過react腳手架生成的項目組織結構是這樣的:

          -components  component1  component2  -actions.ts ...allActions
          -reducers.ts ...allReducers

              這種代碼組織方式,比如actions.ts 中的 actions 其實沒有功能邏輯關系;當增加新的功能的時候,只是機械的往每個文件夾中加入對應的component,action,reducer,而沒有關心他們功能上的關系。那么這種項目的演進方向就是:
          項目初期:規(guī)模小,模塊關系清晰  ---> 迭代期:加入新的功能和其他元素 ---> 項目收尾:文件結構,模塊依賴錯綜復雜。
          因此我們可以通過領域模型的方式來組織代碼,降低耦合度。
          1. 首先從功能角度對項目進行拆分。將業(yè)務邏輯拆分成高內聚松耦合的模塊。從而對 feature 進行新增,重構,刪除,重命名等變得簡單 ,不會影響到其他的feature,使項目可擴展和可維護。

          2. 再從技術角度進行拆分,可以看到componet, routing,reducer 都來自等多個功能模塊
          可以看到:
          • 技術上的代碼按照功能的方式組織在feature下面,而不是單純通過技術角度進行區(qū)分。
          • 通常是由一個文件來管理所有的路由,隨著項目的迭代,這個路由文件也會變得復雜。那么可以把路由分散在feature中,由每個feature 來管理自己的路由。
          通過feature來組織代碼結構的好處是:當項目的功能越來越多時,整體復雜度不會指數(shù)級上升,而是始終保持在可控的范圍之內,保持可擴展,可維護。

          如何組織 componet,action,reducer

          文件夾結構該如何設計?
          • 按feature組織組件,action 和 reducer
          • 組件和樣式文件在同一級
          • Redux放在單獨的文件
          1. 每個feature下面分為 redux文件夾 和 組件文件
          1. redux文件夾下面的 action.js 只是充當loader的作用,負責將各個action引入,而沒有具體的邏輯。reducer 同理
          1. 項目的根節(jié)點還需要一個 root loader 來加載 feature 下的資源

          如何組織 router

          組織router的核心思想是把每個路由配置分發(fā)到每個 feature 自己的路由表中,那么需要:
          • 每個feature都有自己專屬的路由配置
          • 頂層路由(頁面級別的路由)通過JSON配置1,然后解析JSON到React Router
          1.每個feature有自己的路由配置
          2.頂層的routerConfig引入各個feature的子路由


          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
          3.解析JSON 路由到 React Router
          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 list const 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( <Route key={newContextPath} render={props => <item.component {...props}>{childRoutes}</item.component>} path={newContextPath} />, ); } else if (item.component) { children.push( <Route key={newContextPath} component={item.component} path={newContextPath} exact />, ); } else if (item.childRoutes) { item.childRoutes.forEach(r => renderRoute(r, newContextPath)); } }; routes.forEach(item => renderRoute(item,path)) return <Switch>children</Switch>}

          function Root() { const children = renderRouteConfig(routeConfig, '/'); return ( <ConnectedRouter>{children}</ConnectedRouter> );}



          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  免费一级a毛一级a看免费视频下载 | 乱伦五月| 欧美全黄一级裸片 | 丝袜足交片 | 国产成人黄色毛片不卡在线看 |