Архитектура React-приложений и Data Access Layer (DAL)
Автор конспекта: Арина Василевская
Введение в архитектурные концепции
Архитектура программного обеспечения — это фундаментальная структура системы, определяющая
её качество, производительность, масштабируемость и сопровождаемость. Она описывает, из
каких компонентов состоит приложение, как они взаимодействуют между собой и какие у них зоны
ответственности.
Важно понимать, что архитектура — это повседневная деятельность разработчика. Каждый раз, когда вы решаете, где хранить логику, как передать данные или как изолировать компонент, вы принимаете архитектурное решение. Архитектурные подходы схожи для всех типов систем — веб-приложений, игр, серверов и микросервисов. Основу любого подхода составляют декомпозиция, чёткое распределение зон ответственности и рациональная организация взаимодействия между слоями.
Основные принципы и модели

1. Клиент-серверная модель
- Клиент запрашивает ресурсы или сервисы;
- Сервер предоставляет эти ресурсы и обрабатывает запросы.
Клиентом является браузер, а сервером — backend. Но backend, в свою очередь, может быть
клиентом для другого сервера — например, базы данных. Так реализуется взаимодействие независимых
процессов (межпроцессная коммуникация).
2. Информационный эксперт
Данные должны храниться там, где они используются и обрабатываются. Если хранение и обработка данных разделены, система усложняется и становится менее устойчивой.
3. Инверсия управления (Inversion of Control, IoC)
Происходит, когда библиотека или фреймворк управляет нашим кодом. Например, React вызывает
компонент для перерисовки в нужный момент жизненного цикла, что делает его фреймворком в контексте
IoC. Каждый раз, передавая callback, мы фактически создаём локальную инверсию управления.
Дочерние компоненты могут управлять поведением родителя через callback, хотя сами не зависят от
родителя напрямую (нет импортов на родителя).
4. Принцип единственной ответственности (Single Responsibility Principle, SRP)
«У модуля должна быть только одна причина для изменения.»
Компонент или модуль должен отвечать только за одну задачу. Если необходимо изменить внешний вид, не должны затрагиваться логика данных или структура запросов. Мы, как программисты, хотим расширять систему, но не модифицировать уже написанный код.
Во многих React-проектах один компонент совмещает несколько разных ролей:
- Запросы к серверу (коммуникация с API).
- Логика обработки данных (преобразование, фильтрация, вычисления).
- Отрисовка UI (визуальное представление и обработка событий).
Такой компонент нарушает принцип SRP и становится «толстым» (fat/thick component). Это
затрудняет тестирование, повторное использование и сопровождение кода.
5. Разделение ответственностей (Separation of Concerns)
Separation of Concerns (SoC) — это архитектурный принцип, согласно которому система должна быть
разделена на независимые аспекты (зоны ответственности) (concerns): отрисовка интерфейса,
управление состоянием, сетевые запросы, бизнес-логика и т.д. Каждый concern должен решать одну
задачу и знать минимум о других.

Основные слои:
Слой (layer) — это практическое воплощение определённого concern в структуре проекта,
например, в виде папки.
-
🖥️
Presentation Layer— презентационный слой — отвечает за отображение данных и взаимодействие с пользователем (UI). Идеально, если этот слой «тонкий»: содержит только код интерфейса и не выполняет бизнес-логику. -
⚙️
Business Logic Layer (BLL)— слой бизнес-логики — управляет состоянием и данными, реализует правила и алгоритмы. -
🗃️
Data Access Layer (DAL)— отвечает за работу с API и хранилищами. Содержит логику запросов и не зависит от фреймворков, что позволяет переиспользовать его, например, и вReact, и вAngularпроектах.
✅ Преимущества разделения:
- Упрощает масштабирование и поддержку проекта.
- Повышает гибкость и переносимость кода.
- Позволяет переиспользовать модули в разных архитектурах.
- Снижает связанность компонентов и делает систему устойчивой к изменениям.
Чем меньше слои знают друг о друге — тем проще развивать и адаптировать систему без глобальных переделок.
Практический рефакторинг: создание DAL
Рефакторинг — это улучшение структуры кода на основе архитектурных принципов без изменения видимого поведения программы для пользователя.
Создание структуры
Разделим структуру нашего приложения на два слоя: слой пользовательского интерфейса (UI)
вместе с управлением состоянием (state management) и слой доступа к данным
(Data Access Layer).
-
В папке
/componentsнаходятся компоненты. А вReactкомпоненты, как мы знаем, отвечают за пользовательский интерфейс (UI). Чтобы подчеркнуть этот аспект ещё яснее, можно переименовать папку в/ui. После переименования папка четко отражает свой функциональный аспект — слой интерфейса. -
Создадим новую папку
/dalс файломapi.tsвнутри. Этот слой будет отвечать за работу с API и хранилищами
- api.ts
Реализация функции getTrack
Сейчас компонент TrackDetail напрямую выполняет сетевой запрос. Любое изменение, например,
API-ключа, требует правки внутри UI-кода, что противоречит его назначению — отрисовка интерфейса.
Поэтому вынесем логику запроса в api.ts.
Использование в компоненте
Компоненту не нужно знать детали реализации запроса (адрес, метод, API-ключ). Ему достаточно
вызвать функцию и получить данные в ответ. Логика запроса (fetch, преобразование JSON и
т.д.) скрыта внутри DAL, заменим код запроса на вызов функции getTrack:
- Компонент вызывает
getTrackи подписывается наPromiseчерез.then(). - В обработчик
.then()приходят данные, которые затем устанавливаются в локальный стейт. Это снова демонстрирует инверсию управления, так какPromiseвызывает нашуcallback-функцию.
✅️ Результат рефакторинга
- Компонент становится тонким — вместо множества строк сетевого кода остаётся 1–2 строки вызова.
- Компонент не знает, откуда берутся данные (сервер, кэш и т.п.), что повышает гибкость и тестируемость.
- В проекте появляется одно место —
api.ts, куда мы идём по единственной причине: если изменился контракт взаимодействия с бэкендом — типизация, эндпоинт илиAPI-ключ.
Типизация
Типизация — один из ключевых элементов архитектуры, обеспечивающий предсказуемость,
надёжность и прозрачность структуры кода. В контексте Data Access Layer (DAL) типизация
помогает чётко определить, что именно возвращает функция, выполняющая запрос, и какая
структура данных ожидается от сервера.
Типизируем Promise, возвращаемые функцией getTrack, для этого используем тип
GetTrackDetailsOutputData, который импортируем из TrackDetail.tsx:
Мы столкнулись с проблемой цикличности импортов - компонент импортирует функцию из DAL, а
DAL импортирует тип из компонента.
Решение: перенести тип в api.ts:
И Импортировать его в TrackDetail.tsx:
Типы, описывающие данные, полученные от сервера, должны находиться в том же модуле, где впервые
используются. Это логично, потому что именно DAL первым «касается» данных сервера.
Рефакторинг TracksList
Реализация функции getTracks
Использование в компоненте
Типизация
- Перенесем все необходимые типы в
DAL
- Избавимся от инлайновых типов (
{data: GetTrackDetailsOutputData}):
Mocking данных
Выделение DAL позволяет легко подменить реализацию функции, сохраняя ее интерфейс(например: вход —
trackID, выход — Promise с данными).
- Можно создать фейковый
API-модуль, который не выполняет реальных запросов, а возвращает заранее подготовленные данные, используя фабричную функциюPromise.resolve(). - Такой подход позволяет разрабатывать и верстать приложение без обращения к реальному серверу и
без расхода
API-токенов. - Переключение между реальным и фейковым
DALвыполняется просто — достаточно, например, переименовать файлы или изменить путь импорта. - Это облегчает тестирование, автономную разработку и демонстрацию интерфейса без зависимости от бэкенда.
Пример: имитация запроса трека по ID
Создадим api-fake.ts и в нем реализуем функцию getTrack(trackId: string), которая не делает
запрос на сервер, а сразу возвращает Promise, разрешённый объектом с данными трека через
Promise.resolve(). Типы импортируем из реального api.ts. Данные для Promise.resolve() возьмем
из Network:
По аналогии с getTrack(), можно реализовать и имитацию получения списка треков — getTracks().
Таким образом, весь DAL можно быстро "подменить" фейковой реализацией, что позволяет
разрабатывать и тестировать UI и управление состоянием без обращения к настоящему API.
Заключение 🚀
✔️ Правильное разделение ответственности, выделение DAL делают фронтенд-проект более
структурированным, поддерживаемым и безопасным для изменений.
✔️ Компоненты сосредоточены на UI, DAL управляет данными, границы ответственности чётко
определены — это основа профессиональной архитектуры React-приложений.
🏠 Домашнее задание
Цель задания: Разделить приложение на слои: ui, dal
По аналогии как в видео необходимо доработать основное приложение trelly, над которым мы закончили
работать в 21 домашнем задании
Задание 1
- Переименуй директорию
componentsвui. - Создай директорию
dalна одном уровне сui. - В
dalдиректории создай файлapi.ts - В файле
api.tsсоздай функциюgetTaskи вынеси в нее код связанный с запросом на сервер из компонентаTaskDetails.tsx
Итоговый результат 🚀 разделили код на 2 слоя. Приложение работает как и прежде
Задание 2
- перенеси типизацию ответа с сервера в
api.ts - протипизируй результат которым резолвится
Promiseдля функцииgetTaskчтобы при обращение в компоненте не былоany - избавься от инлайновых типов и создай
type GetTaskOutputсогласно swagger документации
Итоговый результат 🚀. Типизация перенесена, приложение корректно отрабатывает, есть подсказки
от TS
- до 😥

- после 🙂

Задание 3
По аналогии с заданиями 1 и 2 сделай следующее:
- в файле
api.tsсоздай функциюgetTasksи вынеси в нее код связанный с запросом на сервер из компонентаTasksList.tsx - перенеси типизацию ответа с сервера в
api.tsизTaskItem.tsx - протипизируй результат которым резолвится
Promiseдля функцииgetTasksчтобы при обращение в компоненте не былоany - избавься от инлайновых типов и создай
type GlobalTaskListResponseсогласно swagger документации
Итоговый результат 🚀. Вынесли детали работы с сервером в api.ts. При этом приложение
корректно отрабатывает, есть подсказки от TS
Задание 4 ⭐
⭐ Дополнительное задание со звёздочкой. Проделывай по желанию.
В последующих домашках моковые данные использоваться не будут. Это практика для закрепления и более глубокого понимания материла
Шаги которые нужно выполнить:
- в директории
dalсоздай фейковую апишкуapi-fake.ts - скопируй полностью (вместе с типами) код из
api.ts - вместо реальных запросов на сервер, возвращай
Promise, который резолвится моковыми данными. Пример данных ты можешь достать изnetworkили взять данные которые мы прикрепили
- в компонентах
TaskDetails.tsxиTasksList.tsxпоправь импорты, чтобы доставать фейковые данные
Итоговый результат 🚀. В результате ты увидишь на экране теже таски, но запрос на сервер уходить не будет. И при нажатии на разные таски будет подгружаться всегда одна и таже таска. Такой подход позволяет разрабатывать и верстать приложение без обращения к реальному серверу и без расхода API-токенов.
🔶 Помни: DAL — это не усложнение, а упрощение будущей жизни. Когда API меняется (а оно
изменится), ты правишь код в одном месте, а не ищешь fetch по всему проекту. Сегодняшние 10 минут
на создание DAL экономят завтрашние часы рефакторинга.
🔶 Думай как архитектор: разделяй и властвуй. Компонент не должен знать, откуда приходят данные — с
сервера, из localStorage или mock-данных. DAL — это шлюз между UI и внешним миром. Меняй
источник данных без единого изменения в компонентах. 🎯
🔶 Профессионализм — в деталях. Новичок пишет fetch прямо в компоненте. Мидл выносит в отдельную
функцию. Сеньор создаёт DAL с типизацией, обработкой ошибок и единой точкой конфигурации. Какой путь
выбираешь ты? 🚀⚡


