mcpField
M
mvc-architecture

mvc-architecture

github.com/ds-codi开发40 次安装
代码仓库

关于 mvc-architecture

mvc-architecture 提供使用 Model-View-Controller 模式组织和维护代码的指南,涵盖各层职责、数据流、测试、反模式及从现有代码库迁移。

主要功能

  1. 01Model、View 和 Controller 职责
  2. 02目录结构指引
  3. 03分层数据流建议
  4. 04各层测试策略
  5. 05迁移步骤与反模式

适用场景

  1. 01规划 MVC 代码库结构
  2. 02将业务逻辑与 UI 组件分离
  3. 03独立测试各层
  4. 04将现有代码库迁移到 MVC

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ds-codi/project-memory-mcp --skill mvc-architecture

安装后在 Claude Code 中输入/mvc-architecture确认已加载。

详细介绍

MVC 架构指南

本文档提供了在代码库中实现和维护模型-视图-控制器(MVC)架构的指南。

核心原则

1. 分离关注点
  • 模型:数据结构、业务逻辑和状态管理
  • 视图:UI 组件和呈现逻辑
  • 控制器:请求处理、协调和数据流
2. 目录结构
src/
├── models/           # Data models and business logic
│   ├── types/       # Type definitions
│   ├── entities/    # Domain entities
│   ├── services/    # Business logic services
│   └── repositories/ # Data access layer
│
├── views/            # UI components
│   ├── components/  # Reusable UI components
│   ├── pages/       # Page-level components
│   ├── layouts/     # Layout components
│   └── styles/      # Stylesheets
│
├── controllers/      # Request handlers and orchestration
│   ├── api/         # API route handlers
│   ├── hooks/       # React hooks (for React apps)
│   └── middleware/  # Request middleware
│
└── utils/            # Shared utilities
    ├── helpers/     # Helper functions
    └── constants/   # Application constants

模型层

职责
  • 定义数据结构和类型
  • 实现业务规则和验证
  • 处理数据持久化和检索
  • 管理应用程序状态
最佳实践
// models/entities/User.ts
export interface User {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
}

// models/services/UserService.ts
export class UserService {
  constructor(private repository: UserRepository) {}

  async createUser(data: CreateUserDTO): Promise<User> {
    // Business logic validation
    if (!this.isValidEmail(data.email)) {
      throw new ValidationError('Invalid email');
    }
    return this.repository.create(data);
  }

  private isValidEmail(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
}

// models/repositories/UserRepository.ts
export interface UserRepository {
  create(data: CreateUserDTO): Promise<User>;
  findById(id: string): Promise<User | null>;
  update(id: string, data: UpdateUserDTO): Promise<User>;
  delete(id: string): Promise<void>;
}
指南
  • 保持模型纯粹,不包含 UI 逻辑
  • 使用依赖注入来提高可测试性
  • 使用仓库模式访问数据
  • 为所有服务定义清晰的接口

视图层

职责
  • 渲染 UI 组件
  • 处理用户交互
  • 显示模型中的数据
  • 仅管理组件本地状态
最佳实践
// views/components/UserCard.tsx
interface UserCardProps {
  user: User;
  onEdit: () => void;
  onDelete: () => void;
}

export function UserCard({ user, onEdit, onDelete }: UserCardProps) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <div className="actions">
        <button onClick={onEdit}>Edit</button>
        <button onClick={onDelete}>Delete</button>
      </div>
    </div>
  );
}

// views/pages/UsersPage.tsx
export function UsersPage() {
  const { users, isLoading, error, deleteUser } = useUsers();

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;

  return (
    <div className="users-page">
      <h1>Users</h1>
      <UserList users={users} onDelete={deleteUser} />
    </div>
  );
}
指南
  • 保持组件小巧且职责明确
  • 优先使用组合而非继承
  • Props 应保持不可变
  • 避免在视图中编写业务逻辑
  • 适当时使用展示层/容器模式

控制器层

职责
  • 处理传入请求
  • 协调模型和视图
  • 管理数据流
  • 处理错误和边界情况
最佳实践
// controllers/hooks/useUsers.ts
export function useUsers() {
  const [users, setUsers] = useState<User[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetchUsers();
  }, []);

  const fetchUsers = async () => {
    try {
      setIsLoading(true);
      const data = await userService.getAll();
      setUsers(data);
    } catch (err) {
      setError(err as Error);
    } finally {
      setIsLoading(false);
    }
  };

  const deleteUser = async (id: string) => {
    await userService.delete(id);
    setUsers(users.filter(u => u.id !== id));
  };

  return { users, isLoading, error, deleteUser, refetch: fetchUsers };
}

// controllers/api/usersController.ts (for backend)
export class UsersController {
  constructor(private userService: UserService) {}

  async getAll(req: Request, res: Response) {
    try {
      const users = await this.userService.getAll();
      res.json(users);
    } catch (error) {
      res.status(500).json({ error: 'Failed to fetch users' });
    }
  }

  async create(req: Request, res: Response) {
    try {
      const user = await this.userService.createUser(req.body);
      res.status(201).json(user);
    } catch (error) {
      if (error instanceof ValidationError) {
        res.status(400).json({ error: error.message });
      } else {
        res.status(500).json({ error: 'Failed to create user' });
      }
    }
  }
}
指南
  • 控制器应保持精简——将工作委托给服务
  • 处理所有错误情况
  • 将输入传递给模型前先进行验证
  • 使用中间件处理横切关注点

数据流

User Action → View → Controller → Model → Controller → View → Updated UI
     │           │         │          │         │          │
     │           │         │          │         │          └─ Re-render
     │           │         │          │         └─ Update state
     │           │         │          └─ Business logic
     │           │         └─ Handle request
     │           └─ Event handler
     └─ Click/Input

测试策略

模型测试
describe('UserService', () => {
  it('should validate email before creating user', async () => {
    const service = new UserService(mockRepository);
    await expect(service.createUser({ email: 'invalid' }))
      .rejects.toThrow('Invalid email');
  });
});
视图测试
describe('UserCard', () => {
  it('should display user information', () => {
    render(<UserCard user={mockUser} onEdit={jest.fn()} onDelete={jest.fn()} />);
    expect(screen.getByText(mockUser.name)).toBeInTheDocument();
  });
});
控制器测试
describe('useUsers', () => {
  it('should fetch users on mount', async () => {
    const { result } = renderHook(() => useUsers());
    await waitFor(() => expect(result.current.isLoading).toBe(false));
    expect(result.current.users).toHaveLength(2);
  });
});

应避免的常见反模式

❌ 在视图中编写业务逻辑
// BAD
function UserList({ users }) {
  const activeUsers = users.filter(u => u.status === 'active' && u.lastLogin > Date.now() - 86400000);
  // ...
}
✅ 移至模型/服务
// GOOD
function UserList({ users }) {
  const activeUsers = userService.getActiveUsers(users);
  // ...
}
❌ 在视图中直接访问数据
// BAD
function Dashboard() {
  const [data, setData] = useState([]);
  useEffect(() => {
    fetch('/api/data').then(r => r.json()).then(setData);
  }, []);
}
✅ 使用控制器/Hook
// GOOD
function Dashboard() {
  const { data, isLoading } = useDashboardData();
}

迁移策略

如果要将现有代码库转换为 MVC:

  1. 识别各层:将现有代码映射到 M、V 或 C
  2. 先提取模型:提取数据类型和业务逻辑
  3. 创建控制器:将现有的数据获取逻辑封装到 Hook/控制器中
  4. 精简视图:从组件中移除业务逻辑
  5. 添加测试:为每一层独立编写测试

总结

层包含内容依赖项测试方式
模型业务逻辑、类型、服务无(纯粹)单元测试
视图UI 组件、样式仅 Props组件测试
控制器Hook、处理程序、中间件模型、外部 API集成测试

评论

支持 Markdown