1. Фон выбора технологии: максимизация данного окружения (Ag-Grid) и необходимость TypeScript
Одной из самых больших задач проекта было предоставить интерфейс, позволяющий пользователю быстро и интуитивно просматривать десятки тысяч данных. Особенно это требовало обеспечения пользовательского опыта, аналогичного Excel, что значительно повысило сложность требований.
Пользователь естественно ожидал функции, такие как следующие.
● Поиск и фильтрация в реальном времени
● Выравнивание и фиксирование столбцов
● Множественный выбор
● Оптимизация прокрутки и рендеринг большого объема данных
● Загрузка в Excel
Когда я был введен в проект, уже была внедрена Ag-Grid на основе React, специализированная на обработке больших объемов данных, в качестве ключевого компонента. Поскольку объем данных был настолько велик, что обычная HTML-таблица не могла справиться из-за нагрузки на DOM браузера, внедрение Ag-Grid, поддерживающего виртуальную прокрутку по умолчанию, было необходимым выбором.
Моя основная задача заключалась в том, чтобы максимально повысить производительность уже внедренного Ag-Grid и настроить его в соответствии с требованиями. Я оптимизировал его для плавной обработки десятков тысяч записей, используя серверную модель строк, множественную сортировку и пользовательские рендереры ячеек, предоставляемые Ag-Grid.
Дополнительно в проекте обязательно использовался TypeScript. По мере увеличения масштаба сервиса структура ответа API и структура управления состоянием становились все более сложными. В случаях использования только JavaScript часто возникали ситуации, когда ошибки обнаруживались только во время выполнения, однако мы решили эту проблему, четко определив структуру ответа API на основе интерфейсов.
● Интерфейс ответа API
● Модель строк сетки / Тип домена
● Тип состояния / Тип полезной нагрузки события
Таким образом, только по коду можно было сразу понять структуру данных, а также значительно сократить затраты на коммуникацию в процессе совместной работы.
2. Проектирование архитектуры и разделение на уровни: два опыта проекта
С развитием проекта важность поддерживаемости и структурной стабильности стала гораздо более значимой, чем простая реализация функций. Я на собственном опыте ощутил влияние архитектуры фронтенда на производительность разработки и поддерживаемость, проходя через два проекта с разными временными рамками.
Первый проект, в котором я принимал участие, использовал структуру View-Stub-State. В этой структуре папки были разделены по функциональным единицам, и роли каждого уровня были четко определены.
● View: отвечает за чистую отрисовку интерфейса
● Stub: отвечает за API-коммуникацию и бизнес-логику домена
● State: глобальное состояние и управление бизнес-состоянием
Главным преимуществом этой структуры было то, что «проектный поток можно быстро понять». На самом деле, когда я впервые был назначен на проект как новый разработчик, благодаря этому четкому разделению уровней я смог легко усвоить функциональную структуру и значительно сократить время onboarding. Высокая независимость на функциональном уровне также обеспечивала отличные возможности для обслуживания и масштабирования.
Во втором проекте, который был внедрён позже, использовался шаблон Container-Presenter. Этот шаблон представляет собой типичную структуру, которая разделяет рендеринг пользовательского интерфейса (Presenter) и бизнес-логику (Container).
Эта структура имела преимущество в том, что разделение интересов (Separation of Concerns) было ясным. Presenter можно было поддерживать как чистый компонент UI, что обеспечивало хорошую тестируемость и повторное использование. Однако, по мере роста и усложнения масштаба сервиса начали проявляться ограничения по сравнению с предыдущей структурой View-Stub-State.
● Увеличение сложности структуры папок
● Распределение управления состоянием
● Проблемы с дублированием доменной логики и управлением структурой API
В результате, имея опыт работы с обеими архитектурами, я пришел к пониманию, что для крупных проектов, которые эксплуатируются в долгосрочной перспективе, модульное разделение функциональности в форме View-Stub-State, с более тщательной изоляцией доменной логики и состояния, может быть более удобным.
Опыт оптимизации обработки больших данных
Обработка больших данных была проблемой, которая выходила за рамки простого отображения данных на экране. Особенно важно было оптимизировать производительность рендеринга и управление состоянием в уже внедренной среде Ag-Grid.
Сначала при изменении данных весь Grid рендерился заново, что вызывало задержки прокрутки и увеличивало потребление памяти браузера. Для решения этой проблемы были применены несколько стратегий оптимизации.
Первой стратегией, которую мы применили, была оптимизация рендеринга на основе React.memo. Мы мемоизировали Cell Renderer и Row Component, чтобы сократить количество ненужных повторных рендеров. Кроме того, активно использовались useMemo и useCallback. В частности, в Ag-Grid, если объект определения столбца (Column Definition) часто пересоздается, то это приводило к полной перезагрузке Grid, поэтому мы надежно закрепили его с помощью useMemo.
Кроме того, для уменьшения нагрузки на сеть и времени начальной загрузки была улучшена структура пагинации на стороне сервера. Путем перераспределения логики поиска и сортировки с клиента на сервер, мы значительно снизили нагрузку на браузер и завершили структуру, обеспечивающую надежную обработку тысяч записей в реальной рабочей среде.
4. Опыт решения проблем: целостность данных и обработка асинхронных событий
Одной из самых проблемных частей проекта была проблема целостности данных сервиса уведомлений. В одном методе одновременно выполнялись сохранение записей уведомлений в БД и отправка сообщений во внешнее API.
Проблема возникала после вызова внешнего API, когда происходило исключение и транзакция БД откатывалась. Пользователь получал уведомление, но в системе не оставалось записей, что приводило к серьезной ошибке целостности. Вызов внешнего API по своей сути не мог гарантировать ту же атомарность, что и транзакция БД.
В конечном итоге в проект была внедрена асинхронная обработка на основе событий. Основная идея заключалась в том, что «внешние операции выполняются только после коммита в БД».
1. Основная бизнес-логика и выполнение сохранения в БД
2. Событие выпускается в момент завершения транзакции DB Commit
3. Отдельный обработчик событий независимо обрабатывает отправку сообщений (отделно от основной транзакции)
С помощью этой структуры, если сохранение в БД не удается, сообщения также не отправляются, и мы смогли полностью изолировать внешний API, чтобы он не влиял на основную бизнес-логику, даже если он потерпел неудачу.
5. Улучшение с точки зрения сотрудничества и обслуживания
Проект фронтенда становится все более сложным с течением времени из-за компонентов и структуры управления состоянием. На двух крупных проектах я понял, как сильно первоначальный дизайн архитектуры и определение типов влияют на сотрудничество с коллегами.
Используя TypeScript для управления API-ответами в общем типе, понимание структуры данных с бэкендом стало гораздо более интуитивным. Кроме того, опыт работы с четкой архитектурой, такой как View-Stub-State, позволил мне научиться минимизировать побочные эффекты при изменении определенной функции.
Качество ревью кода также естественным образом возросло. Поскольку структура унифицирована, время, затрачиваемое на интерпретацию самого способа реализации, сократилось, и стало возможным сосредоточиться на самом бизнес-логике в ревью.
6. Завершение
Это время позволило мне глубже понять, как важны архитектурное проектирование и эксплуатационная надежность в условиях крупных данных, выходящих за пределы простого внедрения интерфейсов на React.
Хотя у нас уже был отличный инструмент под названием Ag-Grid, настройка и оптимизация его под требования проекта оказалась совсем не простой задачей. Более того, получив опыт работы с двумя различными фронтенд-архитектурами подряд, я понял, что проектирование структуры, соответствующей масштабу команды и характеристикам услуги, является необходимым.
В результате этот опыт имел значение, выходящее за рамки простого выполнения функций, и станет надежной основой при проектировании и устранении неполадок в крупных системах на основе React в будущем.
LEE DAVID