我的文档库

Monorepo 业务域包设计:用领域边界承载可复用业务能力

文档讲解 Monorepo 业务域包,位于应用层之下,基于 DDD 限界上下文拆分领域。分为领域模型、服务、状态等七层结构,实现业务逻辑与 UI 框架解耦,规范依赖与跨域交互方案,定义目录、对外导出 API、测试策略,避免业务逻辑散落在应用内。

业务域包位于基础工具与 UI 组件之上、应用层之下,是 Monorepo 中连接“技术复用”和“业务复用”的核心层。

1. 为什么需要业务域包

如果所有业务逻辑都直接写在 App 中,多个端或多个应用很容易出现:

  • 相同业务规则重复实现;
  • API 调用方式不一致;
  • 状态模型重复;
  • 登录、支付、用户信息等核心流程在不同应用中产生差异;
  • 业务规则与 React 页面生命周期深度耦合。

业务域包的目标,是把某个业务领域中可以复用的模型、服务、状态、规则、Hooks 和业务组件沉淀为独立能力。

原方案将业务域包定义为位于“基础工具 + UI 组件”之上、“业务应用”之下的核心业务层。fileciteturn1file4L193-L217

2. 依赖关系

推荐架构:

Application

Business Domain

┌───────────────┬───────────────┐
│ UI Components │ Base Packages │
└───────────────┴───────────────┘

业务域包:

  • 可以依赖基础工具;
  • 可以依赖通用 UI;
  • 不应该依赖应用层;
  • 域与域之间尽量减少直接依赖;
  • 禁止循环依赖。

原方案明确要求业务域包只能依赖下层基础包和 UI 包,并通过事件、适配层或聚合层处理跨域交互。fileciteturn1file8L408-L437

3. 用限界上下文划分业务域

可以借鉴 DDD 的限界上下文思想:

User Domain
Payment Domain
Order Domain
Content Domain

每个域内部高内聚,域与域之间低耦合。

不要创建万能业务包

不推荐:

@org/business

里面同时放用户、支付、订单、活动和内容。

这种结构会让任何业务都可以依赖整个业务包,最终形成新的“大泥球”。

也不要过度拆分

一个小型业务域被拆成十几个包,同样会产生过多协作成本。

原方案同样强调既不要创建万能业务大包,也不要过度拆分领域。fileciteturn1file8L408-L421

4. 业务域包的七层结构

完整业务域可以拆成七层:

Domain Model

API / Service

State

Adapter / Utility

Business Hooks

Business Components

Facade

原方案将这七层分别定义为领域模型、接口服务、状态管理、工具适配、业务 Hooks、业务组件和业务门面。fileciteturn1file4L202-L209

5. 领域模型层

领域模型是业务域的基础契约。

例如用户域:

export interface User {
  id: string
  userId: number
  status: UserStatus
  createTime: string
}

export interface LoginParams {
  account: string
  password: string
}

同时可以定义:

  • Entity;
  • Value Object;
  • Enum;
  • Domain Constants;
  • Request / Response Types。

需要区分通用类型和业务类型:

@org/types
  └── IResponse / IDictionary / IPagination

@org/user-domain
  └── User / UserStatus / LoginParams

原方案也明确要求业务实体、业务状态和业务请求类型留在业务域,而通用结构放在基础类型包。fileciteturn1file7L314-L344

6. 接口服务层

接口服务层负责屏蔽后端接口细节:

export async function getCurrentUser(): Promise<User> {
  const response = await request.get<IResponse<User>>('/api/user/info')
  return response.data.data
}

它适合负责:

  • 请求参数组装;
  • 公共参数注入;
  • 响应转换;
  • 错误处理;
  • 请求缓存;
  • 超时;
  • 重试。

但业务判断不应该全部堆在 API 方法中。

原方案强调接口服务层主要承担 API 封装、协议转换和错误处理,业务判断仍然应该回到领域层。fileciteturn1file7L345-L396

7. 状态管理层

业务域包经常需要管理跨组件共享状态,例如:

  • 当前用户;
  • Token 状态;
  • 权限;
  • 业务配置;
  • 请求缓存。

轻量场景可以使用 Context + Reducer;中大型业务域可以采用 Zustand 等轻量状态管理方案。

例如:

interface UserState {
  user: User | null
  token: string | null
  setToken: (token: string) => void
  fetchUser: () => Promise<void>
  logout: () => void
}

原方案使用 Zustand 作为中大型业务状态管理示例,并覆盖持久化和异步用户信息加载。fileciteturn1file4L190-L258

8. 工具与适配层

这一层负责业务专属的数据转换,但不要把它误认为基础工具包。

例如:

formatUserStatus(status)
formatOrderAmount(amount)
mapPaymentResponse(response)

这些函数已经包含业务语义,因此应该留在对应业务域中。

原方案将业务格式化、后端数据转换、表单参数转换和权限判断等能力归入业务域的横向工具与适配层。fileciteturn0file3L258-L297

9. 业务 Hooks

业务 Hooks 是领域能力面向 React 的适配层。

例如:

useCurrentUser()
useLogin()
usePermission()
usePaymentStatus()

它们可以组合:

  • API;
  • Store;
  • Domain Logic;
  • UI 状态。

但核心规则不应该只存在于 Hook 中。

推荐:

Domain Logic

Business Hook

React Component

而不是:

React Component

所有业务逻辑

10. 业务组件层

业务组件是领域能力的 UI 化表达,例如:

  • UserProfileCard;
  • PermissionSelector;
  • PaymentMethodDialog;
  • OrderStatusBadge。

与通用 UI 组件的区别是:

UI Package
    Button
    Dialog
    Form

Business Domain
    PaymentDialog
    UserProfileCard

业务组件可以依赖通用 UI,但通用 UI 不能反过来依赖业务组件。

11. 业务门面层

当一个业务域内部存在多个子模块时,可以通过 Facade 暴露统一入口:

export const userService = {
  login,
  logout,
  getCurrentUser,
  hasPermission,
}

Facade 的目标是减少调用方对内部结构的感知。

如果 Facade 开始承担大量跨领域协调逻辑,应进一步考虑应用编排层或专门的聚合层,而不是继续膨胀业务域包。

12. 跨域依赖如何处理

假设:

Payment → User
User → Payment

这就是典型的循环依赖风险。

可以考虑:

方案一:提取公共能力

Payment ─┐
         ├──► Common
User ────┘

方案二:接口倒置

由更低层定义接口,上层提供实现。

方案三:事件驱动

UserLoggedIn
PaymentSessionCreated
OrderCompleted

方案四:应用层编排

让 App 负责协调两个业务域,而不是让两个域互相依赖。

原方案建议使用事件、适配层或聚合层解决跨域交互,并要求从根源消除循环依赖。fileciteturn1file8L415-L421

13. 业务域与 UI 的边界

业务逻辑必须尽可能脱离 React:

Domain Logic

Service / Store

Hooks

Business Components

例如权限判断:

function canAccess(permission: Permission, user: User): boolean {
  // domain logic
}

React Hook 只是调用:

const allowed = usePermission(permission)

这样同一套业务逻辑可以用于:

  • Web;
  • SSR;
  • Node.js 脚本;
  • 自动化测试;
  • 其他客户端。

原方案明确要求核心业务逻辑与 React 解耦,Hooks 和业务组件只作为 UI 层封装。fileciteturn1file8L430-L437

14. 标准目录结构

packages/user-domain/
├── src/
│   ├── index.ts
│   ├── domain/
│   │   ├── types.ts
│   │   ├── enums.ts
│   │   └── rules.ts
│   ├── services/
│   │   └── userApi.ts
│   ├── store/
│   │   └── userStore.ts
│   ├── adapters/
│   ├── hooks/
│   ├── components/
│   └── facade/
├── __tests__/
├── package.json
├── tsconfig.json
└── README.md

目录不是绝对规则,核心是让领域模型、基础设施、React 封装和业务组件保持清晰边界。

15. 对外 API

公共入口建议只从 index.ts 导出:

export type { User, LoginParams } from './domain/types'
export { UserStatus } from './domain/enums'
export { login, getCurrentUser } from './services/userApi'
export { useCurrentUser } from './hooks/useCurrentUser'
export { UserProfileCard } from './components/UserProfileCard'

这样调用方不需要知道内部目录结构。

16. 业务域包的红线

以下内容通常不应该进入业务域包:

  • 通用日期工具;
  • 通用对象工具;
  • 通用 Button;
  • 通用 Dialog;
  • 通用 HTTP Client;
  • 其他领域的核心业务逻辑;
  • 应用路由;
  • 页面级组合逻辑。

它们分别应该放在基础工具、UI 包、其他业务域或 App 层。

17. 测试策略

建议按照七层分别测试:

层级重点
Domain业务规则、边界状态
Service参数转换、响应处理、异常
Store状态转换、持久化、缓存
Adapter数据映射
HooksReact 生命周期和异步状态
Components交互和展示
Facade跨模块组合

其中领域层测试应尽量不依赖浏览器和 React,以提高执行速度和稳定性。

18. 典型业务域

可以根据实际产品拆分:

通用支撑域

  • User;
  • Auth;
  • PWA;
  • Service Worker。

核心业务域

  • Payment;
  • Payout;
  • Machine / Vendor;
  • Order;
  • Content。

原方案也将用户、认证、PWA、Service Worker 作为通用支撑域,并将支付、提现、机台等归入核心业务域示例。fileciteturn1file8L438-L447

19. 总结

业务域包的关键不是“把业务代码移动到 packages”,而是建立真正的领域边界:

模型定义业务语言,Service 屏蔽接口细节,Store 管理状态,Domain Logic 承载规则,Hooks 与组件负责框架适配,Facade 提供稳定入口。

这样可以让业务能力从具体页面中解耦出来,并在多个应用之间持续复用。

本页目录