Как фронтенд-разработчик, мой технический стек для клиента обычно включает React и Redux/Redux Saga. Причина, по которой я тяготел к Node.js, заключается в том, что он позволил мне использовать свои знания JavaScript на сервере. Это означало, что мне не нужно было тратить время на изучение синтаксиса таких языков, как Python или Ruby. Это также уменьшает переключение контекста, которое я имел обыкновение испытывать между PHP на бэкэнде (давным-давно!) и JavaScript на фронтенде.

Предположения

Я предполагаю, что у вас есть некоторые знания о JavaScript и Redux, поскольку я расскажу, как ваши знания о Redux можно применить к серверу Node.js. Также поможет знание Redux-Saga.

Государство без гражданства

Мне очень нравится идея сервисов без гражданства. Это означает, что у вас есть индивидуально развертываемые и масштабируемые сервисы, которые могут обрабатывать запросы, не зависящие друг от друга. Это решает многие проблемы, с которыми вы сталкиваетесь в приложениях с отслеживанием состояния или монолитных приложениях.

Мне нравится утверждать, что в службе без сохранения состояния вы все равно должны управлять каким-то состоянием. Допустим, ваш сервис использует базу данных. В момент первого запуска службы служба должна установить соединение с базой данных, прежде чем ее можно будет использовать. Это означает, что состояние службы не готово до тех пор, пока соединение с базой данных не будет установлено успешно. Вот почему я люблю говорить, что ничто не должно быть «настоящим» без состояния, потому что так или иначе вы должны управлять состоянием в своих службах без состояния; будь то большой или маленький.

Представьте, что у вас есть функция Lambda, которая должна обрабатывать сотни или даже тысячи запросов в секунду. Хотели бы вы, чтобы каждый отдельный запрос устанавливал новое соединение с вашей базой данных? Я бы не стал. Вы должны иметь возможность повторно использовать соединения между запросами, чтобы улучшить время отклика и оптимизировать нагрузку на базу данных (ваша база данных также может иметь максимальные ограничения на количество соединений).

Редукс спешит на помощь

Для управления состоянием мой любимый фреймворк — Redux. Многие любят утверждать, что это добавляет слишком много шаблонов к вашему проекту. Я думаю, что шаблон — это здорово! Это дает мне последовательную и повторяемую структуру для работы. Такая структура также помогает, когда вы работаете в команде.

Redux имеет удобную структуру для отправки действий и их обработки для обновления вашего состояния. В сочетании с Redux Logger он дает вам отличную видимость того, что происходит с вашим состоянием и где/когда в него были внесены изменения.

Редукс на сервере

Redux в основном используется на клиенте и часто сочетается с React. Точно так же, как мы можем использовать React на сервере, я хотел изучить, можно ли использовать другие технологии, в данном случае Redux, который обычно предназначен для клиента, для серверных приложений.

Установка Redux в Node.js точно такая же, как и при использовании его с React на клиенте.

npm install redux

Скорее всего, вы захотите обрабатывать асинхронные события, поэтому для этого я рекомендую добавить Redux-Saga.

npm install redux-saga

Мне также нравится использовать удобную библиотеку под названием redux-act для создания действий.

npm install redux-act

Добавить соединение с базой данных в состояние

Давайте посмотрим, как мы можем добавить состояние нашего подключения к базе данных. В этом примере я буду использовать соединение MongoDB.

У нас будет четыре файла для управления состоянием:

  • store.js
  • действия.js
  • редуктор.js
  • саги.js

Примечание: это шаблон, о котором я говорил. Может показаться, что много файлов только для управления состоянием, однако такая структура помогает, когда ваше приложение растет.

Создайте файл с именем store.js со следующим содержимым:

// store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import actions from './actions';
import sagas from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(sitesReducer, 
applyMiddleware([sagaMiddleware]));
sagaMiddleware.run(sagas);
export default store;

Создайте файл с именем actions.js:

// actions.js
import { createAction } from 'redux-act';
const connectDatabase = createAction('CONNECT_DATABASE');
const dbConnectionSuccess = createAction('DB_CONNECTION_SUCCESS');
const dbConnectionFail = createAction('DB_CONNECTION_FAIL');
export default {
    connectDatabase,
    dbConnectionSuccess,
    dbConnectionFail,
};

Создайте файл с именем reducer.js:

import { createReducer } from 'redux-act';
import actions from './actions';
const defaultState = {
    isDatabaseReady: false,
    databaseConnectionError: null,
};
export default createReducer(
    {
        [actions.dbConnectionSuccess.toString()]: (state) => {
            return { ...state, isDatabaseReady: true };
        },
        [actions.dbConnectionFail.toString()]: (state, error) => {
            return {
                ...state,
                isDatabaseReady: false,
                databaseConnectionError: error,
            };
        },
    },
    defaultState,
);

В редюсере мы просто устанавливаем для isDatabaseReady значение true или false, в зависимости от действия.

Установите драйвер MongoDB:

npm install mongodb

Создайте файл с именем sagas.js:

import { all, takeLatest, put } from 'redux-saga/effects';
import actions from './actions';
import { MongoClient } from 'mongodb';
function* connectDatabaseSaga() {
    const client = new MongoClient(process.env.MONGODB_URI);
    
    try {
        yield client.connect();
        yield put(actions.dbConnectionSuccess());
    } catch (error) {
        yield put(actions.dbConnectionFail(error));
    }
}
export default function* sagas() {
    yield all([
        takeLatest(actions.connectDatabase.getType(),
        connectDatabaseSaga),
    ]);
}

В саге мы подключаемся к нашей базе данных MongoDB. Если соединение установлено успешно, мы отправляем действие dbConnectionSuccess, в противном случае мы отправляем действие dbConnectionFail.

Теперь у вас есть состояние подключения к базе данных, к которому легко и быстро получить доступ.

Улучшенный пользовательский опыт

Поскольку теперь вы можете проверить состояние своей базы данных из хранилища избыточности без необходимости выполнять асинхронный вызов базы данных, вы можете быстро создавать собственные ответы для потребителя службы. Вы можете ответить настраиваемым кодом состояния, чтобы уведомить потребителя о том, что это действительный запрос, но необходимо повторить попытку через несколько секунд (когда база данных будет готова). На этом этапе потребитель может уведомить своего пользователя с помощью модального или всплывающего уведомления о том, что «работа занимает немного больше времени, чем ожидалось», а затем автоматически повторить тот же запрос.

Отлично подходит для фоновых задач

Управление фоновыми задачами в приложении также является хорошим вариантом использования Redux на сервере. Это дает вам удобную структуру для запуска и остановки заданий, а также для обработки ошибок и мониторинга состояния этих заданий. Наличие такого структурированного формата для управления состоянием позволяет легко сообщать эту информацию клиенту (например, на панель мониторинга).

// Dispatch a backup job
store.dispatch(actions.startJob({ type: 'backup' }));

Независимая аналитика

Поскольку структура Redux тесно связана со структурой аналитики (например, действия отправки = отправка событий), это позволяет вам использовать действия Redux для отправки событий в пакет аналитики. Это также означает, что если вы решите использовать другой пакет аналитики, код для остальной части вашего приложения не должен изменяться, поскольку он просто отправляет действия Redux.

// Dispatch analytics event
store.dispatch(actions.analytics({ name: 'event', data: 'xyz' }));

Что следует учитывать

Redux и Redux-Saga — не самые маленькие пакеты. Если ваш проект требует, чтобы вы считали каждый килобайт, т. е. вы хорошо осведомлены об общем размере вашего сервиса, то я бы предложил написать некоторую пользовательскую логику для обработки состояния. Поскольку Redux в настоящее время имеет размер 163 КБ, а Redux-Saga — 224 КБ, добавленный размер не должен быть проблемой для большинства случаев использования.

Вывод

Я думаю, что Redux — отличный инструмент для клиента, и я надеюсь, что показал несколько вариантов использования того, как он также может быть отличным инструментом для сервера. Это дает вам согласованный опыт управления состоянием в ваших внутренних службах.

Также было бы здорово услышать комментарии о том, как вы обрабатываете состояние на сервере, или почему это было бы ужасной идеей и никогда не должно быть сделано; Никогда!!!

В любом случае я надеюсь, что это даст вам еще одну возможность изучить, как вы можете писать более качественные и управляемые приложения.