Разработка с нейронкой. Часть 2. Пара слов о фронте

Тут по большей части техническое и по большей части архитектура. #frontend #nuxt

Переехал с Nuxt ui на Reka ui. Не потому что Nuxt ui плохой, а решил, что Nuxt ui слишком геморойно кастомизировать. Headless kit для этого сильно удобнее.

Так же переехал с typescript-swagger на orval для генерации api. Orval генерит сразу и DTO слой, и запросы, и на модули бьёт. И конфиг удобный. Со всех сторон приятнее, чем typescript-swagger. Единственное — пришлось написать мутатор http клиента для работы с nuxt ofetch, но это мелочи, честно говоря.

Теперь о новой архитектуре фронта.

Весь фронт строится от domain слоя. Я сложил его в shared/domain (там же по соседству, в shared лежит сгенерированная api). Домен содержит внезапно описания доменных сущностей; мапперы с api dto в доменные сущности, константы и репозитории. А, и ещё бизнес-описания модуля тоже лежат в домене. Где-то же нужно хранить документацию. На фронте я стараюсь хранить только важные для фронта описания и не включать туда бэкенд-специфичные штуки типа описаний моделей данных бэкенда.

Ну, про api слой уже понятно. Api генерируется из openapi доки и домен лезет туда за данными. Всё.

Если считать, что api — самый нижний слой, то domain — прямо над ним.

Следующий после domain слой — commands/queries/stores. Они находятся параллельно и служат прослойками для бизнес-функций. Запросы/команды, по сути, абстракция, нужная только для явного разделения запросов и команд. Этакое cqrs на минималках. Сторы выделены отдельно, потому что это абстракция выше домена, к тому же лишняя для домена зависимость. Сторов на самом деле не много: в них хранятся фронтендспецифичные данные вроде состояния аутентификации и аутентифицированного пользователя. То, что проще хранить, чем перезапрашивать. Остальное — кэш useAsyncData по уникальным ключам запроса.

Выше — composable-сущности. Это, по-сути, пользовательские фичи и вспомогательные фронтовые функции типа useToast или useHandleError. Фронтовые функции никогда ничего не знают ни об API, ни о репозиториях, ни о мапперах. Они оперируют только store/commands/queries и знают только о доменных сущностях.

Ну и в самом конце всей этой лесенки дураков стоит ui. Он общается только с composable (ну и забирает типы из домены) и ничего не знает ни о запросах, ни о командах, ни о сторах… потому что нефиг.

У кого-то может возникнуть вопрос: зачем так сложно?

А причин несколько: 1. При такой архитектуре нейронка получает понятные правила работы и простой способ самопроверки. 2. При таком подходе ниже шанс, что что-то развалится. А его оно развалится, это легко локализовать и исправить. 3. Я, как проверяющий, получаю очень понятную структуру и мне проще проверить, что нужные части функции на своих местах и реализованы как надо.

Ничего не путается, всё хорошо типизированно, фронт явно отделён от бэка собственным доменным слоем.

Мне удобно, я доволен :)