在開始本篇文章前,我給讀者們分享一個很考驗人性的有趣現象,在公司洗手間的洗漱臺旁邊,放置了一個垃圾桶,每次我洗完手,用紙巾擦乾手後,將其扔進垃圾桶,可是偶爾扔不許會扔到垃圾桶外面。javascript
通常狀況下,我會將其撿起,再放入垃圾桶,內心想着:「不能破壞這麼幹淨的環境呀」。css
可是,當垃圾桶周邊有不少別人沒扔進去的餐巾紙時,我就不會那麼願意將本身沒扔進去的餐巾紙再撿起來扔進去,想着:「反正都這麼邋遢了,多了一個也不會怎樣」。html
萬惡的人心呀!前端
過了好久,我接手了一個老的項目,這個項目通過近十我的手迭代,傳到我這裏時,已是很是混亂的狀態了,閱讀代碼時,發現了不少不合理的寫法與隱藏式BUG,當我在寫新的需求時,很天然地,我不會那麼精益求精地編寫業務邏輯,甚至也會留下一些隱藏的坑給後人。java
偏偏相反,前段時間有幸接手一個大佬的項目,閱讀其代碼彷彿如沐春風,整個結構堪稱完美,邏輯條理清晰,看代碼就像看需求文檔同樣,堪稱一絕。這個時候,當我要在其寫新的需求,我會模仿其設計,當心翼翼地將本身代碼插入其中,就像不忍心破壞這件藝術品同樣。react
以上故事純屬我一個理想主義程序員虛構。ios
可是回到現實當中,咱們維護一個混亂項目和一個優雅項目的心情確定是不同的,就像上面講的那個垃圾桶現象,混亂的項目就像周圍遍及不少垃圾的垃圾桶,當你在混亂項目裏再添加一些混亂代碼後會良心也不會很痛,而優雅的項目你就會注意本身的行爲,不能一顆老鼠屎壞了一鍋粥。git
這裏咱們講到的困難並非指技術細節實現層面上的困難,而是從整個軟件開發過程當中,遇到對高複雜度業務的開發困難,好比說很難從代碼中直觀地看出業務邏輯,項目經歷不一樣人手迭代致使的邏輯書寫規範不一致而進一步致使的後續人員理解成本高昂,說簡單點,就是在高複雜度的業務之上,開發人員沒有很強的意識去簡化邏輯,將業務知識直接體如今代碼中,咱們具體從如下幾個點來說解這個問題。程序員
這一點做爲開發者是很難避免的,在一個項目中,必然會存在一些邏輯複雜的業務,初始開發者是最可以理解該業務的每一個細節的,將業務映射成實際的代碼過程當中,複雜的業務轉換成的代碼確定是也是複雜的,如何將其直面地轉換成更易理解的代碼,讓後續維護者閱讀代碼就能大體理解其業務邏輯概貌,而進一步提高維護者開發的信心。github
一個項目在開發過程當中人員變更是很正常的,多是前人離職後人接手,也多是新增人手,新人對業務的理解每每是不夠透徹的,可能一來就直接評審接着就進入開發,好比新增了一個接口須要將數據展現在頁面上,該需求來龍去脈並不知曉,這就造成了一種「面向頁面」開發模式,對業務不熟悉,天然沒法合適地將新的需求代碼融入整個項目體系中。
複雜的業務邏輯知識在團隊中是很難傳播的,在人員的變更後,更是支離破碎,業務知識丟失後,開發者就會陷入「不知在哪改、不敢改、不肯改」的泥淖中,最終致使業務開發不下去,推倒重來,嚴重影響整個項目的進展,咱們在這裏能作的,就是儘可能將代碼寫成既能運行又能展現業務邏輯知識的形態,讓後續的維護者更有信心的面對「知識丟失」這一困境。
這裏我指的書寫規範並非指 eslint 之類的 style 規範,而是書寫業務邏輯的位置、方式、分層、複用等,好比 A 爲了將應用隔離而習慣將接口寫在 UI 層直接處理數據,B 習慣將接口寫在 common 模塊供本身或者別人在 UI 層調用,A 習慣將 util 類工具函數直接和 api 接口混在一塊兒寫,而 B 更願意將 util 類函數寫得更通用放在 common 模塊,假如新來了開發者 C , C 看到各式各樣的風格就會很疑惑,不知應該按照 A 仍是 B 或者按照本身的習慣書寫,隨着開發人員愈來愈多,直接會致使了整個項目邏輯書寫規範的崩潰,維護者的維護信心會大打折扣。
爲了讓讀者可以更直觀的理解領域驅動設計的思想,咱們用一個多頁面應用來舉一些例子,同時爲了體現出普通設計與領域驅動設計的區別,咱們會用兩種設計方式來實現同一需求,而且每一個需求都由團隊中的 A B C D 成員完成,成員的技術水平與代碼風格各不相同,咱們會分析在普通設計下,會出現哪些使得代碼複雜度失控的行爲。以後咱們使用領域驅動設計的思惟去重構該項目,再分析其設計方式如何讓項目業務邏輯更清晰與更易維護。
該需求爲一個大型零售業務的 demo 版,請讀者儘量地將其想象爲更爲複雜的業務場景,該項目分爲商品主頁、我的中心頁、積分中心頁、抽獎活動頁面,具體需求爲:
具體業務需求原型圖以及頁面對接口的調用如圖所示:
在上文中已經假設該項目會愈來愈龐大,因此爲了更高效地開發咱們將其設置成多頁面應用,咱們看到在寫少許接口與頁面的狀況下,視圖與不一樣領域的接口調用已是很是混亂的,在實際代碼中,這種混亂程度會由於上述講到「前端開發面臨的困難」中的問題而進一步放大,下一節咱們將使用很是不規範的團隊協做來實現整個項目。
咱們假設該團隊中成員的規範意識不強烈,各有各的代碼風格與分層習慣,這樣的代碼會寫成怎樣呢?
該項目我已經傳到 Github 中,讀者可訪問:ddd-fe-demo clone 代碼後執行如下操做啓動項目:
// 切換到不規範寫法的分支下
git checkout feature/normal
// 啓動 mock 數據
npm run server
// 啓動頁面
npm run start
複製代碼
多頁面應用,各頁面url:
這裏咱們用 mock 數據模仿後端的請求,下面是因此的數據接口,返回的數據請在/ddd-fe-demo/server/*.js
中查看:
// goods API
'GET /goods/list' // 獲取商品列表
// user API
'GET /user/detail' // 獲取用戶信息詳情
// ponit API
'GET /interest/point' // 獲取用戶剩餘積分
'GET /interest/pointRecord' // 獲取用戶積分記錄數據
'GET /interest/gift' // 獲取積分兌換獎品
// lottery API
'GET /lottery/detail' // 獲取該抽獎活動的詳情
'GET /lottery/prizeList' // 獲取獎品列表
'POST /lottery/play' // 觸發抽獎
'POST /lottery/address' // 填寫獎品收貨地址
複製代碼
├── common
│ └── util
│ └── http.js // 統一axios庫
└── page
├── index // 商城主頁目錄
│ ├── App.js
│ ├── apis // 商城頁面用到的api
│ │ ├── goods.js
│ │ └── user.js
│ ├── components
│ │ ├── GoodsItem.js
│ │ ├── GoodsItem.scss
│ │ ├── Nav.js
│ │ └── Nav.scss
│ └── index.js
├── interest // 積分權益頁面目錄
│ ├── App.js
│ ├── App.scss
│ ├── apis // 商城頁面用到的api
│ │ ├── interest.js
│ │ └── user.js
│ ├── components
│ │ ├── GiftItem.js
│ │ ├── GiftItem.scss
│ │ ├── PointRecordItem.js
│ │ └── PointRecordItem.scss
│ └── index.js
├── ....
複製代碼
這裏咱們經過貼出項目中的問題代碼,來分析出一些存在問題,以及討論其會致使的後果與優化的方案。
問題代碼的位置: /src/page/index/components/GoodsItem.js
return(
<div className="goods-item"> <div className="main-info"> <img className="goods-img" src={mainPic} alt=""/> <div className="goods-name">{goodsName}</div> {/* 當 status 爲2時,表示無貨 */} {status === 2 ? <span className="out-stock">已無貨</span> : null} </div> <div className="detail-info"> {/* 當 activityType 爲 3 表示該商品正在參與活動,爲特價商品 */} {activityType === 3 ? <span className="price discount">特價:{price / 100} 元</span> : <span className="price">價格:{price / 100} 元</span>} <div className="tag-wrap"> {filterTag.map(v=>{ return ( <span className="tag">{v.title}</span> ) })} </div> </div> </div> ) 複製代碼
存在的問題: 視圖層本來只須要展現 DOM 的結構,但這裏卻承擔了各類邏輯判斷、數據篩選、數據轉換等「雜活」,視圖代碼與邏輯代碼比例已經接近 1 : 1。
致使的後果: 難以直觀地理解視圖結構,而且在視圖層寫大段的註釋顯然是很不優雅。
優化思路: 視圖層最好單一,數據展現到視圖層以前,作好數據的篩選、轉換,判斷邏輯抽象層公用函數放入 util 中。
問題代碼位置: src/page/index/components/Nav.js
& src/page/user/App.js
<div className="user">
<img className={`${userInfo.vip ? 'vip' : ''}`} src={userInfo.avatar} alt=""/> <span>{userInfo.userType === 2 ? '尊敬的簽約客戶:' : null}{userInfo.userName}</span> </div>
複製代碼
<div>{userType === 2 ? '尊敬的簽約客戶:' : null}{userName}</div>
複製代碼
存在的問題: 一樣的邏輯在兩個視圖層中重複出現,這是團隊協做常常會遇到的問題,假設例子中的邏輯較假設很是複雜的,各成員實現方式不一致,在後期維護將會形成許多問題。
致使的後果:
優化思路: 試圖將某個實體抽象成一個類,好比將用戶抽象成 User 類,類中有一個方法爲 isContractUser 用來判斷用戶是否爲簽約客戶,以後視圖層只須要調用 User.isContractUser()
便可以複用這塊邏輯,而且容易理解其含義。
問題代碼位置: src/page/index/apis/user.js
& src/page/lottery/apis/user.js
& src/page/user/apis/user.js
import axios from '@common/util/http';
export function getUserInfo() {
return axios('/user/detail');
}
複製代碼
存在問題: 多塊業務頁面用到了同一個接口,而且在各自的根目錄下都有一份相同的請求代碼。做爲成員,可能有這樣的辨詞:「我怕他改動了這個接口的參數配置會致使個人頁面出問題,爲了相互隔離而將其複製一份」,雖然有道理,可是這不是最優解。
致使的後果: 很是直觀地後果就是代碼重複不優雅,修改一塊業務卻找到了不少相同的請求邏輯,容易搞混,而且接口發生變化後,統一維護的成本較大。
優化思路: 將整個項目中全部的請求函數統一放在 commom 中管理,根據領域劃分,好比說用戶領域下,存放用戶相關的接口,接口函數儘可能可配置、可拓展,供多個業務使用。
問題代碼: src/page/user/App.js
getUserInfo().then(data => {
this.setState({
userInfo: data
})
})
}
getUserPonitCount = () => {
getUserPointCount().then(count => {
this.setState({
pointCount: count
})
})
}
render() {
const { userInfo, pointCount } = this.state;
// vip 單從字面上難以辨別出是一個bool類型,更規範的命名應該是 isVip
// avatar 是一個 url 類型的字段,更規範的寫法應該是 avatarUrl 會更直觀
const { avatar, userName, userType, tel, vip, email, vipValidityDate } = userInfo;
複製代碼
存在的問題: 定義字段在理想的狀況下是前端主導,且先後端有共同的認知,可是不排除特殊狀況下接口字段定義混亂且不直觀。
致使的後果: 閱讀代碼時,接口字段不規範,在視圖層展現時,會致使誤解或者難以理解的代碼邏輯。
優化思路: 將接口層抽離出來,在接口返回時,逐一將字段列舉出來,將不符合規範的字段進行糾正,轉換成更易理解的詞語,甚至在這一層中就能將不少字段內容進行轉換,好比後端返回的金額爲分單位*100的整數,在這一層中即將其轉換爲浮點數,在視圖層中便可直接使用。
問題代碼: 上述整個項目例子。
存在的問題: 在一個龐大、多人協做的項目,做爲其中一員極可能出現對整個系統理解不夠,只知道本身負責的那幾個頁面,逐步惡化成「面向頁面編程」。
致使的後果:
這對整個項目的「成長」是不利的,會致使像上述舉例代碼中出現的「重複性」問題。假如開發者對整個項目有全局的瞭解,在編碼時,會考慮更多的「可拓展性」與「預判將來性」,或者在接手其餘成員負責的領域也會減小不少上手成本。
從業務的角度看,在需求評審的過程當中,熟悉總體業務,會對其新的需求進行更深的思考,判斷其對整個項目是否會有明顯的「驅動」做用,而進一步考慮是否應該拒絕該需求或者提出更好的需求建議,避免成爲產品經理說什麼就作什麼的「面向頁面編程」工程師。
優化思路: 將每一塊業務劃分紅不一樣的領域,各領域下包含哪些服務,每一個頁面調用的並非 API 接口,而是各自領域的服務。
首先提出領域的角色是需求方,每個需求都必將會映射到某個領域,好比「搜索商品」這個動做對應着商品中心域,「用戶登陸」對應着用戶信息&鑑權域。從產品-後端-前端對其領域的劃分認知都是一致的,這是各角色對其整個項目進行合做的基礎,在一塊兒討論問題時,都知道對方講述的信息是處在哪一個域上。
在對領域具備統一認知的狀況下,需求方也會更謹慎、清晰地提出新的需求或是更改業務邏輯,各方人員對其業務的熟悉後,也能從本身負責的職能角度上表達出本身對新業務新迭代的見解或建議,而不是「機械」地根據需求文檔完成本身的職責。
假設各方角色對總體業務領域不熟悉,你們對其業務的認知不統一,項目很快就會成爲一個鬆散的結構,需求方、開發方、設計方的產出模型沒法大體匹配,最後成爲開發/維護代價極高的「危樓」項目。
領域驅動設計不是萬能的,它只是解決了軟件開發中的部分問題,也不是可適用於任何場景的,可是其核心思想是能夠借鑑到軟件設計與開發過程當中的,本文主要講解領域驅動設計在前端中解決的問題以及核心思想。
在龐大的項目中,領域是很是繁多的,在對領域進行劃分時,咱們須要與產品、後端進行統一。我理解的前後流程是:產品產出需求,需求被劃分到已有或新的領域,後端接到需求根據其領域的劃分產出接口文檔,前端根據產品&後端的領域劃分設計出前端的領域,三方大體統一領域劃分後,各自對需求概念、名詞認知統一,最後才進行代碼的編寫。
咱們根據上述項目業務進行領域劃分,獲得如下領域模塊圖,每一個具體的功能都明確對應惟一一塊領域,產品、後端、前端對其都應該有一致的認知。
領域模塊圖是須要各方人員進行持續維護演進的,其存在的意義是增強了成員對業務的理解,讓團隊成員力量進行聚焦,共同思考業務,這樣才能讓項目走的更遠、更穩。
回到前端開發的設計上,咱們理解了上述講解的業務領域的概念後,接着將其落實到前端開發中,咱們重點須要理解的概念是職責分明,合理分層
,根據上述提出的「問題代碼」,咱們但願在前端結構設計中能作到:
帶着以上五個目的,咱們以第一步的業務領域爲基礎,進行前端結構的分層,而且開始改造上文出現問題的項目。
改造後的項目在 ddd-fe-demo 中,請讀者 clone 下來後,進行如下操做啓動項目:
git checkout master
npm run server // 啓動 mock 接口
npm run start // 啓動前端服務
複製代碼
一樣是訪問如下 url
建議讀者在開始下面內容閱讀前,將改造後的源碼大體閱讀一遍,帶着問題進入接下來的內容,會有更深的理解。
爲了讓各層職責分明,視圖層儘量純粹,咱們將各功能塊代碼進行分層,獲得如下層級:
分層以後明顯地下降了項目的複雜度,將前端的業務邏輯代碼與視圖邏輯進行解耦。
咱們根據上述結構圖的分層思想,在實際項目中定義瞭如下的文件目錄:
├── common
│ ├── components // 公用組件
│ ├── constants // 全局變量
│ │ ├── goods
│ │ │ └── index.js
│ │ ├── ...
│ ├── data-source // 數據接口層
│ │ ├── goods
│ │ │ ├── requestApis.js
│ │ │ └── translators.js
│ │ ├── ...
│ ├── domains // 領域層
│ │ ├── goods-domain
│ │ │ ├── entities // 實體
│ │ │ │ └── goods.js
│ │ │ └── goodsService.js // 領域Service服務
│ │ ├── ...
│ └── util // 公用函數
│ └── http.js
└── page // 頁面視圖層
├── index
│ ├── App.js
│ ├── components
│ │ ├── GoodsItem.js
│ │ ├── GoodsItem.scss
│ │ ├── Nav.js
│ │ └── Nav.scss
│ ├── index.js
│ └── services // 該頁面須要用到的Service
│ └── index.js
├── ...
複製代碼
代碼位置: src/common/data-source/interest/requestApis.js
import axios from '@common/util/http';
src/common/data-source/interest/requestApis.js
import { pointRecordTranslator, pointGiftTranslator } from './translators'
export function getUserPointRecordList() {
return axios('/interest/pointRecord').then(data => {
return data.map(item => pointRecordTranslator(item));
})
}
export function getInterestGiftList() {
return axios('/interest/gift').then(data => {
return data.map(item => pointGiftTranslator(item))
})
}
複製代碼
分層做用: 在這一層中集結了 interest 領域下全部的接口函數,避免了數據接口分散到各個頁面,統一存放更易管理,這裏咱們解決了上文提出的接口調用不統一
問題。
代碼位置: src/common/data-source/goods/translators.js
export function goodsTranslator({ id, goodsName, price, status, activityType, desc, brand, relatedModelId, mainPic, tag, relatedModelImg }) {
return {
id,
name: goodsName,
price: (price / 100).toFixed(2),
status,
activityType,
description: desc,
brandName: brand,
mainPicUrl: mainPic,
tags: tag
}
}
複製代碼
分層做用: 在這一層對接口字段、內容通過二次加工,避免了後端定義字段不規範、混亂對前端的影響,含義清晰、規範的字段在視圖層使用時更具備表現力,這裏咱們解決了上文提出的接口字段不可控性
問題。
數據接口層是整個項目的根基,提供告終構清晰、定義規範、可直接使用的數據。
領域層是整個項目的核心層,它掌管了全部領域下的行爲與定義,它是整個項目中最能體現業務知識的一層。
代碼位置: src/common/domains/lottery-domain/entities/lottery.js
/** * 抽獎活動實體 */
import dayjs from 'dayjs'
import { lotteryTypeMap } from '@constants/lottery'
class Lottery {
constructor(lottery={}) {
this.id = lottery.id
this.name = lottery.name
this.type = lottery.type
this.startDate = lottery.startDate
this.endDate = lottery.endDate
}
// 獲取活動時間範圍
getLotteryTimeScope() {
return `${dayjs(this.startDate).format("M月D日")} - ${dayjs(this.endDate).format("M月D日")}`
}
// 獲取活動類型描述
getLotteryType() {
return this.type && lotteryTypeMap[this.type].title
}
}
export default Lottery
複製代碼
在前端中,咱們把它定義爲一個 class 類,構造函數中初始化實體的屬性,在類中定義了實體的方法,屬性和方法的返回值主要是用於視圖層中的直接展現,同一個實體的邏輯確保只在實體類中編寫,在不一樣視圖下可複用,這裏咱們解決了上文提出的判斷邏輯重複
的問題。
代碼位置: src/common/domains/lottery-domain/lotteryService.js
import {
getLotteryDetail,
getPrizeList,
playLottery,
savePrizeAddress
} from '@data-source/lottery/requestApis';
import Prize from './entities/prize';
import Lottery from './entities/lottery';
class LotteryService {
/** * 獲取本次抽獎活動詳情 * @param {string} id 活動id */
static getLotteryDetail(id) {
return getLotteryDetail(id).then(lottery => new Lottery(lottery))
}
/** * 獲取本次抽獎活動的獎品列表 * @param {string} id 抽獎活動id */
static getPrizeList(id) {
return getPrizeList(id).then(list => {
return list.map(item => new Prize(item));
})
}
/** * 進行抽獎 * @param {string} id 抽獎活動id */
static playLottery(id) {
return playLottery(id).then(result => {
const { recordId, prize } = result;
return {
recordId,
prize: new Prize(prize)
}
})
}
/** * 填寫中獎的收貨地址信息 * @param {Object} param0 中獎記錄id以及地址信息 */
static savePrizeAddress({ recordId, name, phoneNumber, address }) {
const data = {
recordId,
name,
phoneNumber,
address
}
return savePrizeAddress(data)
}
}
export default LotteryService
複製代碼
分層做用: 咱們能夠看到,Service 層鏈接了 entity 層與 data-source 層,接收後端返回的數據將其轉換成具備屬性與方法的 entity 實體類,供視圖層直接進行展現。不只如此,Service 層還定義了該領域下的全部行爲,好比填寫收貨地址。領域服務層涵蓋了整個業務領域的行爲,直觀地體現了業務需求。這裏咱們解決了上文提出的忽略業務總體
問題。
視圖層也就是咱們書寫交互邏輯、樣式的一層,可使用純 HTML 或者框架(React、Vue),這一層只須要調用了領域的服務,將返回值直接體如今視圖層中,無需編寫條件判斷、數據篩選、數據轉換等與視圖展現無關的邏輯代碼,這些「糙活」都在其餘層中以已經完成,因此視圖層是很是「薄」的一層,只需關注視圖的展現與交互,整個 HTML 結構很是直觀清晰。
代碼位置: src/page/user/App.js
import React from 'react';
import { UserService, InterestService } from './services';
import User from '@domain/user-domain/entities/user';
import { SIGN_USER_TYPE } from '@constants/user';
import "./App.scss"
class App extends React.Component {
state = {
pointCount: null,
user: new User()
}
componentDidMount() {
this.getUserInfo();
this.getUserPonitCount();
}
// 獲取用戶信息
getUserInfo = () => {
UserService.getUserDetail().then(user => {
this.setState({
user
})
});
}
// 獲取用戶積分
getUserPonitCount = () => {
InterestService.getUserPointCount().then(count => {
this.setState({
pointCount: count
})
})
}
render() {
const { pointCount, user } = this.state;
return (
<div className="user-page"> <h3>我的中心</h3> <div className="user"> <div className="info"> <div>{user.type === SIGN_USER_TYPE ? `尊敬的${user.getUserTypeTitle()}:` : null}{user.name}</div> <div>綁定手機號: {user.phoneNumber}</div> <div>綁定email: {user.email}</div> </div> <div className="avatar"> <img className={`${user.isVip ? 'vip' : ''}`} src={user.avatarUrl} alt=""/> { user.isNeedRemindUserVipLack() && user.isVip ? <div>會員還有{user.getVipRemainDays()}天</div> : '' } </div> </div> <div className="lottery-tips"> <div>剩餘積分:{pointCount} 分</div> <a href="/interest.html">前往積分權益中心 ></a> </div> </div> ); } } export default App; 複製代碼
咱們能夠對比以前寫的「問題代碼」:
render() {
const { userInfo, pointCount } = this.state;
const { avatar, userName, userType, tel, vip, email, vipValidityDate } = userInfo;
// console.log()
const remainDay = dayjs(vipValidityDate).diff(new Date(), 'day');
return (
<div className="user-page"> <h3>我的中心</h3> <div className="user"> <div className="info"> <div>{userType === 2 ? '尊敬的簽約客戶:' : null}{userName}</div> <div>綁定手機號: {tel}</div> <div>綁定email: {email}</div> </div> <div className="avatar"> <img className={`${vip ? 'vip' : ''}`} src={avatar} alt=""/> { remainDay < 6 && vip ? <div>會員還有{remainDay}天</div> : '' } </div> </div> <div className="lottery-tips"> <div>剩餘積分:{pointCount} 分</div> <a href="/interest.html">前往積分權益中心 ></a> </div> </div> ); } 複製代碼
分層做用: 將 Service 中返回的數據直接使用,視圖層中只編寫交互與樣式,無論是 HTML 純粹的結構仍是代碼可讀性,新的設計都更討人喜歡。除了視圖層與前端框架有關,其餘層可獨立應用於任何框架的,分層的結構解決了上文提出的視圖層過厚
問題。
領域驅動設計的初衷是將項目進行合理地結構分層,下降複雜項目的維護難度,有效地減小團隊成員之間的協做成本,將業務直觀地映射成代碼,讓開發者更關注業務總體的自己,不侷限於本身的職責,共同提出更好的業務建議,只有業務真正有價值了,你寫的優秀代碼才能保證被傳承下去。
而一個複雜項目的生命週期是很是久的,可能長達幾年,維護舊代碼時間確定會比編寫新需求更長,爲了後期可以爽快地維護,前期多付出寫時間去改善代碼結構與質量,從長遠的角度來看,是很是值得的。就算中途離職,留給後人的代碼也能作到心安理得,更不會留下臭名昭著的名聲。
因此,這裏我想說的是,作一個更理想主義的程序員,堅決本身的信念,在實行領域驅動設計的初期或許會出現各類不適,甚至會受到冷嘲熱諷,挺過了這段適應期後,就能體會到本身設計的結構與代碼很絲滑很優雅。
團隊成員之間都須要熟悉全局的領域模型,特別是當須要修改他人負責領域下的代碼,更是要熟悉其領域下的細節。當團隊中加入了新的成員後,先向他介紹咱們項目下的領域模型,再分享咱們的項目架構與分層。
在另外一方面,團隊協做開發最不利的因素是閉門造車,你們都不知道對方作了什麼、怎麼作的,很常見的問題就是重複開發,建議團隊指定間隔多久進行一次討論,你們分享本身最近作了什麼或者遇到了什麼困難,或許本身的困惑其餘人以前也遇到過而且有很好的解決方案。你們也能夠一塊兒吐槽需求方不合理的需求,聽聽你們的觀點,說不定還能提升本身的業務思考能力。
既然選擇了領域驅動設計,那麼天然地要把本身融入到整個業務、整個項目中,把本身認定爲項目中不可缺乏的一部分,肩負了業務前進的重任。
由於團隊中各成員的能力水平、對領域驅動設計的領悟程度不一致,在初期可能會寫出不規範的代碼或者結構,甚至出現錯誤的領域劃分,在合入分支前進行嚴格的 Code Review 是很是有必要的,領域驅動設計是很是不抗「腐蝕」的,不能接受不規範的代碼或結構,在初期的 Review 成本或許有些大,等成員之間認知統一後,後續便能愉快地一塊兒寫代碼了~
本篇文章咱們以一個頗有趣的現象開頭,結合有問題的代碼分析出前端開發過程當中遇到的困難,接着提出了領域驅動設計,結合其實踐,逐一解決了以前遇到的困難,注意,上文實踐的領域驅動結構並非徹底按照 Evans 在《領域驅動設計》書中提出的結構,由於該書中的結構更適合後端的實踐,而在前端中,咱們提取了書中部分優良的設計,與實際的前端開發場景進行結合而總結出上述結構,固然讀者能夠對其結構進一步的改造、優化,也期待讀者與我進行交流,文中出現的錯誤歡迎指正。
領域驅動設計有效地下降了項目的複雜度,分層的結構讓各功能代碼職責分明,在前端中將其業務邏輯代碼逐一分層,與視圖層解耦,作到了真正的業務邏輯複用,在代碼的可讀性、可維護性上也有了質的提高。在業務上也讓開發者更融入整個項目,加深了開發者對業務的理解,可以共同的促進業務的進步。
我的微信是vince_hua,很高興能與你進行交流。
歡迎關注個人公衆號,發文章的頻率不會很高,但都能保證質量,由於關注量少因此不會有廣告之類的。