Command Query Responsibility Segregation (CQRS) — это архитектурный паттерн, разделяющий операции изменения состояния приложения (команды) и операции чтения данных (запросы). Основная идея заключается в том, чтобы обеспечить независимое масштабирование, оптимизацию и управление логикой для команд и запросов.
CQRS особенно полезен в сложных системах с высокими требованиями к производительности, согласованности и масштабируемости.
Команда (Command) Команда описывает действие, которое изменяет состояние системы. Она является императивной, имеет цель и может содержать необходимые параметры для выполнения операции. Команды не возвращают данных, кроме, возможно, статуса выполнения.
Пример структуры команды в Jav * aScript:
class CreateUserCommand {
constructor({ id, name, email }) {
this.id = id;
this.name = name;
this.email = email;
}
}
Запрос (Query) Запрос предназначен исключительно для получения данных. Он не должен изменять состояние системы. Запросы часто оптимизируются под чтение, используют кэширование и специализированные структуры данных.
Пример запроса:
class GetUserByIdQuery {
constructor(userId) {
this.userId = userId;
}
}
CQRS предполагает наличие обработчиков для команд и запросов. Обработчики инкапсулируют логику выполнения и позволяют разделять ответственность:
class CreateUserHandler {
constructor(userRepository) {
this.userRepository = userRepository;
}
execute(command) {
const { id, name, email } = command;
const user = { id, name, email };
this.userRepository.save(user);
}
}
class GetUserByIdHandler {
constructor(userRepository) {
this.userRepository = userRepository;
}
execute(query) {
return this.userRepository.findById(query.userId);
}
}
Inferno — это высокопроизводительный UI-фреймворк для JavaScript, ориентированный на реактивное обновление интерфейса. CQRS в контексте Inferno позволяет отделить управление состоянием приложения от отрисовки компонентов, повышая предсказуемость и тестируемость.
Компоненты Inferno могут генерировать команды в ответ на пользовательские действия, а обработчики команд изменяют состояние глобального хранилища:
import { useState } from 'inferno-hooks';
function UserForm({ commandBus }) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const submit = () => {
commandBus.execute(new CreateUserCommand({ id: Date.now(), name, email }));
};
return (
<form onSub mit={e => { e.preventDefault(); submit(); }}>
<input value={name} onIn put={e => setName(e.target.value)} placeholder="Name" />
<input value={email} onIn put={e => setEmail(e.target.value)} placeholder="Email" />
<button type="submit">Create User</button>
</form>
);
}
Компоненты используют обработчики запросов для получения актуального состояния:
function UserList({ queryBus }) {
const users = queryBus.execute(new GetAllUsersQuery());
return (
<ul>
{users.map(user => <li key={user.id}>{user.name} ({user.email})</li>)}
</ul>
);
}
Event Sourcing Часто CQRS сочетается с Event Sourcing — хранением всех событий, изменяющих состояние системы. Каждая команда генерирует событие, которое затем обновляет состояние хранилища. Это позволяет:
Пример генерации события при выполнении команды:
class UserCreatedEvent {
constructor({ id, name, email }) {
this.id = id;
this.name = name;
this.email = email;
this.timestamp = Date.now();
}
}
Глобальное состояние можно реализовать через простую шину команд и запросов:
class Bus {
constructor() {
this.handlers = new Map();
}
register(type, handler) {
this.handlers.set(type, handler);
}
execute(message) {
const handler = this.handlers.get(message.constructor);
return handler.execute(message);
}
}
const commandBus = new Bus();
commandBus.register(CreateUserCommand, new CreateUserHandler(userRepository));
const queryBus = new Bus();
queryBus.register(GetAllUsersQuery, new GetAllUsersHandler(userRepository));
Такой подход обеспечивает единую точку входа для команд и запросов, упрощая поддержку и масштабирование приложения.
CQRS в сочетании с Inferno позволяет построить реактивное, масштабируемое и легко поддерживаемое веб-приложение с четким разграничением ответственности между изменением состояния и чтением данных.