一步一步了解Monorepo各包間正確的通信方式
概述
Monorepo(Monolithic Repository)指的是把多個包(項目)的代碼,統(tǒng)一放在同一個代碼倉庫里進(jìn)行管理的一種倉庫組織方式。適合多個項目強(qiáng)相關(guān)且有大量共享代碼的中大型項目。使用 Monorepo 管理項目的好處非常明顯,它不用發(fā) npm 包,不用來回同步版本,改完公共庫,所有項目立即可用,使得共享代碼更簡單。 Monorepo 是存放多個包的倉庫,包之間的通信方式至關(guān)重要,接下來我們以一個交易項目為例,一步一步了解 Monorepo 各包間正確的通信方式。
交易平臺項目簡述
該項目是一個給用戶提供股票交易服務(wù)的平臺。項目包括交易(Trade)、資產(chǎn)(Portfolios)、行情(Quotes)、市場(Market)等模塊。項目還有提供公共方法、UI、數(shù)據(jù)等公共模塊。
交易平臺各包之間的通信問題
包之間的依賴關(guān)系不合理
包之間出現(xiàn)循環(huán)依賴問題,業(yè)務(wù)包依賴業(yè)務(wù)包。
├── @repo/shared ←→ @repo/trade (循環(huán)依賴) ├── @repo/account → @repo/trade (不合理) ├── @repo/quote → @repo/trade (不合理) ├── @repo/portfolios → @repo/trade (不合理) └── ...
封裝性破壞:直接訪問包內(nèi)部實現(xiàn)
直接導(dǎo)入其他包的內(nèi)部路徑
// packages/shared/utils/order.ts
import { EOrderVerify } from '@repo/trade/data';
import { canFillSearch } from '@repo/trade/utils';
// packages/shared/reports/modules/trade.ts
import { ECondOrderVerifyParaKey } from '@pkg/trade/data/sensors';
import { getIsEntrustType } from '@pkg/trade/utils/type';
import { useIndexSession, useNightTradeSession } from '@repo/trade/utils/hooks';緊耦合:直接依賴應(yīng)用層 stores
// packages/shared/utils/order.ts
import { useConfigStore } from '@/stores/config';
import { useMarketStore } from '@/stores/market.ts';
import { usePasswordStore } from '@/stores/password';
import { useTradeStore } from '@/stores/trade';
import { useUserStore } from '@/stores/user';
// packages/trade/utils/verify.ts
import { EStatementType } from '@/stores/quote-statement.ts';
// packages/portfolios/components/orders/index.vue
import { useMarketStore } from '@/stores/market';
import { useOrderStore } from '@/stores/order';項目包間正確通信方式
一、依賴層次原則
1.1 依賴層次結(jié)構(gòu)
┌─────────────────────────────────────┐
│ apps/web (應(yīng)用層) │
│ 依賴所有業(yè)務(wù)包,負(fù)責(zé)組裝和路由 │
└─────────────────────────────────────┘
│
├─────────────────────────────────────┐
│ │
┌─────────────▼──────────────┐ ┌──────────────────▼──────────────┐
│ 業(yè)務(wù)包 (Business) │ │ 基礎(chǔ)包 (Foundation) │
│ - trade │ │ - shared (不依賴其他業(yè)務(wù)包) │
│ - account │ │ - data-center │
│ - quote │ │ - ui │
│ - portfolios │ │ │
│ - company │ │ │
│ - market │ │ │
│ - ... │ │ │
└────────────────────────────┘ └──────────────────────────────────┘1.2 依賴規(guī)則
允許的依賴方向
- 應(yīng)用層 → 所有包
- 業(yè)務(wù)包 → 基礎(chǔ)包(shared, data-center, ui)
- 基礎(chǔ)包 → 只依賴更底層的基礎(chǔ)包或外部庫
- shared 包 → 不依賴任何業(yè)務(wù)包
禁止的依賴方向
- 基礎(chǔ)包 → 業(yè)務(wù)包(如 shared → trade)
- 業(yè)務(wù)包 → 業(yè)務(wù)包(如 account → trade)
- 循環(huán)依賴(如 shared ↔ trade)
1.3 依賴層次示例
// ? 正確:業(yè)務(wù)包依賴基礎(chǔ)包
// packages/trade/package.json
{
"dependencies": {
"@repo/shared": "workspace: " ,
"@repo/data-center" : "workspace: ",
"@repo/ui": "workspace:*"
}
}
// ? 錯誤:基礎(chǔ)包依賴業(yè)務(wù)包
// packages/shared/package.json
{
"dependencies": {
"@repo/trade": "workspace:*" // ? 不應(yīng)該依賴業(yè)務(wù)包
}
}二、通信模式
2.1 事件通信模式(Event Bus)
適用場景:
- 包間的松耦合通信
- 一對多的通知場景
- 異步事件通知
實現(xiàn)方式:
- 定義事件類型(在 shared 包中)
// packages/shared/events/index.ts
import mitt from 'mitt';
export const enum EEventBusType {
ORDER_SUCCESS = 'orderSuccess',
USER_LOGIN_SUCCESS = 'userLoginSuccess',
// ...
}
export type TEvents = {
[EEventBusType.ORDER_SUCCESS]?: {
orderId: string;
orderType: string;
};
[EEventBusType.USER_LOGIN_SUCCESS]?: unknown;
};
export const eventBus = mitt<TEvents>();
export default eventBus;- 發(fā)送事件(在 trade 包中)
// packages/trade/order/index.vue
import eventBus, { EEventBusType } from '@repo/shared/events';
// 下單成功后發(fā)送事件
const handleOrderSuccess = () => {
eventBus.emit(EEventBusType.ORDER_SUCCESS, {
orderId: '123',
orderType: 'limit'
});
};- 監(jiān)聽事件(在 portfolios 包中)
// packages/portfolios/components/orders/index.vue
import eventBus, { EEventBusType } from '@repo/shared/events';
import { onMounted, onUnmounted } from 'vue';
const orderChangeCallBack = (data: any) => {
// 處理訂單變化
refreshOrderList();
};
onMounted(() => {
eventBus.on(EEventBusType.ORDER_SUCCESS, orderChangeCallBack);
});
onUnmounted(() => {
eventBus.off(EEventBusType.ORDER_SUCCESS, orderChangeCallBack);
});優(yōu)點:
- 解耦:發(fā)送者和接收者不需要直接依賴
- 靈活:可以動態(tài)添加/移除監(jiān)聽器
- 支持一對多通信
注意事項:
- 事件類型定義應(yīng)在 shared 包中,確保類型安全
- 及時清理事件監(jiān)聽器,避免內(nèi)存泄漏
- 事件名稱應(yīng)清晰明確,避免命名沖突
2.2 服務(wù)模式(Singleton Service)
適用場景:
- 提供統(tǒng)一的數(shù)據(jù)訪問接口
- 跨包共享服務(wù)實例
- 需要單例模式的服務(wù)
實現(xiàn)方式:
- 定義服務(wù)(在 data-center 包中)
// packages/data-center/core/index.ts
import { Singleton } from '@repo/shared/utils/singleton';
export class DataCenter extends Singleton {
static tag = 'DataCenter';
async getStockInfo(code: string) {
// 獲取股票信息
}
async getQuotes(codes: string[]) {
// 獲取行情數(shù)據(jù)
}
}
// 2. 導(dǎo)出服務(wù)
export { DataCenter } from './core';- 使用服務(wù)(在任何包中)
// packages/trade/utils/stock.ts
import { DataCenter } from '@repo/data-center';
const getStockData = async (code: string) => {
const dataCenter = DataCenter.getInstance();
return await dataCenter.getStockInfo(code);
};優(yōu)點:
- 統(tǒng)一接口:所有包通過相同的接口訪問服務(wù)
- 單例保證:確保全局只有一個實例
- 易于測試:可以 mock 服務(wù)接口
注意事項:
服務(wù)接口應(yīng)穩(wěn)定,避免頻繁變更
服務(wù)應(yīng)在 shared 或 data-center 等基礎(chǔ)包中
避免在服務(wù)中直接依賴業(yè)務(wù)包
2.3 共享類型和接口
適用場景:
- 包間傳遞數(shù)據(jù)
- 定義公共接口
- 類型約束
實現(xiàn)方式:
- 定義共享類型(在 shared 包中)
// packages/shared/types/order.ts
export interface IOrderForm {
stockCode: string;
stockName: string;
entrustType: EEntrustType;
// ...
}
export interface IMaxAvailableAsset {
cashAvailable: number;
marginAvailable: number;
// ...
}- 使用共享類型(在 trade 包中)
// packages/trade/order/index.vue
import type { IOrderForm } from '@repo/shared/types/order';
const form: IOrderForm = {
stockCode: 'AAPL',
stockName: 'Apple Inc.',
// ...
};- 使用共享類型(在 portfolios 包中)
// packages/portfolios/components/orders/index.vue
import type { IOrderForm } from '@repo/shared/types/order';
const displayOrder = (order: IOrderForm) => {
// 使用共享類型
};優(yōu)點:
- 類型安全:TypeScript 提供編譯時類型檢查
- 一致性:確保數(shù)據(jù)結(jié)構(gòu)一致
- 可維護(hù)性:類型定義集中管理
注意事項:
- 共享類型應(yīng)在 shared 包中定義
- 避免在業(yè)務(wù)包中定義共享類型
- 類型定義應(yīng)向后兼容
2.4 公共 API 導(dǎo)出模式
適用場景:
- 包需要對外暴露功能
- 隱藏內(nèi)部實現(xiàn)細(xì)節(jié)
- 提供穩(wěn)定的接口
實現(xiàn)方式:
- 建立公共 API(在 trade 包中)
// packages/trade/index.ts
// 導(dǎo)出常量
export { EOrderVerify, EOrderErrorCode } from './data/constant';
// 導(dǎo)出工具函數(shù)
export { canFillSearch, getOpenAccountUrl } from './utils';
// 導(dǎo)出類型
export type { IOrderForm, IAttachedOrder } from './types';
// 導(dǎo)出組件(如果需要)
export { default as OrderPanel } from './order/index.vue';- 使用公共API(在其他包中)
// ? 正確:通過公共API導(dǎo)入
import { getOpenAccountUrl } from '@repo/trade';
// ? 錯誤:直接訪問內(nèi)部實現(xiàn)
import { getOpenAccountUrl } from '@repo/trade/utils';優(yōu)點:
- 封裝性:隱藏內(nèi)部實現(xiàn)
- 穩(wěn)定性:公共API相對穩(wěn)定
- 可維護(hù)性:內(nèi)部重構(gòu)不影響外部使用
注意事項:
每個包都應(yīng)建立 index.ts 作為公共 API 入口
只導(dǎo)出需要對外暴露的功能
避免導(dǎo)出內(nèi)部實現(xiàn)細(xì)節(jié)
2.5 依賴注入模式
適用場景:
- 需要解耦 stores 依賴
- 提高可測試性
- 支持不同的實現(xiàn)
實現(xiàn)方式:
- 定義接口(在 shared 包中)
// packages/shared/interfaces/stores.ts
export interface IUserStore {
logined: boolean;
customerInfo: any;
goLogin: () => void;
}
export interface IConfigStore {
urlsConfig: {
acOpen: string;
};
}- 通過參數(shù)注入(在 shared 包中)
// packages/shared/utils/order.ts
import type { IUserStore, IConfigStore } from '@repo/shared/interfaces/stores';
export const preVerify = async (
userStore: IUserStore,
configStore: IConfigStore
) => {
if (!userStore.logined) {
userStore.goLogin();
return false;
}
// ...
};- 在應(yīng)用層注入依賴(在 apps/web 中)
// apps/web/src/views/trade/index.vue
import { preVerify } from '@repo/shared/utils/order';
import { useUserStore } from '@/stores/user';
import { useConfigStore } from '@/stores/config';
const userStore = useUserStore();
const configStore = useConfigStore();
const handlePreVerify = async () => {
await preVerify(userStore, configStore);
};優(yōu)點:
- 解耦:包不直接依賴 stores
- 可測試:可以輕松 mock 依賴
- 靈活:支持不同的實現(xiàn)
注意事項:
接口定義應(yīng)在 shared 包中
依賴注入會增加調(diào)用復(fù)雜度
適合復(fù)雜場景,簡單場景可直接使用
2.6 API Linker 模式
適用場景:
- 通過 URL 參數(shù)調(diào)用 API
- 跨頁面通信
- 外部系統(tǒng)集成
實現(xiàn)方式:
- 注冊 API(在 trade 包中)
// packages/trade/utils/api-linker.ts
import apiLinker from '@repo/shared/app/router/api-linker';
apiLinker.registerLinkerApi({
openTradePanel: async (params: { stockCode: string }) => {
// 打開交易面板
},
submitOrder: async (params: IOrderForm) => {
// 提交訂單
}
});- 創(chuàng)建 API 鏈接(在其他包中)
// packages/company/components/stock-card/index.vue
import apiLinker from '@repo/shared/app/router/api-linker';
const openTrade = (stockCode: string) => {
const url = apiLinker.createApiLink('/trade', {
apiNames: ['openTradePanel'],
apiOptions: {
openTradePanel: { stockCode }
}
});
window.open(url);
};優(yōu)點:
- 跨頁面通信
- 支持外部系統(tǒng)集成
- 解耦頁面間依賴
三、最佳實踐
3.1 包內(nèi)導(dǎo)入規(guī)則
正確:包內(nèi)使用相對路徑
// packages/trade/utils/verify.ts
import { EOrderVerify } from '../data/constant';
import { canFillSearch } from './common';錯誤:包內(nèi)使用包名路徑
import { EOrderVerify } from '@repo/trade/data/constant';
// 討論:是否可以使用 @pkg/trade
import { EOrderVerify } from ' @pkg/trade/data/constant';正確:跨包使用包名路徑
// packages/account/index.vue
import { getOpenAccountUrl } from '@repo/trade';
import { eventBus } from '@repo/shared/events';3.2 類型定義規(guī)則
正確:共享類型在 shared 包中
// packages/shared/types/order.ts
export interface IOrderForm {
// ...
}正確:業(yè)務(wù)特定類型在業(yè)務(wù)包中
// packages/trade/types/condition.ts
export interface IConditionOrder {
// ...
}3.3 常量定義規(guī)則
正確:通用常量在 shared 包中
// packages/shared/constant/order.ts
export enum EEntrustType {
LIMIT = 1,
MARKET = 2,
}正確:業(yè)務(wù)特定常量在業(yè)務(wù)包中
// packages/trade/data/constant.ts
export enum EOrderVerify {
NOT_LOGIN = 'not_login',
// ...
}3.4 工具函數(shù)規(guī)則
正確:通用工具函數(shù)在 shared 包中
// packages/shared/utils/order.ts
export const isTrue = (flag: any) => {
// 通用邏輯
};正確:業(yè)務(wù)特定工具函數(shù)在業(yè)務(wù)包中
// packages/trade/utils/verify.ts
export const verifyOrder = (order: IOrderForm) => {
// 業(yè)務(wù)特定邏輯
};四、通信模式選擇指南
| 場景 | 推薦模式 | 示例 |
|---|---|---|
| 通知其他包某個事件發(fā)生 | 事件通信 | 訂單成功、登錄成功 |
| 獲取共享數(shù)據(jù) | 服務(wù)模式 | 獲取股票信息、行情數(shù)據(jù) |
| 傳遞數(shù)據(jù) | 共享類型 | 訂單表單、用戶信息 |
| 調(diào)用其他包功能 | 公共 API | 工具函數(shù)、組件 |
| 需要解耦 stores | 依賴注入 | 驗證函數(shù)、工具函數(shù) |
| 跨頁面通信 | API Linker | 打開交易面板 |
五、重構(gòu)建議
5.1 解決循環(huán)依賴
步驟1:識別共享內(nèi)容
// 找出 shared 包中使用的 trade 包內(nèi)容
// packages/shared/utils/order.ts
import { EOrderVerify } from '@repo/trade/data'; // 需要移到shared
import { canFillSearch } from '@repo/trade/utils'; // 需要移到shared步驟2:遷移到 shared 包
// packages/shared/constant/order.ts
export enum EOrderVerify {
NOT_LOGIN = 'not_login',
// ...
}
// packages/shared/utils/order.ts
export const canFillSearch = async (params: any) => {
// 實現(xiàn)邏輯
};步驟3:更新引用
// packages/trade/utils/verify.ts
// 從 shared 包導(dǎo)入
import { EOrderVerify } from '@repo/shared/constant/order';
import { canFillSearch } from '@repo/shared/utils/order';步驟4:移除依賴
// packages/shared/package.json
{
"dependencies": {
// 移除 "@repo/trade": "workspace:*"
}5.2 建立公共 API
步驟1:在各包的根目錄創(chuàng)建 index.ts
// packages/trade/index.ts
// 導(dǎo)出常量
export * from './data/constant';
// 導(dǎo)出工具函數(shù)
export { canFillSearch } from './utils/verify';
export { getOpenAccountUrl } from './utils/config';
// 導(dǎo)出類型
export type { IOrderForm } from './types';
// 導(dǎo)出組件(可選)
export { default as OrderPanel } from './order/index.vue';步驟2:更新引用
// packages/account/index.vue (account 包)
// 其他包通過 trade 包的公共 API 訪問
import { getOpenAccountUrl } from '@repo/trade';5.3 解耦 stores 依賴
步驟1:定義接口
// packages/shared/interfaces/stores.ts
export interface IUserStore {
logined: boolean;
customerInfo: any;
goLogin: () => void;
}步驟2:修改函數(shù)簽名
// packages/shared/utils/order.ts
import type { IUserStore } from '@repo/shared/interfaces/stores';
export const preVerify = async (userStore: IUserStore) => {
if (!userStore.logined) {
userStore.goLogin();
return false;
}
// ... 步驟3:在應(yīng)用層注入
// apps/web/src/views/trade/index.vue
import { preVerify } from '@repo/shared/utils/order';
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
await preVerify(userStore);六、檢查清單
在添加新的包間通信時,請檢查:
- 是否遵循依賴層次原則?
- 是否避免了循環(huán)依賴?
- 是否使用了合適的通信模式?
- 共享類型是否在 shared 包中?
- 是否建立了公共 API?
- 是否避免了直接訪問內(nèi)部實現(xiàn)?
- 是否避免了直接依賴 stores?
- 事件監(jiān)聽器是否及時清理?
七、總結(jié)
正確的包間通信應(yīng)該是:
遵循依賴層次:基礎(chǔ)包不依賴業(yè)務(wù)包
使用合適的通信模式:根據(jù)場景選擇事件、服務(wù)、類型等
建立公共 API:通過 index.ts 導(dǎo)出,隱藏內(nèi)部實現(xiàn)
共享類型和常量:在 shared 包中定義
解耦 stores:通過依賴注入或接口
及時清理資源:事件監(jiān)聽器等
遵循這些原則,可以確保包間的松耦合、高內(nèi)聚,提高代碼的可維護(hù)性和可測試性。
總結(jié)
到此這篇關(guān)于Monorepo各包間正確的通信方式的文章就介紹到這了,更多相關(guān)Monorepo包通信方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序?qū)崿F(xiàn)實時圓形進(jìn)度條的方法示例
這篇文章主要給大家介紹了利用微信小程序?qū)崿F(xiàn)實時圓形進(jìn)度條的方法,文中給出了詳細(xì)的示例代碼,相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-02-02
JS組件中bootstrap multiselect兩大組件較量
這篇文章主要介紹了JS組件中bootstrap multiselect兩大組件,兩者之間的較量,優(yōu)缺點比較,感興趣的小伙伴們可以參考一下2016-01-01
通過JAVASCRIPT讀取ASP設(shè)定的COOKIE
通過JAVASCRIPT讀取ASP設(shè)定的COOKIE...2007-02-02
javascript實現(xiàn)數(shù)字驗證碼的簡單實例
本篇文章主要是對javascript實現(xiàn)數(shù)字驗證碼的簡單實例進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-02-02
javascript實時獲取鼠標(biāo)坐標(biāo)值并顯示的方法
這篇文章主要介紹了javascript實時獲取鼠標(biāo)坐標(biāo)值并顯示的方法,涉及javascript操作鼠標(biāo)事件的相關(guān)技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04
詳解使用Next.js構(gòu)建服務(wù)端渲染應(yīng)用
這篇文章主要介紹了詳解使用Next.js構(gòu)建服務(wù)端渲染應(yīng)用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07

