← Назад к вопросам
Где применяется Command Query Separation?
3.0 Senior🔥 102 комментариев
#Архитектура и паттерны
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Command Query Separation (CQS) в разработке
Command Query Separation (CQS) — архитектурный принцип, который разделяет операции на две категории:
- Command — операции, которые изменяют состояние (side effects), но не возвращают данные
- Query — операции, которые возвращают данные без изменения состояния
Основной принцип: функция должна либо изменять состояние, либо возвращать информацию, но не делать оба одновременно.
Где применяется CQS
1. Backend API и REST
Разделение методов HTTP:
// Query: GET — получить данные, не меняя состояние
GET /api/v1/users/123
GET /api/v1/posts?limit=10
// Command: POST, PUT, DELETE — изменить состояние
POST /api/v1/users (создать)
PUT /api/v1/users/123 (обновить)
DELETE /api/v1/users/123 (удалить)
Плохо: нарушение CQS
// Одна операция делает всё: возвращает и меняет
GET /api/v1/users/123/activate
// Непредсказуемо: это query или command?
Хорошо: CQS разделение
// Query: только получение
GET /api/v1/users/123
// Command: изменение
PATCH /api/v1/users/123 { "status": "active" }
2. JavaScript/TypeScript функции и методы
Query функция — возвращает данные, не меняет состояние:
class UserService {
// Query: чистая функция
getUser(id) {
return this.users.find(u => u.id === id);
}
getActiveUsers() {
return this.users.filter(u => u.active);
}
getTotalUsers() {
return this.users.length;
}
}
// Использование
const user = userService.getUser(123);
const count = userService.getTotalUsers();
Command функция — изменяет состояние, не возвращает данные:
class UserService {
// Command: меняет состояние, ничего не возвращает (или возвращает статус)
createUser(data) {
const user = { id: generateId(), ...data };
this.users.push(user);
return { success: true }; // или ничего не возвращает
}
updateUser(id, data) {
const user = this.getUser(id);
Object.assign(user, data);
return { success: true };
}
deleteUser(id) {
this.users = this.users.filter(u => u.id !== id);
return { success: true };
}
}
Плохо: нарушение CQS
// Функция и возвращает, и меняет состояние
fetchAndActivateUser(id) {
const user = this.getUser(id);
user.active = true;
this.saveUser(user);
return user; // Возвращает и меняет состояние
}
3. React компоненты и состояние
Queries в React:
function UserProfile({ userId }) {
// Query: получение данных
const [user, setUser] = useState(null);
useEffect(() => {
// Query function
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => setUser(data)); // Только setUser
}, [userId]);
return <div>{user?.name}</div>;
}
Commands в React:
function UserForm({ userId }) {
// Command: изменение данных
const handleSubmit = async (data) => {
// Command: POST/PUT запрос
const response = await fetch(`/api/users/${userId}`, {
method: 'PUT',
body: JSON.stringify(data)
});
return response.json();
};
return <form onSubmit={handleSubmit}>...</form>;
}
4. CQRS паттерн (Command Query Responsibility Segregation)
Расширенная версия CQS для сложных систем:
// Модель с разделением ответственности
class UserAggregate {
// Command handlers — меняют состояние
registerUser(command) {
// RegisterUserCommand { name, email }
const user = new User(command.name, command.email);
this.users.push(user);
// Издаем событие
this.emit(new UserRegisteredEvent(user.id, user.name));
}
updateUserProfile(command) {
// UpdateProfileCommand { userId, bio, avatar }
const user = this.getUser(command.userId);
user.bio = command.bio;
user.avatar = command.avatar;
this.emit(new UserProfileUpdatedEvent(user.id));
}
// Query handlers — возвращают данные без changes
getUserById(query) {
return this.getUser(query.userId);
}
getActiveUsers(query) {
return this.users.filter(u => u.active);
}
}
Отдельные классы для Commands и Queries:
// Command
class CreateUserCommand {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
// Query
class GetUserQuery {
constructor(userId) {
this.userId = userId;
}
}
// Handler для Command
class CreateUserCommandHandler {
handle(command) {
const user = new User(command.name, command.email);
userRepository.save(user);
return { success: true, userId: user.id };
}
}
// Handler для Query
class GetUserQueryHandler {
handle(query) {
return userRepository.findById(query.userId);
}
}
5. Форма с CQS принципом
Плохо: нарушение CQS
// Одна функция и отправляет, и получает результат с побочными эффектами
async function submitAndDisplayUser(formData) {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(formData)
});
const user = await response.json();
// Побочные эффекты
localStorage.setItem('lastUser', JSON.stringify(user));
showNotification(`Пользователь ${user.name} создан`);
redirectTo(`/users/${user.id}`);
return user; // Возвращает результат
}
Хорошо: CQS разделение
// Command: только отправить данные
async function createUser(formData) {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(formData)
});
return response.json();
}
// Query: получить созданного пользователя
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Обработчик в компоненте использует оба
async function handleFormSubmit(formData) {
// Command
const newUser = await createUser(formData);
// Query (если нужна полная информация)
const fullUser = await getUser(newUser.id);
// Побочные эффекты отделены
localStorage.setItem('lastUser', JSON.stringify(fullUser));
showNotification(`Пользователь ${fullUser.name} создан`);
redirectTo(`/users/${fullUser.id}`);
}
6. Redux/Zustand с CQS
// Command: изменение состояния
const userSlice = createSlice({
name: 'users',
initialState: { list: [] },
reducers: {
// Commands
addUser: (state, action) => {
state.list.push(action.payload);
},
updateUser: (state, action) => {
const user = state.list.find(u => u.id === action.payload.id);
if (user) Object.assign(user, action.payload);
},
deleteUser: (state, action) => {
state.list = state.list.filter(u => u.id !== action.payload);
}
},
selectors: {
// Queries
selectUsers: (state) => state.list,
selectUserById: (state, id) => state.list.find(u => u.id === id),
selectActiveUsers: (state) => state.list.filter(u => u.active)
}
});
Преимущества CQS
- Ясность и предсказуемость — понятно, что делает функция
- Легче тестировать — query функции чистые, command функции легче мокировать
- Избежание побочных эффектов — query функции без side effects
- Масштабируемость — легче оптимизировать reads и writes отдельно
- Параллелизм — query операции можно выполнять параллельно
- Документирование — имена функций ясны: getUserById vs updateUser
Когда применять CQS
- REST API — разделение GET/POST/PUT/DELETE методов
- Сложные бизнес-операции — CQRS для микросервисов
- Работа с состоянием — Redux, MobX, Zustand
- Сервисы и репозитории — Backend логика
- React компоненты — обработчики форм и запросов
Ключевые моменты
- CQS — функция либо меняет состояние, либо возвращает данные
- CQRS — расширенная версия для больших систем
- Query функции — чистые, без побочных эффектов
- Command функции — меняют состояние, минимум возвращаемых данных
- HTTP методы — GET (query), POST/PUT/DELETE (commands)
- Применение везде — API, функции, компоненты, store