Monorepo 各包间正确的通信方式

概述

Monorepo(Monolithic Repository)指的是把多个包(项目)的代码,统一放在同一个代码仓库里进行管理的一种仓库组织方式。适合多个项目强相关且有大量共享代码的中大型项目。使用 Monorepo 管理项目的好处非常明显,它不用发 npm 包,不用来回同步版本,改完公共库,所有项目立即可用,使得共享代码更简单。 Monorepo 是存放多个包的仓库,包之间的通信方式至关重要,接下来我们以一个交易项目为例,一步一步了解 Monorepo 各包间正确的通信方式。

交易平台项目简述

该项目是一个给用户提供股票交易服务的平台。项目包括交易(Trade)、资产(Portfolios)、行情(Quotes)、市场(Market)等模块。项目还有提供公共方法、UI、数据等公共模块。

交易平台各包之间的通信问题

包之间的依赖关系不合理

包之间出现循环依赖问题,业务包依赖业务包。

ts 复制代码
├── @repo/shared ←→ @repo/trade (循环依赖)
├── @repo/account → @repo/trade (不合理)
├── @repo/quote → @repo/trade (不合理)
├── @repo/portfolios → @repo/trade (不合理)
└── ...

封装性破坏:直接访问包内部实现

直接导入其他包的内部路径

ts 复制代码
  // 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';

紧耦合:直接依赖应用层 stores

ts 复制代码
// 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 依赖层次结构

ts 复制代码
┌─────────────────────────────────────┐
│         apps/web (应用层)            │
│  依赖所有业务包,负责组装和路由      │
└─────────────────────────────────────┘
              │
              ├─────────────────────────────────────┐
              │                                     │
┌─────────────▼──────────────┐  ┌──────────────────▼──────────────┐
│   业务包 (Business)        │  │   基础包 (Foundation)            │
│  - trade                   │  │  - shared (不依赖其他业务包)     │
│  - account                 │  │  - data-center                  │
│  - quote                   │  │  - ui                            │
│  - portfolios              │  │                                  │
│  - company                 │  │                                  │
│  - market                  │  │                                  │
│  - ...                     │  │                                  │
└────────────────────────────┘  └──────────────────────────────────┘

1.2 依赖规则

✅ 允许的依赖方向
  1. 应用层 → 所有包
  2. 业务包 → 基础包(shared, data-center, ui)
  3. 基础包 → 只依赖更底层的基础包或外部库
  4. shared 包 → 不依赖任何业务包
❌ 禁止的依赖方向
  1. 基础包 → 业务包(如 shared → trade)
  2. 业务包 → 业务包(如 account → trade)
  3. 循环依赖(如 shared ↔ trade)

1.3 依赖层次示例

ts 复制代码
// ✅ 正确:业务包依赖基础包
// packages/trade/package.json
{
  "dependencies": {
    "@repo/shared": "workspace: " ,
 "@repo/data-center" :  "workspace: ",
    "@repo/ui": "workspace:*"
  }
}

// ❌ 错误:基础包依赖业务包
// packages/shared/package.json
{
  "dependencies": {
    "@repo/trade": "workspace:*"  // ❌ 不应该依赖业务包
  }
}

二、通信模式

2.1 事件通信模式(Event Bus)

适用场景

  1. 包间的松耦合通信
  2. 一对多的通知场景
  3. 异步事件通知

实现方式

  1. 定义事件类型(在 shared 包中)
ts 复制代码
// 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;
  1. 发送事件(在 trade 包中)
ts 复制代码
// packages/trade/order/index.vue
import eventBus, { EEventBusType } from '@repo/shared/events';

// 下单成功后发送事件
const handleOrderSuccess = () => {
  eventBus.emit(EEventBusType.ORDER_SUCCESS, {
    orderId: '123',
    orderType: 'limit'
  });
};
  1. 监听事件(在 portfolios 包中)
ts 复制代码
// 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);
});

优点

  1. 解耦:发送者和接收者不需要直接依赖
  2. 灵活:可以动态添加/移除监听器
  3. 支持一对多通信

注意事项

  1. 事件类型定义应在 shared 包中,确保类型安全
  2. 及时清理事件监听器,避免内存泄漏
  3. 事件名称应清晰明确,避免命名冲突

2.2 服务模式(Singleton Service)

适用场景

  1. 提供统一的数据访问接口
  2. 跨包共享服务实例
  3. 需要单例模式的服务

实现方式

  1. 定义服务(在 data-center 包中)
ts 复制代码
// 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[]) {
    // 获取行情数据
  }
}

// 2. 导出服务
export { DataCenter } from './core';
  1. 使用服务(在任何包中)
ts 复制代码
// 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);
};

优点

  1. 统一接口:所有包通过相同的接口访问服务
  2. 单例保证:确保全局只有一个实例
  3. 易于测试:可以 mock 服务接口

注意事项

  1. 服务接口应稳定,避免频繁变更

  2. 服务应在 shared 或 data-center 等基础包中

  3. 避免在服务中直接依赖业务包

2.3 共享类型和接口

适用场景

  1. 包间传递数据
  2. 定义公共接口
  3. 类型约束

实现方式

  1. 定义共享类型(在 shared 包中)
ts 复制代码
// packages/shared/types/order.ts
export interface IOrderForm {
  stockCode: string;
  stockName: string;
  entrustType: EEntrustType;
  // ...
}

export interface IMaxAvailableAsset {
  cashAvailable: number;
  marginAvailable: number;
  // ...
}
  1. 使用共享类型(在 trade 包中)
ts 复制代码
// packages/trade/order/index.vue
import type { IOrderForm } from '@repo/shared/types/order';

const form: IOrderForm = {
  stockCode: 'AAPL',
  stockName: 'Apple Inc.',
  // ...
};
  1. 使用共享类型(在 portfolios 包中)
ts 复制代码
// packages/portfolios/components/orders/index.vue
import type { IOrderForm } from '@repo/shared/types/order';

const displayOrder = (order: IOrderForm) => {
  // 使用共享类型
};

优点

  1. 类型安全:TypeScript 提供编译时类型检查
  2. 一致性:确保数据结构一致
  3. 可维护性:类型定义集中管理

注意事项

  1. 共享类型应在 shared 包中定义
  2. 避免在业务包中定义共享类型
  3. 类型定义应向后兼容

2.4 公共 API 导出模式

适用场景

  1. 包需要对外暴露功能
  2. 隐藏内部实现细节
  3. 提供稳定的接口

实现方式

  1. 建立公共 API(在 trade 包中)
ts 复制代码
// packages/trade/index.ts
// 导出常量
export { EOrderVerify, EOrderErrorCode } from './data/constant';

// 导出工具函数
export { canFillSearch, getOpenAccountUrl } from './utils';

// 导出类型
export type { IOrderForm, IAttachedOrder } from './types';

// 导出组件(如果需要)
export { default as OrderPanel } from './order/index.vue';
  1. 使用公共API(在其他包中)
ts 复制代码
// ✅ 正确:通过公共API导入
import { getOpenAccountUrl } from '@repo/trade';

// ❌ 错误:直接访问内部实现
import { getOpenAccountUrl } from '@repo/trade/utils';

优点

  1. 封装性:隐藏内部实现
  2. 稳定性:公共API相对稳定
  3. 可维护性:内部重构不影响外部使用

注意事项

  1. 每个包都应建立 index.ts 作为公共 API 入口

  2. 只导出需要对外暴露的功能

  3. 避免导出内部实现细节

2.5 依赖注入模式

适用场景

  1. 需要解耦 stores 依赖
  2. 提高可测试性
  3. 支持不同的实现

实现方式

  1. 定义接口(在 shared 包中)
ts 复制代码
// packages/shared/interfaces/stores.ts
export interface IUserStore {
  logined: boolean;
  customerInfo: any;
  goLogin: () => void;
}

export interface IConfigStore {
  urlsConfig: {
    acOpen: string;
  };
}
  1. 通过参数注入(在 shared 包中)
ts 复制代码
// 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;
  }
  // ...
};
  1. 在应用层注入依赖(在 apps/web 中)
ts 复制代码
// 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);
};

优点

  1. 解耦:包不直接依赖 stores
  2. 可测试:可以轻松 mock 依赖
  3. 灵活:支持不同的实现

注意事项

  1. 接口定义应在 shared 包中

  2. 依赖注入会增加调用复杂度

  3. 适合复杂场景,简单场景可直接使用

2.6 API Linker 模式

适用场景

  1. 通过 URL 参数调用 API
  2. 跨页面通信
  3. 外部系统集成

实现方式

  1. 注册 API(在 trade 包中)
ts 复制代码
// 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) => {
    // 提交订单
  }
});
  1. 创建 API 链接(在其他包中)
ts 复制代码
// 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);
};

优点

  1. 跨页面通信
  2. 支持外部系统集成
  3. 解耦页面间依赖

三、最佳实践

3.1 包内导入规则

✅ 正确:包内使用相对路径
ts 复制代码
// packages/trade/utils/verify.ts
import { EOrderVerify } from '../data/constant';
import { canFillSearch } from './common';
❌ 错误:包内使用包名路径
ts 复制代码
import { EOrderVerify } from '@repo/trade/data/constant';

// 讨论:是否可以使用 @pkg/trade
import { EOrderVerify } from ' @pkg/trade/data/constant';
✅ 正确:跨包使用包名路径
javascript 复制代码
// packages/account/index.vue
import { getOpenAccountUrl } from '@repo/trade';
import { eventBus } from '@repo/shared/events';

3.2 类型定义规则

✅ 正确:共享类型在 shared 包中
ts 复制代码
// packages/shared/types/order.ts
export interface IOrderForm {
  // ...
}
✅ 正确:业务特定类型在业务包中
ts 复制代码
// packages/trade/types/condition.ts
export interface IConditionOrder {
  // ...
}

3.3 常量定义规则

✅ 正确:通用常量在 shared 包中
ts 复制代码
// packages/shared/constant/order.ts
export enum EEntrustType {
  LIMIT = 1,
  MARKET = 2,
}
✅ 正确:业务特定常量在业务包中
ts 复制代码
// packages/trade/data/constant.ts
export enum EOrderVerify {
  NOT_LOGIN = 'not_login',
  // ...
}

3.4 工具函数规则

✅ 正确:通用工具函数在 shared 包中
ts 复制代码
// packages/shared/utils/order.ts
export const isTrue = (flag: any) => {
  // 通用逻辑
};
✅ 正确:业务特定工具函数在业务包中
ts 复制代码
// packages/trade/utils/verify.ts
export const verifyOrder = (order: IOrderForm) => {
  // 业务特定逻辑
};

四、通信模式选择指南

场景 推荐模式 示例
通知其他包某个事件发生 事件通信 订单成功、登录成功
获取共享数据 服务模式 获取股票信息、行情数据
传递数据 共享类型 订单表单、用户信息
调用其他包功能 公共 API 工具函数、组件
需要解耦 stores 依赖注入 验证函数、工具函数
跨页面通信 API Linker 打开交易面板

五、重构建议

5.1 解决循环依赖

步骤1:识别共享内容

ts 复制代码
// 找出 shared 包中使用的 trade 包内容
// packages/shared/utils/order.ts
import { EOrderVerify } from '@repo/trade/data';  // 需要移到shared
import { canFillSearch } from '@repo/trade/utils'; // 需要移到shared

步骤2:迁移到 shared 包

ts 复制代码
// packages/shared/constant/order.ts
export enum EOrderVerify {
  NOT_LOGIN = 'not_login',
  // ...
}

// packages/shared/utils/order.ts
export const canFillSearch = async (params: any) => {
  // 实现逻辑
};

步骤3:更新引用

ts 复制代码
// packages/trade/utils/verify.ts
// 从 shared 包导入
import { EOrderVerify } from '@repo/shared/constant/order';
import { canFillSearch } from '@repo/shared/utils/order';

步骤4:移除依赖

ts 复制代码
// packages/shared/package.json
{
  "dependencies": {
    // 移除 "@repo/trade": "workspace:*"
  }

5.2 建立公共 API

步骤1:在各包的根目录创建 index.ts

ts 复制代码
// packages/trade/index.ts
// 导出常量
export * from './data/constant';

// 导出工具函数
export { canFillSearch } from './utils/verify';
export { getOpenAccountUrl } from './utils/config';

// 导出类型
export type { IOrderForm } from './types';

// 导出组件(可选)
export { default as OrderPanel } from './order/index.vue';

步骤2:更新引用

ts 复制代码
// packages/account/index.vue (account 包)
// 其他包通过 trade 包的公共 API 访问
import { getOpenAccountUrl } from '@repo/trade';

5.3 解耦 stores 依赖

步骤1:定义接口

ts 复制代码
// packages/shared/interfaces/stores.ts
export interface IUserStore {
  logined: boolean;
  customerInfo: any;
  goLogin: () => void;
}

步骤2:修改函数签名

ts 复制代码
// 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:在应用层注入

ts 复制代码
// 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);

六、检查清单

在添加新的包间通信时,请检查:

  • 是否遵循依赖层次原则?
  • 是否避免了循环依赖?
  • 是否使用了合适的通信模式?
  • 共享类型是否在 shared 包中?
  • 是否建立了公共 API?
  • 是否避免了直接访问内部实现?
  • 是否避免了直接依赖 stores?
  • 事件监听器是否及时清理?

七、总结

正确的包间通信应该是:

  1. 遵循依赖层次:基础包不依赖业务包

  2. 使用合适的通信模式:根据场景选择事件、服务、类型等

  3. 建立公共 API:通过 index.ts 导出,隐藏内部实现

  4. 共享类型和常量:在 shared 包中定义

  5. 解耦 stores:通过依赖注入或接口

  6. 及时清理资源:事件监听器等

遵循这些原则,可以确保包间的松耦合、高内聚,提高代码的可维护性和可测试性。

相关推荐
Dr_哈哈2 分钟前
Node.js fs 与 path 完全指南
前端
啊花是条龙7 分钟前
《产品经理说“Tool 分组要一条会渐变的彩虹轴,还要能 zoom!”——我 3 步把它拆成 1024 个像素》
前端·javascript·echarts
C_心欲无痕8 分钟前
css - 使用@media print:打印完美网页
前端·css
青茶36023 分钟前
【js教程】如何用jq的js方法获取url链接上的参数值?
开发语言·前端·javascript
脩衜者38 分钟前
极其灵活且敏捷的WPF组态控件ConPipe 2026
前端·物联网·ui·wpf
Mike_jia43 分钟前
Dockge:轻量开源的 Docker 编排革命,让容器管理回归优雅
前端
GISer_Jing1 小时前
前端GEO优化:AI时代的SEO新战场
前端·人工智能
没想好d1 小时前
通用管理后台组件库-4-消息组件开发
前端
文艺理科生1 小时前
Google A2UI 解读:当 AI 不再只是陪聊,而是开始画界面
前端·vue.js·人工智能
silver902391 小时前
容器端口映射与存储卷管理、微服务项目管理、compose语法详解、compose项目管理、harbor仓库安装部署、harbor仓库配置管理
微服务·云原生·架构