Command Query Responsibility Segregation

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);
    }
}

Интеграция CQRS с фреймворком Inferno

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>
    );
}

Плюсы и особенности применения CQRS в Inferno

  • Производительность: оптимизация чтения и записи отдельно, что важно для высоконагруженных приложений.
  • Тестируемость: каждая команда и запрос изолированы, что упрощает юнит-тестирование.
  • Масштабируемость: возможность распределять обработку команд и запросов на разные сервисы или кластеры.
  • Прозрачность изменений: четкая модель потока данных делает состояние приложения предсказуемым и легко отлаживаемым.

Дополнительные механизмы

Event Sourcing Часто CQRS сочетается с Event Sourcing — хранением всех событий, изменяющих состояние системы. Каждая команда генерирует событие, которое затем обновляет состояние хранилища. Это позволяет:

  • Восстанавливать состояние системы в любой момент времени.
  • Реализовать аудит и логирование изменений.
  • Синхронизировать несколько проекций данных для запросов.

Пример генерации события при выполнении команды:

class UserCreatedEvent {
    constructor({ id, name, email }) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.timestamp = Date.now();
    }
}

Реализация CQRS в Inferno с глобальным хранилищем

Глобальное состояние можно реализовать через простую шину команд и запросов:

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 позволяет построить реактивное, масштабируемое и легко поддерживаемое веб-приложение с четким разграничением ответственности между изменением состояния и чтением данных.