文章首發於個人博客 https://github.com/mcuking/bl...譯者:這篇文章是在 medium 講解前端架構分層系列的第一篇文章,分層和以前翻譯的文章相似,相對普通項目多出來兩層,領域層(從業務抽離出來領域實體)和用例層(實現應用業務邏輯)。另外在編程範式上,相對面對對象,做者更傾向於採用函數式,讀者可根據項目特色選擇適合本身的方式。html
原文連接 https://blog.codeminer42.com/...前端
這篇博客是《可擴展的前端》系列的一部分,你能夠看到其餘部分:
#2 — Common Patterns 和 #3 — The State Layer。node
關於軟件開發的可擴展性這一律念有兩個最多見的的意義:代碼的性能和可維護性。你能夠同時兼顧這兩點,可是專一於良好的可維護性會讓一件事情變得容易,那就是提高性能且不影響應用的其他部分。更重要的是,前端與後端有一個重要的區別:本地狀態。git
在這個系列博客中,咱們將會討論如何經過實際的通過驗證的方法,來開發和維護可擴展的前端應用。咱們大部分的例子將會使用 React 和 Redux,可是咱們會常常與其餘的技術棧對比較,來展現你如何達到一樣的結果。讓咱們開始這個關於架構方面的系列討論吧,這是你的軟件中最重要的部分。github
那麼架構究竟是什麼?說架構是軟件中的最重要的部分彷佛很自覺得是,但請耐新看下去。數據庫
架構是使軟件的各個部分相互交互以突出必需要作出的最重要的決策,而且推遲次要的決策和實現細節的方式。設計一個軟件的架構意味着將實際的應用從支持它的技術中分離開來。你的實際應用不知道數據庫、AJAX 請求、或者 GUI;而是由用例和領域模型組成。這些用例和領域模型表明了你的軟件所涵蓋的概念,請忽略執行用例的角色或數據在哪裏存儲等。npm
關於架構還有一個重要的事情要說:那就是架構不意味着文件的組織,也不是如何命名你的文件和文件夾。 |
dependency container
。是的,看起來很奇怪,並且擴展性很差,但這並不意味着用例的實現就在該對象內。這些只是委託給用例的方法,這些用例在其餘地方定義。應用程序的全部用例一塊兒使用一個對象比將它們分佈在整個代碼庫中要好得多,後者會使它們很難找到。有了這個對象,咱們要作的就是將其注入到 actions 中,讓每一個 action 決定將觸發什麼用例,對嗎? 若是你使用的是 redux-thunk,則使用 withExtraArgument 方法能夠很容易地實現它,該方法容許你將容器中的每一個 thunk 動做做爲 getState 以後的第三個參數注入。若是你使用的是 redux-saga,則該方法應該很簡單,在該方法中,咱們將容器做爲 run 方法的第二個參數進行傳遞。若是你使用的是 Ember 或 Angular,則內置的依賴項注入機制就足夠了。 這樣作會使 action 與用例解耦,由於你無需在定義 action 的每一個文件中手動導入用例。並且將 actions 與用例分開進行測試如今變得很是簡單:只需注入一個僞造的用例實現便可,該實現的行爲徹底符合你想要的方式。你是否想測試若是用例失敗,將 dispatch 什麼 action?注入一個老是失敗的模擬用例,而後測試 action 如何對此作出響應。無需考慮實際用例如何工做。 太好了,咱們將 state 層注入了 view 層,並將 application 層注入了 state 層。其他的呢?咱們如何將依賴項注入用例來構建
dependency container
?這是一個重要的問題,有不少方法能夠解決。首先,不要忘記檢查你使用的框架是否內置了依賴項注入,例如 Angular 或 Ember。若是確實如此,則你不該該本身構造。若是沒有,你能夠經過兩種方式來作到這一點:手動或在軟件包的幫助下。 手動進行操做應該很簡單: - 將你的模塊定義爲類或閉包, - 首先實例化沒有依賴性的模塊, - 而後再實例化有依賴的的模塊,將它們做爲參數傳遞, - 重複上述步驟,直到實例化全部用例爲止, - 導出它們。 太抽象了?看一些代碼示例: container.js
`
js import api from './infra/api'; // has no dependencies import { validateUser } from './domain/user'; // has no dependencies import makeUserRepository from './infra/user/userRepository'; import makeArticleRepository from './infra/article/articleRepository'; import makeCreateUser from './app/user/createUser'; import makeGetArticle from './app/article/getArticle'; const userRepository = makeUserRepository({ api }); const articleRepository = makeArticleRepository({ api }); const createUser = makeCreateUser({ userRepository, validateUser }); const getArticle = makeGetArticle({ userRepository, articleRepository }); export { createUser, getArticle };
`
createUser.js
`
js export default ({ validateUser, userRepository }) => async userData => { if (!validateUser(userData)) { throw new Error('Invalid user'); } try { const user = await userRepository.add(userData); return user; } catch (error) { throw error; } };
`
userRepository.js
`
js export default ({ api }) => ({ async add(userData) { const user = await api.post('/users', userData); return user; } });
`
你會注意到,重要部分(用例)已在文件末尾實例化,而且是惟一導出的對象,由於它們將被注入到 actions 中。你的其他代碼無需瞭解 repository 的操做方式和工做方式。這並不重要,而只是技術細節。對於用例,repository 是發送 AJAX 請求仍是在 LocalStorage 中保留某些內容都沒有關係;用例沒有職責須要知道。若是你想在 API 仍在開發中時使用 LocalStorage,而後切換爲使用經過網絡 API 的調用,只要與 API 交互的代碼遵循與 LocalStorage 交互的接口,而無需更改用例。 即便你有數十個 use cases(用例), repositories, services 等,也能夠如上所述手動完成注入。若是太麻煩而沒法構建全部依賴關係,則能夠始終使用依賴注入的庫,只要它不會增長耦合。 檢驗你的 DI(Dependency injection) 庫是否足夠好的一條經驗法則是,檢查從手動方法轉移到使用庫是否只須要操做 container 代碼便可。若是不是這樣,則說明庫太過侵入,你應該選擇其餘庫。若是你確實要使用庫,咱們建議你使用
Awilix。它很是簡單易用,無需手動操做,只需操做 container 文件便可。這個庫的做者撰寫了一系列有關如何使用以及爲何使用它的很好的文章,
點擊查看。 ## 接下來 好的,咱們已經討論了架構以及如何以一種很好的方式鏈接各層!在下一篇文章中,咱們將爲剛纔討論的層展現一些實際的代碼和通用模式,但 state 層除外,它會在單獨的文章中介紹。花一些時間來吸取這些概念。當咱們詳細介紹這些模式時,它們將很是有用,一切都會變得更加有意義。到時候那裏見! ## 推薦閱讀連接
NodeJS and Good Practices
Bob Martin — Architecture the Lost Years
Rebecca Wirfs-Brock — Why We Need Architects (and Architecture) on Agile Projects
Domain-Driven Design