Backend for Frontend

Backend for Frontend (BFF) — это подход, при котором создаётся отдельный слой серверной логики для конкретного фронтенд-приложения. Он служит посредником между клиентом и множеством микросервисов, упрощая интеграцию и оптимизацию данных под нужды конкретного интерфейса. В контексте Fresh, фреймворка на базе Deno, BFF реализуется с использованием роутинга, middleware и серверных обработчиков, позволяя создавать высокопроизводительные приложения с минимальной задержкой.


Организация роутинга

В Fresh маршруты создаются через файловую систему. Каждый файл в директории routes/ соответствует отдельному URL. Для BFF важно структурировать маршруты так, чтобы они отражали логику фронтенда, а не внутренние сервисы:

// routes/api/user.ts
import { HandlerContext } from "$fresh/server.ts";

export const handler = async (req: Request, ctx: HandlerContext) => {
  const response = await fetch("https://internal-service.example.com/users");
  const data = await response.json();

  return new Response(JSON.stringify({
    id: data.id,
    name: data.name,
    email: data.email
  }), {
    headers: { "Content-Type": "application/json" },
  });
};

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


Middleware и обработка запросов

Fresh поддерживает middleware через handlers и функции верхнего уровня в маршрутах. Для BFF это важно для:

  • Аутентификации и авторизации.
  • Логирования и мониторинга.
  • Кэширования ответов от микросервисов.

Пример middleware для проверки токена:

// routes/api/_middleware.ts
import { HandlerContext } from "$fresh/server.ts";

export const handler = async (req: Request, ctx: HandlerContext) => {
  const token = req.headers.get("Authorization")?.replace("Bearer ", "");
  if (!token || token !== Deno.env.get("API_KEY")) {
    return new Response("Unauthorized", { status: 401 });
  }
  return ctx.next();
};

Использование middleware позволяет централизовать логику безопасности, не дублируя её в каждом маршруте.


Серверная агрегация данных

BFF позволяет объединять ответы нескольких микросервисов в один:

// routes/api/dashboard.ts
export const handler = async (req: Request) => {
  const [userRes, ordersRes] = await Promise.all([
    fetch("https://internal-service.example.com/users/123"),
    fetch("https://internal-service.example.com/orders?userId=123")
  ]);

  const user = await userRes.json();
  const orders = await ordersRes.json();

  return new Response(JSON.stringify({
    user: { id: user.id, name: user.name },
    orders: orders.map(o => ({ id: o.id, total: o.total })),
  }), { headers: { "Content-Type": "application/json" } });
};

Этот подход уменьшает число HTTP-запросов с клиента, повышает скорость загрузки страниц и упрощает фронтенд-код.


Оптимизация производительности

Fresh и Deno позволяют использовать встроенные возможности для ускорения BFF:

  • Streaming: отправка данных по мере их обработки.
  • Edge Functions: развертывание BFF ближе к пользователю для снижения латентности.
  • Cache API: хранение промежуточных результатов микросервисов.

Пример кэширования с использованием caches:

const cache = await caches.open("bff-cache");
const cachedResponse = await cache.match(req.url);
if (cachedResponse) return cachedResponse;

// получение данных от сервисов
const response = await fetch("https://internal-service.example.com/data");
const clonedResponse = response.clone();
cache.put(req.url, clonedResponse);

return response;

Ключевой эффект — уменьшение нагрузки на внутренние сервисы и ускорение отклика.


Обработка ошибок и устойчивость

BFF должен корректно обрабатывать ошибки микросервисов, не передавая фронтенду «сырые» исключения:

try {
  const res = await fetch("https://internal-service.example.com/users");
  if (!res.ok) throw new Error("Service unavailable");
  const data = await res.json();
  return new Response(JSON.stringify(data));
} catch (err) {
  return new Response(JSON.stringify({ error: err.message }), {
    status: 502,
    headers: { "Content-Type": "application/json" },
  });
}

Такой подход обеспечивает стабильность фронтенда, даже если один из внутренних сервисов недоступен.


Интеграция с фронтендом Fresh

На стороне Fresh BFF взаимодействует с компонентами через fetch внутри useEffect или серверные функции. Преимущество в том, что фронтенд получает готовые, адаптированные под UI данные, без лишней логики:

/** @jsx h */
import { h } from "preact";

export default function UserProfile({ data }) {
  return (
    <div>
      <h1>{data.user.name}</h1>
      <ul>
        {data.orders.map(order => (
          <li key={order.id}>Order #{order.id}: {order.total}</li>
        ))}
      </ul>
    </div>
  );
}

BFF скрывает сложность микросервисов и предоставляет фронтенду чистый, структурированный API.


Взаимодействие с внешними API

BFF используется не только для внутренних микросервисов, но и для внешних API, обеспечивая:

  • Агрегацию данных из разных источников.
  • Преобразование форматов (например, XML → JSON).
  • Централизованное управление ключами и токенами.

Пример:

const response = await fetch("https://api.example.com/weather");
const data = await response.json();

return new Response(JSON.stringify({
  temperature: data.temp,
  condition: data.weather[0].main
}));

Такой слой облегчает поддержку и масштабирование интеграций с внешними сервисами.


Заключение архитектурных особенностей

BFF в Fresh — это комбинация файлового роутинга, middleware, серверной агрегации и кэширования, ориентированная на потребности фронтенда. Он упрощает управление данными, повышает производительность и обеспечивает стабильность пользовательского интерфейса. Правильная организация BFF позволяет строить масштабируемые и легко поддерживаемые приложения на Deno с минимальными накладными затратами.