背景
在我們開發的過程中,經常會遇到這樣的問題,開發完了一些程式碼或者一個介面,別的小夥伴過來問你,程式碼可不可以給他複用,介面可以給他呼叫。這說明程式碼的複用和抽象對團隊協作是很重要的。舉個例子,如下圖
在這幅圖中A服務先開發的,B服務開發的時候與A服務功能相似,儘量使用A服務的介面以提高效率,那麼B服務是依賴A服務的,這樣會產生如下痛點
- 如果B服務有需求,A服務不滿足,則需求要先提給A服務,如果A服務的小夥伴沒時間開發…那麼B服務只能等待,因為這是一種依賴關係。
- 程式碼複用,因為是通過介面,所以也談不上覆用 要想解決這個兩個痛點,通用的思路是將A服務做下沉抽象,找團隊專門做,或者約定規範A服務組和B服務組共同維護一個下沉的服務 ,這裡我們使用node.js來開發A、B專案,通過包來代替web服務,可以提供一種不一樣的思路,通過對node.js專案的工程化,優化A、B專案的依賴,使開發A服務的同時對B服務進行開發支援
思路
如何一邊開發A服務一邊對外提供支援?node.js抽象的形式就是包,也就是說我們一邊開發A服務一邊對外發布公用的包,我們來看下面這幅圖
這是一個koa專案的目錄,問題來了,除了node_modules,這個專案裡面可以有幾個包,有人會說一個專案就是一個包,我們先寫一個簡單的例子,如下圖
在routes裡面新增一個路由地址,呼叫services裡面的方法
1 2 3 4 5 6 7 |
const router = require('koa-router')() const FooService = require('services').FooService router.get('/queryFoo', async (ctx, next) => { let fooService = new FooService() ctx.body = await fooService.queryFoo() }) module.exports = router |
寫一個service
1 2 3 4 5 6 |
class FooServices { async queryFoo () { return 'foo' } } module.exports = FooServices |
下面開始改造: 在services裡面加入index.js和package.json,將FooService.js通過index.js曝露出來,這樣services我們就拆成了一個包了
然後直接把services扔到node_modules裡面,修改下routes裡面的index.js的引入
1 2 3 4 5 6 7 |
const router = require('koa-router')() const FooService = require('services').FooService //修改 router.get('/queryFoo', async (ctx, next) => { let fooService = new FooService() ctx.body = await fooService.queryFoo() }) module.exports = router |
那麼routes可不可以變成包、models可以不可以,當然可以,雖然不用這麼極端的把一切都拆成包,但把services拆成包,效果是很不錯的,如下圖
在這裡我們可以這樣定義, A服務在開發的同時,也開發一個公用的下沉式的服務包,開發完services後,A組可以繼續開發controller、view層,B組在services基礎之上就可以開始開發了,不需要等A服務都開發完成,更高效,團隊間的配合更加靈活。
實現
上面只是提供了一種想法,實現這個想法還需要一個過程,先說產生了什麼問題: 問題1,如果我們把services單獨拆出來變成一個專案,可以…但是我們開發的時候回很鬱悶,會遇到: 修改services發包、下包、除錯、修改、發包、下包、除錯…真是好麻煩啊~~~ 那麼如何在一個專案裡面解決包依賴,我們來看一種實現的思路: 先編寫一個js指令碼,將services資料夾通過建立符號連結,連結到node_modules
1 2 3 4 5 |
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const rootPath = process.cwd(); fs.symlinkSync(`${rootPath}/services`, `${rootPath}/node_modules/services`, 'dir'); |
隨便配置個命令將呼叫指令碼命令配置到package.json中
1 2 3 4 5 6 7 8 9 10 11 12 13 |
"scripts": { "start": "node bin/www", "dev": "./node_modules/.bin/nodemon bin/www", "prd": "pm2 start bin/www", "test": "echo "Error: no test specified" && exit 1", "l": "node bin/link" } |
使用命令執行指令碼
1 |
npm run l |
建立符號連結後,修改services裡面的程式碼,node_modules裡的services也會跟著變,這樣我們就解決了上面的問題。 注意:這裡只是提供思路,要完善功能,請有興趣的小夥伴自己實現。
問題2,上面的專案裡我們只同時多開發一個包,如果在一個專案裡面同時開發幾十個包,引包和釋出,豈不是每次都要做幾十次,這個問題還好,更恐怖的是,如果這幾十個包還有引用關係….想象不出來,那麼看下面的例子,
在services裡面有兩個包,下面我們用zz-foo-services引用下zz-bar-services,多個services之間的引用是十分常見的。
問題來了,zz-bar-services就沒有釋出,從哪引用啊,難不成又建個指令碼用來建符號連結,那要是包多,幾十個包都有依賴關係,怎麼建連結啊~~~ 這裡我們就需要使用lerna來幫我們管理一個專案的多個包的包依賴、包釋出等,例如babel專案的開發就是使用lerna進行包管理、釋出的,下面我們來看下lerna的用法。
安裝 lerna
1 |
npm i -g lerna |
初始化
在專案根目錄執行命令
1 |
lerna init |
在根目錄建一個packages,是lerna管理的跟目錄,我們把剛才的包移動到這個目錄下,如下圖
使用命令將packages裡面的包的依賴安裝好
1 |
lerna bootstrap |
這個命令會執行npm i,還有建立符號連結
如上圖,zz-foo-services已經被連結進來了。 注意:這裡packages裡面的包連結到node_modules,有興趣的小夥伴自己動手試試。 問題3 多包的版本管理,這裡我們使用lerna publish命令將包批量釋出出去,這裡我們不舉例子,有興趣的小夥伴可以自己都動手試試。
總結
通過對node.js的專案工程化的處理,我們可以在一個專案裡面同時開發多個包,管理多個包,使得更緊密的團隊協作,包的高效拆分,包的管理與優化的開發過程,本文中筆者只是提供了工程化的部分思路,要想產出最佳實踐還需要做很多的優化。