Архитектура фронтенда (доработки и SSR)

Вступление

Изначально эта статья должна была быть на Хабре. И там первоначально и была выложена. Однако Хабр, кажется, заболел нейропаранойей и очистить сайт от нейрослопа для них оказалось важнее, чем сохранить авторов. Мою статью скрыли по причине подозрения в нейрослопе.

Уверяю, статья написана мною лично абсолютно без использования нейросетей. Любой, более-менее серьёзно работающий с текстами человек это подтвердит.

Пока Хабр решает, вернуть ли мою статью или нет, я продублирую её сюда. И, если Хабр так и не восстановит статью, думаю, публиковаться там больше не буду. Пусть дальше на ведьм охотятся. Мне это не интересно.

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

***

Введение и немного болтовни

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

В общем, дней утекло достаточно. За это время я окончательно уверился в том, что рынок умер, а с ним и я как наёмный фронтенд-разработчик. Надежда найти работу если и осталась, то настолько призрачная, что её почти и незаметно.

А потому в этой статье я, пожалуй не стану себя одёргивать и буду самим собой, не опасаясь за будущую карьеру. То, что мертво, всё равно не убить.

Ну, да хватит ныть. В конце концов я готов-таки представить на суд сообщества свою статью и предложить архитектуру, которая подойдёт и SPA, и SSR, и с которой не придётся мучиться так же, как с FSD, которую по странной случайности тоже называют архитектурой.

Ну, приступим.

Об архитектуре

Скажу сразу: примеров кода тут не будет. Разговор исключительно об архитектуре. Я реализовал эту архитектуру на Nuxt. Если интересно, как именно — пишите в коменты, я напишу отдельную статью о конкретной реализации уже с примерами кода.

Понятийный аппарат я описал в предыдущей статье и дублировать его тут не стану. Повторю только одно:

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

Как я уже говорил в предыдущей статье, опыт соприкосновения с FSD в своё время принесло мне довольно много боли. Методология расплывчатая и не конкретная, фичи часто пересекаются и из-за этого слои перемешиваются, из-за чего её трудно готовить и в каждой команде и даже в каждой голове лежит своя версия FSD.

При построении своей архитектуры я держал в уме две вещи:

  1. Устранение болей, которые пыталась устранить FSD.
  2. Устранение болей, которые привнесла FSD.

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

Не буду тянуть кота за тестикулы. Приветствуйте, схема:

Архитектура фронтенда (доработки и SSR)

Тут понадобятся пояснения. В первую очередь, функциональные слои. Их 5 (тут перечислю, подробнее опишу ниже):

  1. API. В идеале генерируется автоматически из документации.
  2. Домен.
  3. Запросы и команды.
  4. Composable слой. Те самые фичи.Сторы
  5. UI.

Подробно о слоях

Домен

Это ядро нашего приложения. Альфа и омега. Единственный источник истины для всех остальных модулей.

Перво-наперво домен должен быть независим. Он не должен знать ничего о том, как у вас там реализуется реактивность, кэширование и прочая прикладная фигня. Vue, React, Angular, Svelte тут быть не должно. Домен — храм, а храм должен быть чист от мирского. Без исключений. Никаких тяжёлых зависимостей.

Домен делает чистые запросы к api, маппит их и отдаёт дальше.

Api

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

Запросы, команды, сторы

Тут живёт управляющий запросами слой. Кэширование, дедупликация, ретрай, всё тут.

По задумке этот слой реализует CQRS паттерн. Не обязательно городить отдельные шины для запросов и команд, но хотя бы на уровне директорий и импортов разделить команды и запросы всё же стоит.

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

Фичи (composable)

Тут уже появляется зависимость от VDOM библиотеки.

На этом слое реализуется конкретная бизнес-логика (user-stories) и вспомогательные ui функции (toast, modal и т.д...). Разделение тут предельно простое:

Структура директорий composable
Структура директорий composable

Правило простое: импорты между entity запрещены. shared свободно импортируется по необходимости.

Composable слой ничего не знает ни об api, ни о репозиториях, ни от маппинге. Из домена он берёт только типы и константы.

Сторы

На самом деле это что-то вроде слоя 3.5

К одному и тому же стору могут обращаться разные composable функции, но при этом сторы не запрашивают данные сами и зависят от VDOM библиотеки. Сторы управляются только composable функциями и больше с ними никто не общается. Совсем.

Сделано это по двум причинам:

  1. Чтобы не тащить VDOM зависимость в слой запросов/команд.
  2. Чтобы не получить неожиданные данные из стора из-за неявного его обновления во время очередного запроса данных. Все обновления сторов явные и из composable функций.

UI

Собственно, слой UI. Не должен содержать описаний бизнес-логики, не должен сам запрашивать данные, не обращается к сторам и ничего в них не пишет. Никогда. В идеале импортирует функции из composable слоя и применяет в разметку.

Внутренняя организация ui компонентов на ваш вкус. Можно имплементировать atomic, можно wiget-component подход. Как нравится. Это мелочи и всегда можно будет не дорого переработать.

Плюсы и минусы

Минусы

Начнём с проблем.

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

Больше я минусов, прямо говоря, не вижу.

Плюсы

  1. Чёткое разделение ответственности между слоями и, как следствие, простая и однозначная декомпозиция
  2. Простая локализация ошибок. Чаще всего сразу понятно, на каком слое и в какой сущности её искать.
  3. Лёгкое погружение в проект новых разработчиков.
  4. Лёгкое и понятное декомпозирование задач:Описать типы => описать маппинг => описать репозитории => описать запросы/команды => описать composable => описать ui
  5. Удобное ревью за счёт понятного и всегда однозначного паттерна реализации функционала
  6. Простой контроль контекста нейросети.Бизнес-документация прекрасно укладывается в корень domain области.За счёт строгого потока зависимостей можно давать изолированные задачи типа «реализовать репозитории сущности A по требованиям `/domain/<entetyA>\readme.md`
  7. Простой контроль правил для нейросети.Жёсткая фиксация отношений между слоями даёт нейросети прочную основу для самопроверки и понятные паттерны при ревью человеком.

Собственно, на этом всё. Добро пожаловать в комментарии.

22