Создание сущностей
В предыдущих модулях мы подробно разобрали доменное моделирование и определили ключевые сущности приложения Food Fleet (рестораны, меню, корзина, отзывы и т. д.).
Теперь пришло время посмотреть, как эти сущности правильно представлять в кодовой базе фронтенда, чтобы:
- данные были удобны для работы,
- минимизировать связанность UI и API,
- избежать «протекания» структуры бекенда в интерфейс,
- упростить дальнейшую поддержку и расширение проекта.
Что такое сущности домена в коде?
Сущности в доменной модели — это фундаментальные объекты системы. На фронтенде они появляются:
- в компонентах,
- в глобальном состоянии,
- в API-запросах,
- в адаптерах данных,
- в моделях или сервисах.
Очень распространненая ошибка начинающих разработчиков - использовать данные прямо из API внутри компонентов. Это работает для простых случаев, но в реальных проектах приводит к различным проблемам. Это может быть глубюокая вложенность атрибутов, избыточные поля, не нужные фронтенду, неунифицированные данные и т.д.
Именно поэтому хорошей практикой является создание дополнительного уровня абстракции - модели, которая превращает API-ответы сервера в удобный объект для использования на фронте.
Пример API: данные ресторана
Давайте для примера рассмотрим возможный вариант ответа сервера на запрос информации о ресторане. В учебном проекте переключитесь на ветку restaurant-entity, запустите мок-сервер (это можно сделать запустив проект с корневого уровня с помощь turborepo или из папки /apps/core-api) и перейдите на http://localhost:3002/restaurants/1. Мок сервер вернет вам сущность ресторана, давайте рассмотрим ее повнимательнее.
{ "id": "1", "attributes": { "name": "Taco Bell", "description": "Taco Bell is a fast food restaurant that serves tacos and other Mexican-inspired dishes.", "logoUrl": "/taco-bell-logo.png", "heroImageUrl": "/taco-bell-hero.jpg", "deliveryFee": 2.99, "deliveryTime": [ 10, 30 ], "offersDelivery": true, "offersPickup": true, "offersGroupOrders": true, "businessHours": [ "08:00", "22:00" ], "rating": 4.5, "numRatings": 100 }, "relationships": { "categories": [ "1", "2" ], "reviews": [ { "id": "1", "rating": 4.5, "comment": "Great tacos!", "date": "2024-01-01", "user": { "id": "1", "name": "John Doe" } }, { "id": "2", "rating": 4, "comment": "The burritos are pretty good.", "date": "2024-01-02", "user": { "id": "2", "name": "Jane Doe" } }, { "id": "3", "rating": 3, "comment": "Both the tacos and the burritos are okay.", "date": "2024-01-03", "user": { "id": "3", "name": "John Smith" } } ] } }json
Во-первых такие атрибуты как имя ресторана, его описание и т.д. лежат внутри свойства attributes. Это означает, что каждый раз когда нам будет нужно обраться к этим атрибутам мы будем добавлять один лишний уровень вложенности в нашем коде. Вместо rest.name будет нужно писать rest.attributes.name. Вроде бы это совсем мелочь, но в больших проектах это может приводить к огромному количесвту лишнего кода.
Во-вторых давайте посмотрим на поле relationships. Внутри него содержаться данные о категориях и отзывах ресторана. Обратите внимание что для категорий представлены только айди, но нет названий категорий. Чтобы отображать эти категории нам нужно дополнительно получить их список, запросив другой эндпоинт сервера.
Кроме этого, вы можете заметить несколько полей которые пока никака не планируется использовать в нашем приложении. Например, offersDelivery, offersPickup и т.д. Безусловно эти поля могут пригодиться в будущем, но на данном этапе нет необходимости таскать за собой лишние данные и можно было бы удалить их без вреда для клиентской части.
Решение: слой абстракции (модель)
Чтобы устранить эти проблемы, создадим модель — промежуточный слой между API и фронтендом. Этот промежуточный слой позволяет нам избежать работы с данными, возвращаемыми с бекенда, напрямую. В этом слое мы также можем заранее осущестить необходимые преобразования или объединить данные из нескольких источников чтобы избежать дополнительной логики на уровне компонентов интерфейса.
Чтобы выполнить это проделаем следующие шаги:
- Определяем тип сущности (с использованием TypeScript) Например, на фронтенде для ресторана нам нужна вот такая сущность:
export type Restaurant = { id: number name: string image: string rating: number categories: string[] reviews: Review[] }typescript
А также вспомогательный тип для отзыва:
export type Review = { author: string text: string rating: number }typescript
- Создаем функцию (или класс) для получения и преобразования данных.
export async function getRestaurant(id: number): Promise<Restaurant> { const data = await fetchRestaurant(id) const categories = await fetchRestaurantCategories(id) const { attributes, relationships } = data const reviews = relationships.reviews.map((review) => ({ author: review.attributes.author, text: review.attributes.comment, rating: review.attributes.rating, })) return { id: data.id, name: attributes.name, image: attributes.image, rating: attributes.rating, categories: categories.map((c) => c.attributes.name), reviews, } }typescript
Эта функция вызывает несколько эндпоинтов для получения необходимых данных, затем нормализует их (убирает лишнюю вложенность) и формирует объект нужного типа Resaturant.
- Используем модель в компонентах.
//Здесь использована константа, но в реальных компонентах // id ресторана может быть получен например из параметров урла const id = 1 const restaurant = await getRestaurant(id) return <RestaurantHeader restaurant={restaurant} />typescript
Основные преимущества такого подхода:
- Единообразие данных Все компоненты получают данные в одном формате, независимо от структуры API.
- Изоляция изменений Если API поменяется, достаточно обновить модель — компоненты продолжать использовать преобразованные данные и останутся нетронутыми.
- Повторное использование Функцию getRestaurant можно вызывать в любом компоненте, нуждающемся в данных о ресторане.
- Чистота компонентов Компоненты не занимаются преобразованием данных — только их отображением.
- Контроль над данными Можно фильтровать, объединять и упрощать данные до передачи в UI.
Итог
Сущности — ключевые элементы предметной области, требующие продуманной работы с данными. Прямой вызов API в компонентах приводит к избыточности, неполноте и неудобному формату данных.
Модель (слой абстракции) решает эти проблемы:
- преобразует данные API в удобный для фронтенда формат;
- агрегирует информацию из нескольких эндпоинтов;
- изолирует компоненты от изменений API.
Использование моделей — инвестиция в масштабируемость и поддерживаемость кода. Даже простая функция‑модель значительно улучшает архитектуру приложения.
Измения в коде, сделанные в результате создания модели для ресторана вы можете найти, посмотрев пулл ревест на Гитхаб
Это платный урок
Купите полный доступ к курсу чтобы просматривать данный контент
Основы архитектуры фронтенда
Изучите основы проектирования современных, высоконагруженных фронтенд-приложений.
Безопасные платежи обрабатываются сервисом Юкасса
Комментарии
Войдите, чтобы оставить комментарий