"Толстые" контроллеры (Fat Controllers) — это антипаттерн, при котором бизнес-логика приложения сосредоточена преимущественно в контроллерах, что приводит к нескольким серьезным проблемам.
Нарушение принципа единой ответственности (SRP) Контроллер должен отвечать только за обработку HTTP-запросов и ответов, но не за бизнес-логику.
Сложность тестирования Большие монолитные функции сложнее тестировать из-за множества зависимостей.
Повторное использование кода Логика, встроенная в контроллеры, трудно переиспользуется в других частях приложения.
Сложность поддержки Большие файлы с множеством ответственностей тяжело читать и изменять.
// Плохой пример: толстый контроллер
class UserController {
async register(req, res) {
try {
// Валидация
if (!req.body.email || !req.body.password) {
return res.status(400).json({ error: 'Email and password are required' });
}
// Проверка существования пользователя
const existingUser = await User.findOne({ email: req.body.email });
if (existingUser) {
return res.status(400).json({ error: 'User already exists' });
}
// Хеширование пароля
const hashedPassword = await bcrypt.hash(req.body.password, 10);
// Создание пользователя
const user = new User({
email: req.body.email,
password: hashedPassword
});
// Сохранение в БД
await user.save();
// Генерация JWT токена
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
// Отправка welcome email
await mailService.sendWelcomeEmail(user.email);
// Логирование
logger.log(`New user registered: ${user.email}`);
// Ответ
return res.status(201).json({ token });
} catch (err) {
// Обработка ошибок
logger.error(err);
return res.status(500).json({ error: 'Something went wrong' });
}
}
}
Оптимальное решение — разделить логику на отдельные слои:
// Рефакторинг: тонкий контроллер
class UserController {
constructor(userService, authService, mailService) {
this.userService = userService;
this.authService = authService;
this.mailService = mailService;
}
async register(req, res) {
try {
const { email, password } = req.body;
const user = await this.userService.createUser(email, password);
const token = this.authService.generateToken(user.id);
await this.mailService.sendWelcomeEmail(user.email);
return res.status(201).json({ token });
} catch (err) {
return res.status(500).json({ error: err.message });
}
}
}
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async createUser(email, password) {
// Валидация могла быть вынесена в middleware
if (!email || !password) {
throw new Error('Email and password are required');
}
const existingUser = await this.userRepository.findByEmail(email);
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await bcrypt.hash(password, 10);
return this.userRepository.create({ email, password: hashedPassword });
}
}
Контроллер можно считать "толстым", если он:
"Толстые" контроллеры — это серьезный антипаттерн, который нарушает основные принципы SOLID и усложняет поддержку приложения. Решение — четкое разделение ответственностей между слоями приложения и вынесение бизнес-логики в сервисный слой.