原文地址: https://juejin.im/post/6845166891670093838
劃了半年,現在開始接客!
❝本篇文章存在大量乾貨,建議調整姿勢反覆觀看,所有技術棧通用,本文以vue項目爲例❞
「好代碼一定是設計出來的!而不是用多麼牛逼的技術棧」
DDD
注意這不是大笑表情包,DDD(domain-driven design-領域驅動設計),大部分前端接到需求的時候都在思考這個原型我要怎麼實現某塊功能細節(用哪個UI庫、該怎麼寫),即使不瞭解業務也一樣可以開發,通常也能完成工作,這種情況稱爲 ——「面向功能編程(沒有思考的前端資源)」。
然而,隨着業務的深入、需求不斷地迭代和變更,我們(前端)彷彿在負重前行,爲什麼?
- 業務不熟悉,不知道爲什麼要改需求,也不知道修改需求影響了原來哪塊代碼
- 歷史包袱多,投入相較後端越來越重
領域怎麼劃分?
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1030" height="284"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1030" height="284"></svg>)
差不多就是這樣抽象(就硬劃),建議和後端同學進行 「深入♂♂探討」(抽象能力個人認爲需要學習思考+後天培養,也不能指望文章)
❝我認爲應該在瞭解產品(或行業領域)的前提下進行軟件開發,先根據項目抽象出業務邊界模塊,建立領域再動手開發。❞
沙箱
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="570" height="374"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="570" height="374"></svg>)
❝哎呀,你這個代碼怎麼參數亂七八糟,方法全依賴在一起我都不敢改啊,怎麼一個js有9000行❞
我們先來康康什麼是沙箱(sandbox),安全獨立的,隔離外界的,互不影響的環境結構。
看起來好像沒什麼,我們再看看設計模式應該准許你的準則,
- 單一原責
- 迪米特(最少知識)原則
- 開放封閉原則
- 依賴倒置
- 接口隔離
- 裏式替換
是不是覺得DDD和設計原則、沙箱環境都有相同想強調的地方?
好像又繞回來了,嘮了半天,盡和我搞文縐縐的文字遊戲,是你需要30萬還是想爬山🧗了?
不是這樣的,如果
「「希望刪除和新增一塊代碼(領域),並不影響原有代碼,並且不需要改動也不會報錯,怎麼做?」」
接下來教大家如何運用在你的前端工程中。
結構
普通工程的目錄結構
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="552" height="490"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="552" height="490"></svg>)
很傳統的目錄結構,包括vue-cli3也是這麼做的,按照功能職責劃分,想修改路由前往routes文件夾裏修改,修改vuex去store裏找。
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500"></svg>)
按DDD劃分的目錄結構(非戰鬥人員請迅速撤離)
ps: 這裏的領域文件夾名不大對,當作錯誤示範將就看了
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="588" height="658"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="588" height="658"></svg>)
貼一份新目錄結構的說明
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="876" height="816"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="876" height="816"></svg>)
優點
爲什麼要這樣做?
職責
假設一個項目多人開發,拆分需求一定是按照某塊業務功能劃分的(分配工作),這就是我們在無意識中利用DDD的思想拆分產品業務。
- 清晰定位了每個同事的工作職責和邊界
- 出現問題方便定位到問題的範圍
- 減少代碼的衝突
效能
- 維護成本低
- 可複用某塊業務領域
- 可拓展(在統一的模式下做自動化、基建等)
缺點
- 代碼風格和編程思路的轉換
- 老項目的兼容 (在歷史債上轉換)
領域內職責&細節
❝因害怕篇幅過長(懶),所以本文使用了大量僞代碼(意識流裝逼)❞
數據訪問對象 DAO (Data Access Object)
static async getXxxList(api, payload) {
const res = await api(payload);
const { results, totalCount } = res.data.data;
return { data: results, total: totalCount };
}
複製代碼
沒什麼好說的,處理領域內的接口,api 接口都放在這裏,如果要處理數據建議也在這裏完成,做到視圖與數據分離,不要在view裏再做數據的封裝。
如果處理非常複雜建議再分層DTO(Data Transfer Object),把邏輯放在這裏面。
❝當你發現有接口重複的時候建議複寫,一般不會超過3個如果超過了思考是否領域拆分有問題❞
參數/枚舉 Model
裏面一般有倆個文件夾:
枚舉 Enums
一些selecet的內容
[
["發佈成功", 1],
["未發佈", 2],
["等待中", 3],
["發佈失敗", 4]
....
]
複製代碼
對應表對象 Vo
放一些對應表結構的數據,包括需要默認的內容、參數
{
// 團隊 Number teamId = null;
// 應用 String appName = null;
// 申請人 Enums(Role) role = 1;
// 開始時間 dateStr beginTime = null;
// 結束時間 dateStr endTime = null;
}
複製代碼
路由 Router
注意,通常我們都是寫在最外層一個統一的router文件夾來註冊彙總。
「但是在領域裏需要自己的路由,根據領域的實現和業務邏輯來規劃細分路由。」
視圖 View
類似路由的理念,可以在同一領域下拆分爲多個視圖,畢竟有些龐大的業務需要很多頁面才能支撐起來他的流轉。
中臺系統的某個領域內可能需要2-3套crud才能支撐起來,那其中的View文件夾下可能就要放2-3套對應的crud文件夾,文件夾內部纔是視圖的結構(搜索表單、表格、詳情等按需細分)。
Vuex
正常用法,領域內不同單元之間需要流通數據可用,不建議跨領域使用
領域註冊 Main.js
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1280" height="641"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1280" height="641"></svg>)
這裏考慮到每個領域需要賦予的能力都不一樣,所以需要針對單個領域模塊提供可選松耦合的能力。
有點發布訂閱模式內味了,註冊了什麼東東?先上一段神奇的代碼
import routes from "./router";
export { routes };
// 領域模塊名稱const MODULE_NAME = "Doctor";
// 註冊模塊能力export default ({
registerRouter,
registerStore,
registerApi,
.......
}) => {
// 使用模塊能力 registerRouter(MODULE_NAME, routes);
registerStore(MODULE_NAME, store);
};
複製代碼
看到了很多register開頭的參數這些是自定義的能力,從能力中心拿出註冊。
「能力分析」
能力的訂(相當於註冊能力的名單封裝),在最後會用到這裏
- registerRouter 把路由註冊到Doctor模塊下
Doctor: {
path: "/doctor/",
name: "doctor",
redirect: "/doctor/goodDoctor",
component: () => import(/* webpackChunkName: "goodDoctor" */ "../view/Main"),
children: [
{
path: "/doctor/goodDoctor",
name: "goodDoctor",
component: () =>
import(/* webpackChunkName: "goodDoctor" */ "../view/goodDoctor/Index")
},
{
path: "/doctor/badDoctor",
name: "badDoctor",
component: () =>
import(/* webpackChunkName: "badDoctor" */ "../view/badDoctor/Index")
},
]
}
複製代碼
- registerStore 把doctor的store註冊到vuex裏
Doctor: {
goodDoctor:{
namespaced: true,
state,
getters,
mutations,
actions
},
badDoctor:{
namespaced: true,
state,
getters,
mutations,
actions
}
}
複製代碼
還有很多就不細說了
「擴展」
- 針對單個領域的埋點
- 針對單個領域的性能監控
- 針對單個領域的錯誤監控
還有很多遐想(瞎想)空間......
中心化-領域彙總 Module.js
問:整了那麼多領域,怎麼讓他實例化把項目跑起來?
答:將領域彙總掛載到vue上
// 獲取src下的領域import 領域1 from "@/領域1/main";
import 領域2 from "@/領域2/main";
——————————————————————————————————————
/*
也可以用
require.context('@', false, /\\main.js/)
匹配所有src下領域內的main文件註冊
*/
const modules = [領域1,領域2,領域3....];
new Center(modules).mount("#app");
複製代碼
這個中心函數Center到底做了什麼了什麼?
import Vue from "vue";
import App from "../App";
class Center {
constructor(modules) {
// modulesCenter做了層遍歷,註冊的所有內容放進函數以便獲取 modulesCenter(modules);
Center.store = this.store = modulesCenter.createStore();
Center.router = this.router = modulesCenter.createRouter();
Center.vue = this.vue = new Vue({
store: this.store,
router: this.router,
render: h => h(App)
});
}
mount(mountEl) {
this.vue.$mount(mountEl);
return (Center.currentCenter = this);
}
}
export default Center;
複製代碼
大致做了什麼,估計你們也猜到了
- createRouter
const router = new VueRouter({
mode: "history",
...modules.router
});
複製代碼
- createStore
const store = new Vuex.Store({
...modules.store
});
複製代碼
共享領域數據
說白了就是跨領域調用,其實都在本地了,代碼和調用vuex modules是一毛一樣的
computed: {
//都在 Doctor 內 ...mapAction('Doctor',[
'/goodDoctor',
'/badDoctor',
])
}
複製代碼
將來的composition Api 可能會更加輕哦。
❝當然是不建議有耦合數據的,如果出現跨領域了依賴,說明拆分有問題,反推後端這裏是否可以進行改變,防患於未然,面向未來的需求提前編程。❞
基本能力講完了是不是覺得這麼做的意義和消耗的時間不成正比? 讓我們把能利再昇華一下,看看如何擴展成架構反哺業務......
[data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="400" height="400"></svg>](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="400" height="400"></svg>)
擴展基建中臺
- 代碼統一規範
- 領域代碼複用(也能跨項目)
- 提升效能,結合webpack5+vite可以只coding個別領域
- 搭建微前端體系前的技術籌備統一
領域gui化
使用某個領域
直接拖拽,low code或者微服務都可以,不限於輸出形式
配套設施
需要管理後臺進行收集與輸出,其它配套設施一大堆就不過多描述了
總結(人話時間)
- 思考業務
- 可插拔
- 提效