您现在的位置是:网站首页 > 控制器与服务的分离文章详情
控制器与服务的分离
陈川
【
Node.js
】
13970人已围观
8490字
控制器与服务的分离
Express框架中,控制器负责处理HTTP请求和响应,而服务则专注于业务逻辑。将两者分离可以提高代码的可维护性和可测试性,避免控制器变得臃肿。这种分层架构让开发者能够更清晰地组织代码,特别是在大型项目中。
为什么需要分离控制器与服务
控制器直接与路由交互,处理请求参数、验证输入、调用服务并返回响应。如果业务逻辑直接写在控制器中,会导致控制器代码量过大,难以维护。例如,一个用户注册功能可能包含密码加密、验证邮箱、发送欢迎邮件等步骤,这些逻辑全部放在控制器中会让代码变得混乱。
服务层则专注于实现核心业务逻辑,不关心HTTP细节。它接收控制器传递的参数,执行操作并返回结果。这种分离让业务逻辑可以独立于Web框架进行测试和复用。
控制器的最佳实践
控制器应该保持精简,主要职责是:
- 解析请求参数
- 调用适当的服务
- 处理响应
// 不好的做法:业务逻辑直接放在控制器中
router.post('/users', async (req, res) => {
const { email, password } = req.body;
// 直接进行密码加密
const hashedPassword = await bcrypt.hash(password, 10);
// 直接操作数据库
const user = await User.create({ email, password: hashedPassword });
res.status(201).json(user);
});
// 好的做法:控制器只负责协调
router.post('/users', async (req, res) => {
try {
const userData = req.body;
const newUser = await userService.createUser(userData);
res.status(201).json(newUser);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
服务层的实现
服务层包含应用程序的核心逻辑,通常按业务领域组织。例如,可以有userService
、authService
、productService
等。
// services/userService.js
const bcrypt = require('bcrypt');
const User = require('../models/User');
module.exports = {
async createUser(userData) {
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = await User.create({
email: userData.email,
password: hashedPassword
});
return user;
},
async getUserById(id) {
return await User.findById(id);
}
};
分层架构的优势
- 可测试性:服务层可以独立测试,不需要模拟HTTP请求
- 可复用性:同样的服务可以被不同控制器调用
- 可维护性:业务逻辑变更只需修改服务层,不影响控制器
- 团队协作:前端和后端开发者可以并行工作,只需约定接口
实际项目中的应用
在大型项目中,可以进一步细化分层:
src/
├── controllers/ # 控制器
├── services/ # 服务层
├── repositories/ # 数据访问层
├── models/ # 数据模型
└── routes/ # 路由定义
控制器调用服务,服务调用仓储层,形成清晰的依赖关系:
// controllers/userController.js
const userService = require('../services/userService');
exports.getUserProfile = async (req, res) => {
try {
const user = await userService.getUserProfile(req.userId);
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
// services/userService.js
const userRepository = require('../repositories/userRepository');
exports.getUserProfile = async (userId) => {
return await userRepository.findById(userId);
};
// repositories/userRepository.js
const User = require('../models/User');
exports.findById = async (id) => {
return await User.findById(id).select('-password');
};
错误处理策略
分离架构下,错误处理也需要分层考虑。服务层抛出业务错误,控制器捕获并转换为适当的HTTP响应。
// services/authService.js
exports.login = async (email, password) => {
const user = await userRepository.findByEmail(email);
if (!user) {
throw new Error('用户不存在');
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('密码错误');
}
return generateToken(user);
};
// controllers/authController.js
exports.login = async (req, res) => {
try {
const token = await authService.login(req.body.email, req.body.password);
res.json({ token });
} catch (error) {
res.status(401).json({ error: error.message });
}
};
中间件的使用
Express中间件可以进一步简化控制器代码。例如,参数验证可以提取到中间件中:
// middlewares/validateRequest.js
const { body } = require('express-validator');
exports.validateCreateUser = [
body('email').isEmail(),
body('password').isLength({ min: 6 })
];
// routes/users.js
const { validateCreateUser } = require('../middlewares/validateRequest');
const userController = require('../controllers/userController');
router.post('/users', validateCreateUser, userController.createUser);
依赖注入模式
为了提高可测试性,可以采用依赖注入方式组织服务:
// services/userService.js
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async createUser(userData) {
// 实现逻辑
}
}
module.exports = UserService;
// app.js
const UserService = require('./services/userService');
const UserRepository = require('./repositories/userRepository');
const userRepository = new UserRepository();
const userService = new UserService(userRepository);
app.use('/api', createRouter({ userService }));
性能考量
分层架构虽然带来了清晰的结构,但也可能引入性能开销。需要注意:
- 避免不必要的分层调用
- 服务层可以缓存常用数据
- 复杂的业务操作可以考虑使用领域事件
// services/orderService.js
const Order = require('../models/Order');
class OrderService {
constructor(eventEmitter) {
this.eventEmitter = eventEmitter;
}
async createOrder(orderData) {
const order = await Order.create(orderData);
this.eventEmitter.emit('orderCreated', order);
return order;
}
}
与前端架构的对比
这种分层思想与前端框架中的组件/服务分离类似。例如在React中:
// React组件(类似控制器)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
userService.getUser(userId).then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
// 服务层(与后端服务类似)
const userService = {
getUser(id) {
return fetch(`/api/users/${id}`).then(res => res.json());
}
};
常见误区与解决方案
-
过度分层:简单的CRUD操作可能不需要完整的分层
- 解决方案:根据项目复杂度灵活调整
-
循环依赖:服务之间相互调用导致复杂依赖
- 解决方案:引入领域事件或命令模式
-
贫血模型:服务层变成纯粹的过程调用
- 解决方案:考虑领域驱动设计(DDD)
// 不好的贫血模型示例
class ProductService {
applyDiscount(product, discount) {
product.price = product.price * (1 - discount);
return product;
}
}
// 更好的富模型示例
class Product {
applyDiscount(discount) {
this.price = this.price * (1 - discount);
}
}
测试策略
分层架构大大简化了测试:
// 测试服务层(不需要启动Express)
describe('UserService', () => {
let userService;
beforeEach(() => {
userService = new UserService(mockUserRepository);
});
it('should create user with hashed password', async () => {
const user = await userService.createUser({
email: 'test@example.com',
password: '123456'
});
expect(user.password).not.toBe('123456');
});
});
// 测试控制器(可以模拟请求)
describe('UserController', () => {
it('should return 201 when user created', async () => {
mockUserService.createUser.mockResolvedValue({ id: 1 });
const req = { body: { email: 'test@example.com', password: '123456' } };
const res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
await userController.createUser(req, res);
expect(res.status).toHaveBeenCalledWith(201);
});
});
与微服务架构的关系
当应用发展为微服务时,服务层的概念可以自然扩展:
- 本地服务变为远程服务调用
- 控制器变为API网关
- 服务间通信使用RPC或消息队列
// 从单体架构的服务调用
const user = await userService.getUser(id);
// 变为微服务架构的调用
const user = await userServiceClient.getUser(id);
演进式架构
项目初期可以简单实现,随着复杂度增加逐步分层:
- 第一阶段:控制器直接包含业务逻辑
- 第二阶段:提取出服务层
- 第三阶段:引入领域层和仓储层
- 第四阶段:考虑CQRS或事件溯源
// 演进示例:从简单到复杂
// 1. 初期
router.post('/orders', async (req, res) => {
const order = await Order.create(req.body);
res.json(order);
});
// 2. 引入服务层
router.post('/orders', async (req, res) => {
const order = await orderService.createOrder(req.body);
res.json(order);
});
// 3. 完整分层
router.post('/orders',
validateOrder,
authMiddleware,
async (req, res) => {
try {
const order = await orderApplicationService.createOrder(
req.body,
req.user.id
);
res.json(order);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
);
框架扩展与集成
Express的灵活性允许集成各种技术栈:
// 集成GraphQL
const { graphqlHTTP } = require('express-graphql');
const userSchema = require('./graphql/userSchema');
app.use('/graphql', graphqlHTTP({
schema: userSchema,
graphiql: true
}));
// GraphQL解析器可以看作是一种特殊的服务层
const resolvers = {
Query: {
user: async (_, { id }) => {
return await userService.getUser(id);
}
}
};
性能监控与日志
分层后可以更方便地添加横切关注点:
// 使用中间件记录控制器执行时间
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.url} - ${Date.now() - start}ms`);
});
next();
});
// 服务层可以添加业务指标监控
class ProductService {
constructor(metricsClient) {
this.metrics = metricsClient;
}
async updateProduct(id, data) {
const start = Date.now();
try {
const result = await productRepository.update(id, data);
this.metrics.timing('product.update', Date.now() - start);
return result;
} catch (error) {
this.metrics.increment('product.update.error');
throw error;
}
}
}
上一篇: 路由分层与模块化设计
下一篇: 数据验证与输入过滤