Constracts для API и межсервисная коммуникация | Вопросы для собеседования | Skilio
Constracts для API и межсервисная коммуникация
Вопрос:

Как бы вы спроектировали взаимодействие между микросервисами в проекте Node.js на TypeScript? Что важно в организации взаимодействия двух сервисов во время разработки и их развития?

Подсказки: 0. Типы коммуникаций и протоколы

  1. Подумайте о контрактах сервисов и их валидации
  2. Рассмотрите gRPC и REST с спецификациями OpenAPI
  3. Посмотрите на инструменты для генерации типов TypeScript из контрактов

Выше ожиданий:

  • IDL Protocol Buffers и генерация кода
  • Обратные/прямые совместимые паттерны
  • Расширенная обработка ошибок с кодами статуса gRPC
  • Стратегии обнаружения сервисов (service discovery) и балансировки нагрузки
Ответ:

Паттерны коммуникации

Существует синхронная и асинхронная коммуникации:

Синхронные протоколы HTTP/REST или gRPC:

  • Односторонние вызовы для запрос-ответ
  • Потоковая передача данных сервером на клиент
  • Потоковая передача данных клиентом для загрузки
  • gRPC поддерживает двустороннюю потоковую передачу для обновлений в реальном времени

Асинхронные (событийно-ориентированные):

  • Брокеры сообщений, такие как RabbitMQ, Kafka
  • Схемы событий с использованием спецификации AsyncAPI
  • Нормально использовать JSON-Schema, Protobuf или Avro для описания формата сообщений как и для синхронных протоколов
  • Очереди сообщений для обработки ошибок (dead letter queues) для сообщений, которые не удалось обработать

Спецификации контрактов

Существует несколько вариантов. Наиболее распространённым является использование REST API и спецификаций OpenAPI (Swagger), которые могут быть описаны в формате YAML.

Пример:

paths:
  /users/{userId}:
    get:
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

Другой вариант — Protocol Buffers (protobuf). Он обеспечивает сильную типизацию, управление версиями и эффективную сериализацию:

syntax = "proto3";

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
}

message UserRequest {
  string userId = 1;
}

message UserResponse {
  string id = 1;
  string name = 2;
  repeated string roles = 3;
}

JSON-Schema и Avro используются для описания формата передаваемых данных, но не описания протокола самого взаимодействия.

  1. Генерация и валидация типов

Генерируйте типы TypeScript из контрактов, используя инструменты:

  • Для REST вы можете использовать openapi-typescript-codegen
  • Для gRPC: используйте grpc-tools и ts-proto

Реализуйте проверку типов во время выполнения с помощью:

  • io-ts или zod для валидации типов
  • Пользовательские middleware для валидации запросов/ответов
  1. Совместимость и управление версиями

Поддерживайте обратную совместимость:

  • Используйте необязательные поля в Protobuf
  • Реализуйте управление версиями API в URL или заголовках
  • Никогда не удаляйте поля, только устаревайте их
  • Используйте значения по умолчанию для новых полей

Пример версионированного endpoint:

interface UserServiceV1 {
  getUser(id: string): Promise<UserV1>;
}

interface UserServiceV2 extends UserServiceV1 {
  getUserWithMetadata(id: string): Promise<UserV2>;
}

Возможна организация версионирования в пакетах protocbuf.

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

Реализуйте обработку ошибок:

  • Используйте gRPC коды состояния для подробного отчёта об ошибках
  • Реализуйте circuit breaker для изоляции отказов и ретраи для митигации короткой недоступности
  • Используйте паттерны таймаутов на вызовы

Пример обработки ошибок:

const getUserWithRetry = async (userId: string): Promise<User> => {
  const backoff = new ExponentialBackoff({
    initialDelay: 100,
    maxDelay: 1000,
    maxAttempts: 3
  });

  return backoff.execute(async () => {
    try {
      return await userService.getUser(userId);
    } catch (error) {
      if (error.code === grpc.status.UNAVAILABLE) {
        throw new RetryableError();
      }
      throw error;
    }
  });
};

Открытие сервисов для сложной архитектуры и балансировка нагрузки

Для небольших приложений достаточно использовать обычные балансировщики такие как nginx, Traefik, HAProxy для балансировки нагрузки, маршрутизации и проверок состояния.

Service discovery для сложных приложений:

  • Consul или etcd для регистрации сервисов
  • Балансировка нагрузки на стороне клиента с gRPC-node
  • Проверки состояния и разрывы цепочки
  • Сетевой меш, такой как Istio, для сложных сценариев

Мониторинг и отслеживание

Observability:

  • Распределённое отслеживание с OpenTelemetry
  • Сбор метрик
  • Correlation ID для отслеживания запросов
  • structured logging

Безопасность

Реализуйте меры безопасности:

  • gRPC TLS для безопасности транспорта
  • JWT или mTLS для аутентификации
  • Rate limiter
  • Валидация входных данных

Этот дизайн обеспечивает:

  • Безопасность типов посредством разработки на основе контрактов
  • Проверка типов сообщений во время выполнения
  • Устойчивость и отказоустойчивость
  • Масштабируемость и поддерживаемость
  • Наблюдаемая и безопасная коммуникация

Подход может быть адаптирован в зависимости от конкретных требований, потребностей масштабирования и опыта команды. Начните с более простых паттернов и развивайтесь по мере необходимости.

0
TypeScript Средний Опубликовано
© Skilio, 2025
Условия использования
Политика конфиденциальности
Мы используем файлы cookie, для персонализации сервисов и повышения удобства пользования сайтом. Если вы не согласны на их использование, поменяйте настройки браузера.