# Edge-мапперы во фронтенде для безопасных изменений бэкенда

> Узнайте, как edge-мапперы во фронтенде изолируют изменения API, сохраняют код UI стабильным и упрощают рефакторинг с помощью простых адаптеров и проверок.

## Почему изменения на бэкенде продолжают ломать экраны

Экраны обычно ломаются, когда код UI читает сырые данные API напрямую. Сначала это кажется быстрым, но каждое изменение на бэкенде размазывается по компонентам, хукам, формам, тестам и маленьким помощникам, о которых все забыли.

Переименование поля — очевидный пример. Если `full_name` становится `displayName`, везде, где ожидалось старое поле, всё ломается. Поломка редко остаётся в одном файле. Карточка профиля, меню аккаунта, форма настроек и строка результата поиска могут читать это поле немного по‑разному.

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

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

Команды также тратят время на исправление одной и той же догадки в нескольких местах. Один разработчик добавляет fallback в карточке, другой добавляет проверку на null в модале, третий обновляет тестовый фикстуру. Приложение снова работает, но слабое место остаётся.

Именно тут помогают edge-мапперы. Они дают API одно место, где можно быть неряшливым, неполным или переименованным. Остальная часть UI продолжает получать ту же простую форму, так что меняется меньше файлов, и баги легче находить.

## Куда помещать маппер

Поместите маппер рядом с вызовом API. Если экран профиля получает данные через `getProfile()`, маппер должен жить в этом файле или рядом с ним. Не прячьте его внутри компонента и не бросайте в гигантскую папку `utils`, где он теряет контекст.

Это одно решение держит сырые данные сервера вне UI. Компоненту не важно, отправляет ли бэкенд `first_name`, `firstName` или `user_name`. Он должен получать те имена, которые использует приложение, и продолжать рендерить.

Держите границу строгой. Слой API может знать поля сервера. Остальная часть фронтенда должна знать только поля приложения. Когда бэкенд меняется, вы обновляете один маленький маппер вместо того, чтобы править каждый экран, который читает ответ.

На практике поток простой: запрашиваете данные, маппите их прямо там, возвращаете отображённый объект и используете эту же форму на экранах. Переименуйте поля один раз и забудьте. Если сервер присылает `avatar_url`, конвертируйте это в `avatarUrl` в маппере. Если отправляет `created_at`, конвертируйте в `createdAt` там же. Смешанные наименования быстро распространяются, и кодовая база начинает выглядеть неряшливо уже через несколько релизов.

Маппер также должен возвращать одну и ту же форму каждый раз, когда экран загружается. Это важно, когда бэкенд присылает частичные данные, null или странные дефолты. Если страница профиля ожидает `{ name, avatarUrl, status }`, возвращайте эту форму при каждом успешном fetch. Не заставляйте компонент угадывать, существует ли `status` сегодня.

Тонкий адаптер — это не второй слой бизнес‑логики. Это небольшой кусочек кода на границе, который переводит вывод сервера в данные, которым UI может доверять. Держите его рядом с запросом и пусть он будет скучным.

## Что делает тонкий адаптер

Тонкий адаптер сидит между ответом API и остальной частью экрана. Он берёт то, что бэкенд отправляет сегодня, и превращает это в ту форму, которую ваши компоненты ожидают каждый день. Звучит просто, но это убирает много шума из UI‑кода.

Предположим, сервер возвращает `full_name`, `created_at`, `bio: null` и ещё десять полей. Ваша карточка профиля не должна этим париться. Адаптер может переименовать `full_name` в `name`, распарсить `created_at` в настоящий `Date` или строку, готовую к отображению, превратить `bio: null` в `""` и игнорировать поля, которые карточка никогда не читает. Компонент получает один чистый объект и остаётся простым.

Это важно, потому что UI‑код легко ломается, когда в него просачиваются сырые данные API. Один компонент проверяет `null`, другой ожидает пустую строку, третий по‑разному парсит одну и ту же дату. Небольшие различия накапливаются. Тонкий адаптер даёт всему экрану одно правило.

Он также создаёт чистую границу вокруг изменений. Если команда бэкенда переименует `full_name` в `display_name`, вы обычно обновляете одну функцию. Карточка профиля, хедер и форма настроек могут продолжать использовать `name`.

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

Важно, чтобы он был «тонким». Адаптер должен переводить и очищать, а затем останавливаться. Если вы вешаете туда правила ценообразования, проверки прав и решения по форматированию, людям перестанет доверять ему.

Простой тест подскажет: после работы адаптера может ли компонент рендериться без защитной логики по всему коду? Если да — адаптер справляется со своей задачей.

## Как добавить без большого рефактора

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

Прежде чем писать маппер, опишите форму, которой экран действительно нуждается. Делайте её маленькой и понятной. Если страница аккаунта показывает только имя, email, тариф и дату продления, — это ваша модель приложения. Оставьте всё остальное, даже если API присылает ещё 40 полей.

Затем добавьте маленький маппер рядом с кодом fetch или внутри папки фичи. Он должен принимать сырый ответ и возвращать модель экрана. Большинство мапперов делают простые вещи: переименовывают поля, уплощают вложенные структуры, задают безопасные дефолты и конвертируют неудобные значения бэкенда в то, что UI может использовать.

После этого переключите экран на чтение отображённого результата вместо сырого ответа. Старайтесь не менять компонент сильно. Цель — вынести знание о бэкенде из UI, а не перестраивать страницу. Если API позже поменяет `subscription.plan_name` на `plan.title`, вы исправите одну функцию и продолжите работу.

Добавьте небольшой тест. Сохраните один реальный JSON‑пейлоад как пример, прогоните его через маппер и проверьте точный результат, который ожидает экран. Это дешёвый тест, который ловит неприятные изменения, которые проскальзывают в ревью.

Часто хорошей первой целью становится страница биллинга. Команды постоянно добавляют скидки, триальные состояния, налоговые поля и варианты планов. Без адаптеров эти изменения просачиваются в несколько компонентов. С тонким адаптером экран продолжает требовать одни и те же поля каждый раз.

## Простой пример страницы профиля

Экран профиля — хорошее место, чтобы увидеть паттерн в действии. Представьте, что API возвращает сырые поля `first_name` и `avatar_url`, а экрану нужны только `name` и `avatar`. Экран не должен знать, как бэкенд называет вещи.

Здесь edge-мапперы делают жизнь спокойнее. Вы берёте ответ сервера на границе, один раз переводите его и передаёте стабильную форму в остальной UI.

```ts
type ProfileApi = {
  first_name: string
  avatar_url: string | null
}

type Profile = {
  name: string
  avatar: string
}

export function mapProfile(api: ProfileApi): Profile {
  return {
    name: api.first_name,
    avatar: api.avatar_url ?? "/default-avatar.png",
  }
}
```

Теперь компонент профиля остаётся простым. Он получает один объект `Profile` и рендерит его. Никаких snake_case полей в экране. Никакой логики fallback, размноженной в трёх местах.

```ts
const profile = mapProfile(apiResponse)
return <ProfileCard name={profile.name} avatar={profile.avatar} />
```

Польза обычно проявляется позже, в обычный вторник. Команда бэкенда меняет `first_name` на `givenName` или перемещает изображение в `media.avatar`. Без маппера каждое место, которое зависит от этих полей, нужно править. С маппером вы меняете один файл.

Этот файл — хорошее место для небольших решений. Если API начинает шлёпать `first_name` и `last_name`, вы можете объединить их в одно `name`. Если `avatar_url` часто пустой, правило про дефолтное изображение остаётся в одном месте вместо того, чтобы разбегаться по приложению.

Всё это — идея тонких адаптеров и API‑адаптеров. Они делают одну вещь: переводят внешние данные в форму, которую ожидает UI. Держите эту работу маленькой, и ваши компоненты останутся устойчивыми даже когда бэкенд нестабилен.

## Ошибки, которые лишают мапперы доверия

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

Одна распространённая ошибка — смешивать код fetch и код преобразования в одном длинном файле. Сетевая логика уже сама по себе шумная: заголовки, ретраи, обработка ошибок, авторизация и состояние загрузки. Если в том же файле ещё и преобразуются полезные нагрузки, никто не поймёт, что сломалось. Маленький чистый маппер легко читать и тестировать. Громоздкий модуль «загрузить‑и‑перевести‑всё» — не очень.

Ещё одна ошибка — засовывать бизнес‑правила в маппер. Маппер должен переводить форму API в ту форму, которую ожидает UI. Он может переименовывать поля, уплощать вложенности и подставлять простые дефолты. Он не должен решать, может ли пользователь апгрейдиться, считается ли заказ просроченным или какой план получает бейдж. Эти правила держите там, где команда их ждёт.

Команды также раздувают мапперы, конвертируя каждое поле «на всякий случай». Это звучит безопасно, но создаёт лишнюю работу. Если экрану нужно шесть полей — мапьте шесть. Когда бэкенд добавит двадцать, вашему UI всё равно не важно. Маленькие мапперы переживают churn бэкенда лучше, потому что они открывают меньшую поверхность.

Одна ошибка рушит доверие быстрее остальных: компонент, который тянется к сырому ответу за одним отсутствующим значением. Как только это происходит, граница ломается. Следующее переименование на бэкенде снова прорастёт в слой представления.

Маппер заслуживает доверия, когда он остаётся скучным. Держите его маленьким, рядом с границей API, и пусть компоненты читают одну чистую форму.

## Быстрая проверка перед релизом

День релиза проходит спокойнее, когда каждый экран читает одну чистую форму вместо сырого ответа. Перед отправкой проверьте несколько простых вещей:

- Каждый экран читает один стабильный объект.
- Компоненты не упоминают имена полей API.
- Маппер превращает null, отсутствующие массивы и старые странные значения в безопасные дефолты.
- Тесты покрывают текущий payload и старый или частичный вариант.

Также пробегитесь по дереву компонентов в поисках спасательной логики вроде `user.photo || user.avatar || ""`. Этот код должен жить в маппере, а не в презентационных компонентах. Компоненты решают, как показывать данные, а не как их ремонтировать.

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

Если экран переживает переименование API, не трогая JSX — граница на своём месте.

## Когда маппер можно пропустить

Прямое использование API подходит в некоторых случаях. Маппер — это инструмент безопасности, а не закон, который нужно применять ко всем запросам.

Небольшой внутренний инструмент часто ещё не нуждается в нём. Если экран используют два человека, один разработчик его поддерживает и форма бэкенда вряд ли изменится в этом месяце, прямой fetch может быть проще. То же самое для одноразового прототипа. Если цель — проверить идею к пятнице, лишняя структура может мешать.

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

Прямой доступ обычно приемлем, когда цена поломки низкая: один экран использует данные, один endpoint их возвращает, один человек за них отвечает, форма API стабильно держится, и вы бы без сожаления удалили экран.

Команды часто тянут с этим слишком долго. Как только endpoint начинает менять форму, восприятие выгод резко меняется. Если бэкенд переименовывает поля, добавляет вложенные объекты или возвращает разные формы для похожих экранов, прямой доступ начинает протекать в компоненты.

Тогда тонкий адаптер окупает себя. Вам не нужен большой рефактор: поместите маппер рядом с вызовом API, не меняйте пропсы компонента и продолжайте работу.

## Следующие шаги для команды под давлением

Команды под нагрузкой обычно проваливаются, когда пытаются починить всё сразу. Лучше идти маленькими шагами: выберите один endpoint, который создаёт наибольший шум, и поставьте вокруг него маппер на этой неделе.

Такой endpoint обычно легко найти. Это тот, что вызывает повторяющиеся UI‑баги, срочные хотфиксы или сообщения вроде «бэкенд снова поменял поле». Если один экран ломается через релиз — начните с него.

Согласуйте имена по‑приложению до следующего изменения на бэкенде. Если в разных местах приходят `user_name`, `fullName` и `display_name`, приложение всё равно должно использовать одно имя внутри UI. Решите его один раз, поместите перевод в маппер и пусть компоненты остаются стабильными, пока API танцует вокруг.

Через спринт откройте несколько файлов мапперов и оцените их как обычный код. Они всё ещё тонкие? Переименовывают, уплощают и задают безопасные дефолты? Сделали ли они UI читабельнее? Если маппер начал тянуть флаги фич, форматировать даты или вызывать внешние сервисы — отрежьте это.

Одна маленькая победа достаточно, чтобы завоевать доверие. Если на следующий месяц поле бэкенда поменяется, и править придётся только один маппер — люди это заметят.

Если вашей команде нужна помощь в решении, где ставить границы, Oleg Sotnikov at oleg.is работает как Fractional CTO и советник стартапов. Короткая консультация может помочь найти endpoint, где тонкий адаптер сэкономит больше всего времени сначала.
