我在Vue Storefront和Vue Storefront Next中實驗 Vue apps 的領域驅動方法有一段時間了。使用這種方法能顯著改善你的代碼庫的可維護性和複雜度。在這個系列,我想分享一些在 Vue Storefront 中對我們有效,且容易應用到任何 Vue 應用程式中的模式。
我將展示如何在 Nuxt 和「普通」Vue 項目中如何應用這些模式。本文中,我們將聚焦理論和 Nuxt 實現。
什麼是領域驅動設計?
領域驅動設計(Domain-Driven Design,DDD)概念是,將業務概念優先於代碼庫的其它分類類型(例如,按文件類型分組)。這意味著,你應該基於你主要的業務領域(「問題」)及其子領域(問題的細分部分)來組織你的代碼。例如,在電子商務領域,我們有產品目錄、客戶、訂單、庫存等子領域。
雖然 DDD 概念來自面向對象編程,依賴於類及其關係,但其核心理念可以很容易地應用到其它範式。
為什麼領域驅動設計很棒?
領域驅動設計越來越受歡迎最簡單的解釋是,它是一種非常簡單的模式,在大多數情況下,它都會在編程最困難和最痛苦的領域之一——代碼維護性上帶來顯著改善。
我記得,我在思考組織代碼庫的最佳方式時度過的無眠之夜和漫長的淋浴。如果你曾經嘗試過,你就會明白這項任務非常困難,而且結果永遠不會完美,因為你必須考慮到你的項目在將來可能演變的所有可能的方式。隨著時間的推移,我們對試圖解決問題的理解不斷增長,這會帶來更有效的解決問題的方法,從而改變我們最初的方案。
我們都經歷過並且知道,改變最初的架構會付出多大的成本。
這就是領域驅動設計的亮點。基於業務需求對系統進行建模要容易得多,因為這個方案不受技術限制和困難的影響。我們改變核心業務需求的頻率比改變底層技術的頻率要少得多。
此外,領域驅動設計推崇在編寫代碼時考慮實際的業務需求,這意味著我們根據業務功能(例如客戶)而不是技術術語(組件、存儲、服務),來組織我們的代碼和文件結構。這種管理代碼的方式讓代碼更容易維護。
下圖顯示了一個「標準的」Vue 文件結構 vs 基於業務領域的文件結構:
如果與某個功能相關的所有東西都放在一個地方,就會很容易理解它是如何工作的,因為不存在忽略某些部分的風險。完全刪除某個功能也同樣非常簡單,因為這個功能相關的代碼不會分布在整個代碼庫中。
另一個好處是,我們只需查看應用程式的文件夾結構,就能很容易地推斷出其業務和功能。這也很容易找到一個開始上手的地方,因為我們的任務大部分時候是用業務語言編寫的。我能夠給出很多其它例子,但是我確信你已經明白為什麼這個方案現在被認為是更容易維護的了。
領域驅動設計和根據功能組織代碼的最大好處之一,就是限制了不同模塊之間的關聯數量。通常,我們在應用程式中有一組擴展點被所有模塊使用。理想情況下,所有的連接都應該通過一個單獨的地方進行,例如一個共享模塊或事件總線。使用這種方法更容易維護這些模塊,因為連接不同功能的部分通常是導致應用程式複雜性增加最多的地方。如果我們設法避免不必要的複雜性並簡化連接,那麼讓你的代碼庫保持簡單也就非常容易了。
弄清楚如何使你的模塊獨立並封裝,是另外一篇文章(或者是一系列文章)的主題,因此現在如果你想要深入了解這一點,我建議你閱讀hexagonal architecture,並應用這種方法中的大部分有用的理念。
那麼,我們該如何在 Vue 中應用領域驅動設計理念呢?
Nuxt 中的領域驅動設計
我們從 Nuxt 開始介紹,它有了一個很好的內置機制,使得應用這個理念非常容易——模塊。
Nuxt 模塊通常用於在你的應用程式中包含第三方功能,例如授權或 i18n。雖然編寫第三方代碼是最常用的利用它們的方法,但是 Nuxt 模塊也可以很好地在我們的內部架構中發揮作用。
因為 Nuxt 模塊可以掛接到應用程式的不同部分,例如路由或 Vuex store,所以我們能很容易地使它成為一個特定子領域與我們的應用程式連接的唯一地方!
看看這個簡單的 Nuxt 模塊:
// index.jsmodule.exports = function ProductCatalogModule (moduleOptions) { this.extendRoutes((routes) => { routes.unshift({ name: 'product', path: '/p/:slug/', component: path.resolve(themeDir, 'pages/Product.vue') }); ); // we can't register stores through Nuxt modules so we have to make a plugin this.addPlugin(path.resolve(__dirname, 'plugins/add-stores.js'))// plugins/add-stores.jsimport CategoryStore from '../stores/category.js'import ProductStore from '../stores/product.js'export default async ({ store }) => { store.registerModule(CategoryStore) store.registerModule(ProductStore)};…有如下文件:
.├── pages| |── Category.vue │ └── Product.vue├── components| |── ProductTile.vue│ └── ProductGallery.vue├── plugins│ └── add-stores.js├── store| |── category.js│ └── product.js└── index.js我們這裡就是一個小的 Nuxt.js 應用程式,專注於我們的應用程式的某一個子領域。現在,當我們想要在應用程式中包含所有目錄相關的功能時,我們只需要一行代碼就可以做到這一點!
// nuxt.config.jsexport default { modules: [ '~/modules/product-catalog/index.js', ]}現在,每當我們在 Jira 中看到產品目錄相關的問題時,我們就會立即知道從哪裡開始改。而且,使用這種模塊化方案,新來的人也能很快知道我們應用程式中有什麼功能,因此可以更快地理解業務和技術背景!
.├── pages│ └── index.vue├── modules| |── product-catalog| |── orders| |── payment| |── shipping| |── inventory│ └── customers└── index.js總結與展望
基於業務領域及其子領域來組織代碼,是使應用程式更易於維護的最簡單有效的方法之一。儘管還有一些已知的挑戰,例如不同模塊之間的通信,了解基礎知識就足以為代碼庫帶來巨大的好處。應用本文中的模式不需要高級編程技巧,這讓所有層次的開發人員都可以使用它。
原文連結:
https://vueschool.io/articles/vuejs-tutorials/domain-driven-design-in-nuxt-apps/
延伸閱讀:
前端周報:微軟發布基於Chromium的Microsoft Edge預覽版;Nuxt發布v2.9.0;npm 發布v6.11.0-InfoQ
關注我並轉發此篇文章,私信我「領取資料」,即可免費獲得InfoQ價值4999元迷你書,點擊文末「了解更多」,即可移步InfoQ官網,獲取最新資訊~