← Назад к вопросам

Что такое адаптеры которые вызывают приложение?

1.7 Middle🔥 172 комментариев
#JavaScript Core

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отличный вопрос, который затрагивает ключевые архитектурные концепции как на уровне приложения, так и на уровне взаимодействия с внешним миром. В контексте Frontend и современной веб-разработки фраза «адаптеры, которые вызывают приложение» чаще всего относится к двум основным паттернам: Ports & Adapters (Hexagonal Architecture) на бэкенде и Adaptor Pattern для интеграции с внешними сервисами на фронтенде. Давайте разберем оба аспекта, поскольку фронтенд-разработчик должен понимать общую картину.

1. Архитектурный паттерн «Порты и Адаптеры» (Hexagonal Architecture)

В этой архитектуре ядро приложения (бизнес-логика) изолировано от внешних агентов (UI, базы данных, внешние API) с помощью двух ключевых абстракций:

  • Порт (Port): Это интерфейс, который определяет контракт (набор методов), как приложение хочет взаимодействовать с внешним миром. Порт «смотрит» из ядра наружу. Например, порт UserRepository определяет методы findById, save. Приложение вызывает этот порт.
  • Адаптер (Adapter): Это конкретная реализация порта, которая знает, как общаться с конкретной внешней системой. Он «адаптирует» запрос ядра к специфическому протоколу (HTTP, SQL, WebSocket и т.д.). Например, адаптер HttpUserRepository реализует интерфейс UserRepository, выполняя под капотом fetch-запросы к REST API.

Как это работает на практике? Приложение (ядро) вызывает методы порта. Во время выполнения (часто через Dependency Injection) этому порту подставляется конкретный адаптер. Таким образом, адаптер — это и есть та сущность, которую «вызывают», чтобы донести запрос приложения до внешней системы.

// 1. ПОРТ (интерфейс в ядре приложения)
interface UserServicePort {
  getCurrentUser(): Promise<User>;
}

// 2. ЯДРО ПРИЛОЖЕНИЯ (бизнес-логика)
class UserDashboard {
  constructor(private userService: UserServicePort) {} // Зависим от порта, а не от адаптера

  async displayUser() {
    const user = await this.userService.getCurrentUser(); // ВЫЗОВ ПОРТА
    // ... логика отображения
  }
}

// 3. АДАПТЕР (конкретная реализация на инфраструктурном уровне)
class HttpUserServiceAdapter implements UserServicePort {
  async getCurrentUser(): Promise<User> {
    // Адаптер знает детали: URL, заголовки, преобразование данных
    const response = await fetch('/api/current-user');
    const data = await response.json();
    return this.mapToDomainUser(data); // Преобразование DTO в доменную модель
  }

  private mapToDomainUser(dto: any): User { ... }
}

// 4. Сборка (Composition Root)
const adapter = new HttpUserServiceAdapter(); // Создаем адаптер
const app = new UserDashboard(adapter); // Внедряем адаптер в ядро
app.displayUser(); // Ядро вызывает порт, работает адаптер

2. Адаптеры на стороне Frontend (Client-Side)

На фронтенде адаптеры чаще всего используются для следующих целей:

  • Адаптация внешних API: Преобразование данных из формата внешнего сервиса в формат, ожидаемый вашим приложением. Это делает ваш код устойчивым к изменениям в API.
  • Интеграция SDK: Обертка над сторонними библиотеками (например, карты, аналитика) для предоставления единого, удобного интерфейса.
  • Работа с разными источниками данных: Адаптер может решать, брать данные из localStorage, кэша или делать сетевой запрос, предоставляя приложению единый метод getData().

Пример адаптера для внешнего погодного API:

// Порт, который ожидает наше приложение
interface WeatherService {
  getTemperature(city: string): Promise<number>;
}

// Адаптер для конкретного API (OpenWeatherMap)
class OpenWeatherMapAdapter implements WeatherService {
  private apiKey: string;
  private baseUrl = 'https://api.openweathermap.org/data/2.5/weather';

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async getTemperature(city: string): Promise<number> {
    // 1. Вызов внешнего сервиса
    const response = await fetch(`${this.baseUrl}?q=${city}&appid=${this.apiKey}&units=metric`);
    const data = await response.json();

    // 2. АДАПТАЦИЯ: преобразование сложного ответа API в простую температуру
    if (data.cod !== 200) {
      throw new Error(`Weather error: ${data.message}`);
    }
    return data.main.temp; // Извлекаем только нужное поле
  }
}

// Адаптер для другого API (WeatherAPI.com) с ТАКИМ ЖЕ интерфейсом
class WeatherApiComAdapter implements WeatherService {
  async getTemperature(city: string): Promise<number> {
    const response = await fetch(`https://api.weatherapi.com/v1/current.json?key=xxx&q=${city}`);
    const data = await response.json();
    return data.current.temp_c; // Преобразование другое, но интерфейс тот же!
  }
}

// Использование в приложении
const weatherService: WeatherService = new OpenWeatherMapAdapter('my-key');
// Завтра можем заменить на new WeatherApiComAdapter('other-key'), не меняя код приложения!
const temp = await weatherService.getTemperature('Berlin');
console.log(`Температура: ${temp}°C`);

Ключевые преимущества использования адаптеров

  • Изоляция бизнес-логики: Ваше ядро приложения не знает и не зависит от HTTP, fetch, форматов JSON конкретных API, библиотек.
  • Тестируемость: Вы можете легко создать мок-адаптер (Mock Adapter) или стаб для тестирования ядра приложения без реальных сетевых запросов.
  • Гибкость и заменаемость: Легко заменить один внешний сервис на другой (например, перейти с REST на GraphQL, сменить провайдера погоды), изменив только адаптер.
  • Единообразие: Предоставление единого, чистого интерфейса для различных и потенциально «неудобных» внешних систем.

Итог: Адаптеры, которые вызываются приложением, — это реализация принципа инверсии зависимостей (Dependency Inversion Principle). Приложение определяет, что ему нужно (порт), а инфраструктурный слой предоставляет реализацию как это сделать (адаптер). На фронтенде это мощный инструмент для управления зависимостями от бэкенда и сторонних сервисов, делающий код более чистым, тестируемым и готовым к изменениям.