Monorepo 业务域包设计:用领域边界承载可复用业务能力
文档讲解 Monorepo 业务域包,位于应用层之下,基于 DDD 限界上下文拆分领域。分为领域模型、服务、状态等七层结构,实现业务逻辑与 UI 框架解耦,规范依赖与跨域交互方案,定义目录、对外导出 API、测试策略,避免业务逻辑散落在应用内。
业务域包位于基础工具与 UI 组件之上、应用层之下,是 Monorepo 中连接“技术复用”和“业务复用”的核心层。
1. 为什么需要业务域包
如果所有业务逻辑都直接写在 App 中,多个端或多个应用很容易出现:
- 相同业务规则重复实现;
- API 调用方式不一致;
- 状态模型重复;
- 登录、支付、用户信息等核心流程在不同应用中产生差异;
- 业务规则与 React 页面生命周期深度耦合。
业务域包的目标,是把某个业务领域中可以复用的模型、服务、状态、规则、Hooks 和业务组件沉淀为独立能力。
原方案将业务域包定义为位于“基础工具 + UI 组件”之上、“业务应用”之下的核心业务层。fileciteturn1file4L193-L217
2. 依赖关系
推荐架构:
Application
↓
Business Domain
↓
┌───────────────┬───────────────┐
│ UI Components │ Base Packages │
└───────────────┴───────────────┘业务域包:
- 可以依赖基础工具;
- 可以依赖通用 UI;
- 不应该依赖应用层;
- 域与域之间尽量减少直接依赖;
- 禁止循环依赖。
原方案明确要求业务域包只能依赖下层基础包和 UI 包,并通过事件、适配层或聚合层处理跨域交互。fileciteturn1file8L408-L437
3. 用限界上下文划分业务域
可以借鉴 DDD 的限界上下文思想:
User Domain
Payment Domain
Order Domain
Content Domain每个域内部高内聚,域与域之间低耦合。
不要创建万能业务包
不推荐:
@org/business里面同时放用户、支付、订单、活动和内容。
这种结构会让任何业务都可以依赖整个业务包,最终形成新的“大泥球”。
也不要过度拆分
一个小型业务域被拆成十几个包,同样会产生过多协作成本。
原方案同样强调既不要创建万能业务大包,也不要过度拆分领域。fileciteturn1file8L408-L421
4. 业务域包的七层结构
完整业务域可以拆成七层:
Domain Model
↓
API / Service
↓
State
↓
Adapter / Utility
↓
Business Hooks
↓
Business Components
↓
Facade原方案将这七层分别定义为领域模型、接口服务、状态管理、工具适配、业务 Hooks、业务组件和业务门面。fileciteturn1file4L202-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原方案也明确要求业务实体、业务状态和业务请求类型留在业务域,而通用结构放在基础类型包。fileciteturn1file7L314-L344
6. 接口服务层
接口服务层负责屏蔽后端接口细节:
export async function getCurrentUser(): Promise<User> {
const response = await request.get<IResponse<User>>('/api/user/info')
return response.data.data
}它适合负责:
- 请求参数组装;
- 公共参数注入;
- 响应转换;
- 错误处理;
- 请求缓存;
- 超时;
- 重试。
但业务判断不应该全部堆在 API 方法中。
原方案强调接口服务层主要承担 API 封装、协议转换和错误处理,业务判断仍然应该回到领域层。fileciteturn1file7L345-L396
7. 状态管理层
业务域包经常需要管理跨组件共享状态,例如:
- 当前用户;
- Token 状态;
- 权限;
- 业务配置;
- 请求缓存。
轻量场景可以使用 Context + Reducer;中大型业务域可以采用 Zustand 等轻量状态管理方案。
例如:
interface UserState {
user: User | null
token: string | null
setToken: (token: string) => void
fetchUser: () => Promise<void>
logout: () => void
}原方案使用 Zustand 作为中大型业务状态管理示例,并覆盖持久化和异步用户信息加载。fileciteturn1file4L190-L258
8. 工具与适配层
这一层负责业务专属的数据转换,但不要把它误认为基础工具包。
例如:
formatUserStatus(status)
formatOrderAmount(amount)
mapPaymentResponse(response)这些函数已经包含业务语义,因此应该留在对应业务域中。
原方案将业务格式化、后端数据转换、表单参数转换和权限判断等能力归入业务域的横向工具与适配层。fileciteturn0file3L258-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 负责协调两个业务域,而不是让两个域互相依赖。
原方案建议使用事件、适配层或聚合层解决跨域交互,并要求从根源消除循环依赖。fileciteturn1file8L415-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 层封装。fileciteturn1file8L430-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 | 数据映射 |
| Hooks | React 生命周期和异步状态 |
| Components | 交互和展示 |
| Facade | 跨模块组合 |
其中领域层测试应尽量不依赖浏览器和 React,以提高执行速度和稳定性。
18. 典型业务域
可以根据实际产品拆分:
通用支撑域
- User;
- Auth;
- PWA;
- Service Worker。
核心业务域
- Payment;
- Payout;
- Machine / Vendor;
- Order;
- Content。
原方案也将用户、认证、PWA、Service Worker 作为通用支撑域,并将支付、提现、机台等归入核心业务域示例。fileciteturn1file8L438-L447
19. 总结
业务域包的关键不是“把业务代码移动到 packages”,而是建立真正的领域边界:
模型定义业务语言,Service 屏蔽接口细节,Store 管理状态,Domain Logic 承载规则,Hooks 与组件负责框架适配,Facade 提供稳定入口。
这样可以让业务能力从具体页面中解耦出来,并在多个应用之间持续复用。
Monorepo UI 组件包设计:从设计系统到可维护组件库
该文档阐述 Monorepo UI 组件包设计,分为设计基础、原子、复合组件、UI 工具四层。遵循零业务耦合、主题化等原则,依托 Design Tokens 构建视觉体系,规范表单、浮层、图标实现,配套构建、Storybook 文档、测试,厘清通用 UI 与业务组件边界。
Monorepo 基础工具包设计:构建稳定、无业务耦合的公共底座
介绍 Monorepo 底层基础工具包,要求零业务耦合、单向依赖。拆分为 types、utils、http、storage 等多个子包,明确各包职责与边界,强调纯函数、强类型、Tree‑Shaking,规范目录、测试、构建发布,给出新增工具包的判定标准。