如何将MongoDB驱动的MERN后端CRUD改造为多数据源通用架构?
改造MERN项目为多数据源通用架构的实操方案
核心思路:依赖倒置 + 仓储模式
核心是把数据操作逻辑和业务逻辑解耦——让控制器只关心业务规则,不关心数据来自MongoDB还是Monday API。具体用仓储(Repository)模式:定义统一的数据操作接口,不同数据源各自实现这个接口,控制器依赖接口而非具体的数据源实现。
具体改造步骤
1. 抽象数据访问层(Repository)
先给每个业务实体(比如Issue)定义通用的操作接口,覆盖你当前控制器里的所有数据操作:
// src/repositories/IssueRepository.ts export interface IssueRepository { getAll(): Promise<Issue[]>; getById(id: string): Promise<Issue | null>; create(issueData: CreateIssueDto): Promise<Issue>; update(id: string, updateData: UpdateIssueDto): Promise<Issue | null>; delete(id: string): Promise<boolean>; } // 统一数据格式,让控制器只处理这个格式 export interface Issue { id: string; title: string; description: string; status: string; createdAt: Date; } export interface CreateIssueDto { /* 定义创建Issue需要的字段 */ } export interface UpdateIssueDto { /* 定义更新Issue需要的字段 */ }
然后分别实现MongoDB和Monday API的仓储类:
// src/repositories/MongoIssueRepository.ts import { IssueRepository, Issue, CreateIssueDto, UpdateIssueDto } from './IssueRepository'; import IssueModel from '../models/Issue'; export class MongoIssueRepository implements IssueRepository { async getAll(): Promise<Issue[]> { const docs = await IssueModel.find(); return docs.map(doc => ({ id: doc._id.toString(), title: doc.title, description: doc.description, status: doc.status, createdAt: doc.createdAt })); } // 其他方法(getById/create/update/delete)同理,把Mongoose文档转成统一的Issue格式 }
// src/repositories/MondayIssueRepository.ts import { IssueRepository, Issue, CreateIssueDto, UpdateIssueDto } from './IssueRepository'; import axios from 'axios'; export class MondayIssueRepository implements IssueRepository { private readonly mondayApiToken = process.env.MONDAY_API_TOKEN; private readonly mondayBoardId = process.env.MONDAY_BOARD_ID; async getAll(): Promise<Issue[]> { // 调用Monday API获取数据,映射成统一的Issue格式 const response = await axios.post('https://api.monday.com/v2', { query: `query { boards(ids: ${this.mondayBoardId}) { items { id name column_values { title value } } } }` }, { headers: { 'Authorization': this.mondayApiToken } }); return response.data.data.boards[0].items.map(item => ({ id: item.id, title: item.name, description: item.column_values.find(col => col.title === 'Description')?.value || '', status: item.column_values.find(col => col.title === 'Status')?.value || '', createdAt: new Date(item.created_at) // 根据Monday API返回的实际时间字段调整 })); } // 其他方法同理,处理Monday API的增删改逻辑,映射成统一格式 }
2. 改造控制器,依赖抽象接口
原来的控制器直接调用Mongoose模型,现在改成依赖IssueRepository接口,通过工厂模式获取具体的仓储实例:
// src/controllers/issueController.ts import { Request, Response } from 'express'; import { IssueRepository } from '../repositories/IssueRepository'; import { getRepository } from '../repositories/repositoryFactory'; // 根据请求参数或配置切换数据源 const getIssueRepository = (req: Request): IssueRepository => { const source = req.query.source || process.env.DEFAULT_DATA_SOURCE; return getRepository(source as string); }; export const getAllIssues = async (req: Request, res: Response) => { try { const repo = getIssueRepository(req); const issues = await repo.getAll(); res.status(200).json(issues); } catch (error) { res.status(500).json({ message: 'Failed to fetch issues' }); } }; // 其他控制器方法(getIssueById/createIssue等)同理,都通过repo调用
3. 实现仓储工厂(切换数据源)
写一个简单的工厂函数,根据传入的数据源类型返回对应的仓储实例:
// src/repositories/repositoryFactory.ts import { IssueRepository } from './IssueRepository'; import { MongoIssueRepository } from './MongoIssueRepository'; import { MondayIssueRepository } from './MondayIssueRepository'; export const getRepository = (source: string): IssueRepository => { switch (source) { case 'mongo': return new MongoIssueRepository(); case 'monday': return new MondayIssueRepository(); default: throw new Error(`Unsupported data source: ${source}`); } };
4. 路由层适配
可以通过路由前缀或者请求参数来指定数据源:
// src/routes/issues.ts import express from 'express'; import * as issueController from '../controllers/issueController'; const router = express.Router(); // 方式1:通过路由前缀区分 router.get('/mongo', issueController.getAllIssues); router.get('/monday', issueController.getAllIssues); // 方式2:通过query参数区分 router.get('/', issueController.getAllIssues); // 比如 /api/issues?source=monday export default router;
额外注意点
- 数据格式统一:不管数据源是什么,控制器只处理你定义的
Issue格式,外部数据的映射逻辑全部放在仓储实现里,避免控制器里出现大量格式转换代码。 - 外部API适配:处理Monday API的认证、限流、错误处理(比如API返回的错误码),这些逻辑都封装在
MondayIssueRepository里,控制器不用关心。 - 配置化管理:把数据源的配置(比如MongoDB连接字符串、Monday API Token)放在
.env文件里,方便切换环境。 - 错误统一处理:可以写一个全局错误中间件,把MongoDB的Mongoose错误、Monday API的请求错误转换成统一的HTTP响应格式。
内容的提问来源于stack exchange,提问作者dani_l_n
相关产品推荐
相关产品推荐

