deotalandAi/openspec/changes/monorepo-architecture-design/design.md

578 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Monorepo架构技术设计文档
## 架构概览
本设计文档详细描述了DeotalandAi项目从分散的独立项目向Monorepo架构迁移的技术实施方案。
## 技术选型分析
### 包管理器选择: pnpm vs Yarn vs npm
#### 选择: pnpm
**原因:**
- **磁盘空间优化**: 硬链接和符号链接减少80%磁盘占用
- **速度快**: 并行安装比npm快2-3倍
- **工作空间支持**: 原生Monorepo支持配置简洁
- **依赖隔离**: 每个包独立node_modules避免依赖污染
- **严格模式**: 防止"吊死依赖"问题
#### 对比分析:
```
pnpm:
✅ 安装速度快
✅ 磁盘占用少
✅ 工作空间原生支持
✅ 依赖严格模式
❌ 相对较新,生态还在完善
Yarn Workspaces:
✅ 成熟稳定
✅ 社区支持好
❌ 安装速度中等
❌ 磁盘占用大
npm Workspaces:
✅ Node.js原生支持
❌ 功能相对简单
❌ 性能不如pnpm
```
### 构建工具选择: Turborepo vs Lerna vs Nx
#### 选择: Turborepo
**原因:**
- **任务管道**: 声明式任务依赖图,易于理解
- **缓存机制**: 本地和远程缓存,显著提升构建速度
- **增量构建**: 只重新构建变更的包
- **简单配置**: JSON配置学习成本低
- **Vercel支持**: 来自Vercel现代化工具链
#### 任务管道设计:
```json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "build/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
```
## 项目结构设计
### 目录结构详解
```
DeotalandAi/
├── package.json # 根级别项目配置
├── pnpm-workspace.yaml # pnpm工作空间定义
├── turbo.json # Turborepo任务配置
├── .npmrc # npm配置使用pnpm时
├── .eslintrc.base.json # 基础ESLint配置
├── .prettierrc # Prettier配置
├── .prettierrc # Prettier配置
├── .gitignore # Git忽略规则
├── README.md # 项目总体说明
├── CHANGELOG.md # 版本变更记录
├── apps/ # 应用程序
│ ├── frontend/ # 主前端应用
│ │ ├── package.json
│ │ ├── vite.config.js
│ │ ├── index.html
│ │ └── src/
│ │ ├── main.ts
│ │ ├── App.vue
│ │ ├── components/
│ │ ├── views/
│ │ ├── stores/
│ │ └── router/
│ └── designer/ # 设计器应用
│ ├── package.json
│ │ ├── vite.config.js
│ │ ├── index.html
│ │ └── src/
├── packages/ # 共享包
│ ├── ui/ # UI组件库
│ │ ├── package.json
│ │ ├── vite.config.js
│ │ ├── src/
│ │ │ ├── index.js # 统一导出
│ │ │ ├── components/ # Vue组件
│ │ │ ├── styles/ # 样式文件
│ │ │ └── composables/ # 组合式函数
│ │ └── stories/ # Storybook文档
│ ├── utils/ # 工具库
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── api/ # API相关工具
│ │ │ ├── format/ # 格式化工具
│ │ │ ├── validate/ # 验证工具
│ │ │ └── storage/ # 存储工具
│ │ └── tests/
│ └── config/ # 配置文件包
│ ├── package.json
│ ├── src/
│ │ ├── index.js
│ │ ├── vite.js # Vite配置
│ │ └── eslint.js # ESLint配置
├── scripts/ # 构建和部署脚本
│ ├── build.js
│ ├── dev.js
│ ├── deploy.js
│ └── utils.js
└── docs/ # 项目文档
├── architecture.md
├── contributing.md
└── guides/
```
### 工作空间配置详解
#### pnpm-workspace.yaml
```yaml
packages:
- 'apps/*'
- 'packages/*'
- '!apps/*/dist/**'
- '!apps/*/.vite/**'
```
#### package.json (根级别)
```json
{
"name": "deotalandai-monorepo",
"private": true,
"version": "1.0.0",
"description": "DeotalandAI Monorepo with Vue3, Element Plus, and shared components",
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test",
"clean": "turbo run clean && rimraf node_modules",
"type-check": "turbo run type-check",
"storybook": "turbo run storybook",
"deploy": "turbo run deploy"
},
"devDependencies": {
"turbo": "^1.10.0",
"concurrently": "^8.2.0",
"rimraf": "^5.0.0",
"eslint": "^8.45.0",
"prettier": "^3.0.0",
"@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.2.2"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
}
}
```
## 共享组件设计
### UI组件库架构
#### 组件分类体系
```
packages/ui/src/components/
├── base/ # 基础组件
│ ├── Button/
│ ├── Input/
│ ├── Card/
│ ├── Modal/
│ └── Loading/
├── layout/ # 布局组件
│ ├── Header/
│ ├── Sidebar/
│ ├── Footer/
│ └── Container/
├── business/ # 业务组件
│ ├── AgentCard/
│ ├── OrderCard/
│ ├── Gallery/
│ └── PurchaseModal/
└── form/ # 表单组件
├── Form/
├── FormItem/
├── Upload/
└── Select/
```
#### 主题系统设计
```typescript
// packages/ui/src/styles/theme.ts
export const theme = {
colors: {
primary: '#6B46C1',
secondary: '#8B5CF6',
success: '#10B981',
warning: '#F59E0B',
error: '#EF4444',
info: '#3B82F6'
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px'
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '12px',
full: '9999px'
},
typography: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace']
},
fontSize: {
xs: '12px',
sm: '14px',
md: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px'
}
}
}
```
#### 组件使用示例
```vue
<!-- apps/frontend/src/components/AgentCard.vue -->
<template>
<UiCard class="agent-card">
<template #header>
<UiCardHeader>
<UiAgentCard :agent="agent" />
</UiCardHeader>
</template>
<UiCardContent>
<p>{{ agent.description }}</p>
</UiCardContent>
<template #footer>
<UiButton @click="onSelect">
{{ $t('common.select') }}
</UiButton>
</template>
</UiCard>
</template>
<script setup lang="ts">
import { UiCard, UiButton, UiAgentCard } from '@deotalandai/ui'
import type { Agent } from '@deotalandai/types'
interface Props {
agent: Agent
}
const props = defineProps<Props>()
const emit = defineEmits<{
select: [agent: Agent]
}>()
const onSelect = () => {
emit('select', props.agent)
}
</script>
```
### 工具库设计
#### API工具模块
```javascript
// packages/utils/src/api/request.js
import axios from 'axios'
export class ApiClient {
constructor(config) {
this.instance = axios.create(config)
this.setupInterceptors()
}
setupInterceptors() {
this.instance.interceptors.request.use(
(config) => {
// 添加认证token
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => Promise.reject(error)
)
this.instance.interceptors.response.use(
(response) => response.data,
(error) => {
// 统一错误处理
return Promise.reject(this.handleError(error))
}
)
}
handleError(error) {
if (error.response) {
const { status, data } = error.response
return {
status,
message: data.message || '请求失败',
code: data.code
}
}
return {
status: 0,
message: error.message || '网络错误',
code: -1
}
}
async get(url, params) {
return this.instance.get(url, { params })
}
async post(url, data) {
return this.instance.post(url, data)
}
async put(url, data) {
return this.instance.put(url, data)
}
async delete(url) {
return this.instance.delete(url)
}
}
export const createApiClient = (baseURL) => {
return new ApiClient({ baseURL })
}
```
#### 国际化工具
```typescript
// packages/utils/src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import type { App } from 'vue'
export const setupI18n = (app: App, locale: string, messages: Record<string, any>) => {
const i18n = createI18n({
legacy: false,
locale,
fallbackLocale: 'en',
messages
})
app.use(i18n)
return i18n
}
export const formatDate = (date: Date, locale: string = 'zh-CN') => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
export const formatCurrency = (amount: number, currency: string = 'CNY', locale: string = 'zh-CN') => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(amount)
}
```
### 类型定义设计
#### API类型定义
```typescript
// packages/types/src/api.ts
export interface ApiResponse<T = any> {
code: number
message: string
data: T
success: boolean
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number
pageSize: number
total: number
totalPages: number
}
}
export interface Agent {
id: string
name: string
description: string
avatar: string
category: string
tags: string[]
created_at: string
updatedAt: string
}
export interface Order {
id: string
agentId: string
userId: string
status: 'pending' | 'processing' | 'completed' | 'failed'
amount: number
currency: string
created_at: string
updatedAt: string
}
```
#### 组件Props类型
```typescript
// packages/types/src/components.ts
import type { Agent, Order } from './api'
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
block?: boolean
}
export interface CardProps {
shadow?: 'none' | 'sm' | 'md' | 'lg'
border?: boolean
padding?: 'none' | 'sm' | 'md' | 'lg'
}
export interface AgentCardProps {
agent: Agent
selectable?: boolean
showActions?: boolean
}
export interface ModalProps {
visible: boolean
title?: string
width?: string | number
confirmText?: string
cancelText?: string
maskClosable?: boolean
}
```
## 开发工具配置
### VS Code工作区配置
```json
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.preferences.importModuleSpecifier": "relative",
"emmet.includeLanguages": {
"vue": "html"
},
"files.associations": {
"*.vue": "vue"
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/build": true,
"**/.turbo": true
}
}
```
### 调试配置
```json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Frontend Dev",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/vite",
"args": ["--config", "apps/frontend/vite.config.ts"],
"cwd": "${workspaceFolder}/apps/frontend",
"console": "integratedTerminal"
}
]
}
```
## 性能优化策略
### 构建优化
1. **依赖预构建**: 使用vite的依赖预构建
2. **代码分割**: 每个应用独立打包
3. **Tree Shaking**: 消除未使用的代码
4. **缓存策略**: 利用Turborepo缓存
### 开发优化
1. **热模块替换**: 跨包的热更新
2. **增量构建**: 只构建变更的包
3. **并行开发**: 同时启动多个应用
4. **类型检查**: 增量TypeScript检查
### 部署优化
1. **CDN分发**: 静态资源CDN部署
2. **压缩优化**: Gzip/Brotli压缩
3. **缓存头**: 合理的缓存策略
4. **域名分片**: 多个域名提升加载速度
## 迁移风险与缓解
### 技术风险
1. **构建复杂度**: 复杂的工作空间依赖图
- **缓解**: 使用Turborepo简化配置
2. **依赖冲突**: 不同包的依赖版本冲突
- **缓解**: 使用pnpm的严格模式
3. **构建时间**: 大型Monorepo构建时间长
- **缓解**: 利用Turborepo缓存和增量构建
### 团队风险
1. **学习成本**: 团队需要学习新的工作方式
- **缓解**: 提供详细的文档和培训
2. **工具兼容性**: 现有工具可能不支持Monorepo
- **缓解**: 选择兼容性好的工具
3. **CI/CD复杂性**: 流水线配置更复杂
- **缓解**: 提供完整的CI/CD模板
---
**设计版本**: 1.0
**创建时间**: 2025-11-18
**技术负责人**:
**审核状态**: 待审核