Kravchenko

Web Lab

АудитБлогКонтакты

Kravchenko

Web Lab

Разрабатываем сайты и автоматизацию на современных фреймворках под ключ

Услуги
ЛендингМногостраничныйВизитка
E-commerceБронированиеПортфолио
Навигация
БлогКонтактыАудит
Обратная связь
+7 921 567-11-16
info@kravlab.ru
с 09:00 до 18:00

© 2026 Все права защищены

•

ИП Кравченко Никита Владимирович

•

ОГРНИП: 324784700339743

Политика конфиденциальности

Эволюция схем API и событий без v2: меньше инцидентов и быстрее релизы

Разработка и технологии24 января 2026 г.
Как добавлять поля и менять контракты, не ломая клиентов и интеграции. Разбираем правила совместимости для JSON/REST, Protobuf и Avro, показываем рабочие примеры схем, инструменты проверки на разрывы и практики депрекейта.
Эволюция схем API и событий без v2: меньше инцидентов и быстрее релизы

Оглавление

  • Зачем бизнесу эволюция схем без v2
  • Что такое совместимость: назад, вперёд и двусторонняя
  • Правила для JSON/REST: как безопасно менять контракт
    • Пример: JSON Schema c мягким расширением
    • Пример: OpenAPI с депрекейтом и заголовками
  • Protobuf и Avro: числа тегов, резервирование и эволюция
    • Protobuf: железные правила
    • Avro: совместимость через «схема писателя/схема читателя»
  • События: конверты, версии и преобразователи
    • Пример: конверт JSON-события
    • Миграции через преобразователи (upcast/downcast)
  • Контрактные тесты и проверки на разрывы в CI
  • Инструменты: OpenAPI, JSON Schema, Buf, реестры схем
  • Пошаговый план внедрения за 2 недели
  • Частые ошибки и как их не допустить
  • Краткий чек-лист для команды

Зачем бизнесу эволюция схем без v2

Полный переход на «v2» API или «новые» события — это месяцы согласований, параллельная поддержка старых клиентов, повышенные расходы и риски, что «что-то забудем» и сломаем партнёров. Бизнесу это бьёт по трём метрикам:

  • Скорость поставки: новые фичи ждут «большого релиза».
  • Надёжность: ломаются интеграции, растут инциденты и штрафы.
  • Издержки: поддерживаем несколько версий, умножаем тесты и документацию.

Эволюция схем без глобального «v2» — это набор правил и инструментов, позволяющий выпускать изменения маленькими шагами, не ломая существующих клиентов и потребителей событий.

Что такое совместимость: назад, вперёд и двусторонняя

  • Обратная совместимость (backward): новые продюсеры/серверы работают с старыми потребителями/клиентами. Старый клиент не замечает новых полей.
  • Прямая совместимость (forward): старые продюсеры/серверы работают с новыми потребителями/клиентами. Новый клиент готов к отсутствию новых полей.
  • Двусторонняя (full): одновременно соблюдаются обе. Это лучший режим для микросервисов и внешних интеграций, потому что порядок выкатывания не важен.

Простой принцип: «добавляй — не ломай; удаляй — только через деактивацию и этапность; меняй типы и смысл — исключительно с миграцией и оговорёнными адаптерами».

Правила для JSON/REST: как безопасно менять контракт

Для REST и JSON даёт результат дисциплина и документация. Базовые правила:

  1. Добавлять новые поля только как необязательные, с понятными разумными значениями по умолчанию на стороне клиента.
  2. Никогда не менять тип уже выпущенного поля (string → number и т. п.) и его семантику.
  3. Не переименовывать поля. Если нужно новое имя — добавьте новое поле, старое объявите устаревшим и поддерживайте до удаления по плану.
  4. Не делать новые поля обязательными для существующих маршрутов — это ломает старых клиентов.
  5. Обрабатывать неизвестные поля: клиент не должен падать, если сервер прислал больше, чем он ожидает. Сервер — игнорировать лишние поля в запросе.
  6. Для больших изменений — вводите новый ресурс или под-ресурс вместо «ломающего апдейта».
  7. Используйте план депрекейта: метки в OpenAPI, заголовки Deprecation/Sunset, телеметрию использования, дата удаления.

Пример: JSON Schema c мягким расширением

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://api.example.com/schemas/customer.json",
  "title": "Customer",
  "type": "object",
  "additionalProperties": true,
  "properties": {
    "id": { "type": "string", "pattern": "^[0-9a-f-]{36}$" },
    "email": { "type": "string", "format": "email" },
    "name": { "type": "string", "minLength": 1 },
    "marketingConsent": { "type": "boolean", "default": false },
    "tags": { "type": "array", "items": { "type": "string" }, "default": [] }
  },
  "required": ["id", "email"]
}

Здесь допускаются неизвестные поля (additionalProperties: true), новые поля добавляются необязательными. Клиенты, не знающие про marketingConsent и tags, не сломаются.

Пример: OpenAPI с депрекейтом и заголовками

openapi: 3.0.3
info:
  title: Customers API
  version: 1.12.0
paths:
  /customers/{id}:
    get:
      summary: Получить профиль клиента
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: OK
          headers:
            Deprecation:
              description: |-
                true — ресурс или поле устаревает. Клиенту стоит перейти на альтернативы.
              schema: { type: string }
            Sunset:
              description: |-
                Дата, когда устаревший аспект перестанет работать.
              schema: { type: string, format: date-time }
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
components:
  schemas:
    Customer:
      type: object
      additionalProperties: true
      required: [id, email]
      properties:
        id: { type: string }
        email: { type: string, format: email }
        name:
          type: string
          deprecated: true
          description: Используйте поле fullName. Будет удалено после 2026-06-01.
        fullName: { type: string }

Сервер может выставлять Deprecation: true и Sunset: 2026-06-01T00:00

, а в схемах отметить поле как deprecated.

Protobuf и Avro: числа тегов, резервирование и эволюция

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

Protobuf: железные правила

  • Никогда не меняйте номер поля (тег). Это ломает совместимость.
  • Не меняйте тип поля; добавляйте новое поле с новым тегом.
  • Удаляя поле, пометьте его как reserved — чтобы номер и имя не использовались снова.
  • Для «опциональности» в proto3 используйте ключевое слово optional или wrapper-типы.
  • В enum не меняйте числовые значения; добавляйте новые значения в конец.

Пример: эволюция .proto

syntax = "proto3";
package customer.v1;

import "google/protobuf/timestamp.proto";

message Customer {
  string id = 1;                 // нельзя менять номер
  string email = 2;              // тип и смысл неизменны
  // name удаляем — резервируем
  reserved 3;                    // номер 3 больше не используем
  reserved "name";               // и имя тоже

  // Новое поле вместо name
  optional string full_name = 4; // безопасное добавление

  // Было: enum Tier { BASIC = 0; PRO = 1; }
  enum Tier { BASIC = 0; PRO = 1; ENTERPRISE = 2; } // добавили значение 2
  Tier tier = 5;

  // Безопасно добавить время обновления
  google.protobuf.Timestamp updated_at = 6;

  // Вариативные контакты — расширяем через oneof
  oneof contact {
    string phone = 7;
    string telegram = 8; // безопасно добавлено позже
  }
}

Если убрать поле name, мы резервируем тег 3 и имя, чтобы никто случайно не переиспользовал. Добавляем full_name с новым тегом — старые потребители его игнорируют, новые — используют.

Проверка разрывов с Buf

Buf помогает ловить ломающие изменения в CI:

# buf.yaml
version: v1
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT
# Инициализация
buf mod init
# Линтинг стиля и правил
buf lint
# Проверка на разрывы относительно main
buf breaking --against "https://github.com/org/repo.git#branch=main"

Avro: совместимость через «схема писателя/схема читателя»

Avro разрешает добавлять поля с default, удалять необязательные, менять порядок полей. Важно вести реестр схем (Schema Registry) и включить режим совместимости, например BACKWARD или FULL.

Ключевые правила:

  • Новые поля — только с default.
  • Не меняйте тип без миграции.
  • Удаляя поле, убедитесь, что читатели его не требуют.

События: конверты, версии и преобразователи

События живут дольше внутренних API, их чаще читают разные системы. Безопасный подход:

  • Каждый тип события имеет конверт с типом и версией.
  • Тело события эволюционирует по правилам схемы (JSON/Avro/Protobuf).
  • Потребители должны игнорировать неизвестные поля и уметь работать без новых полей.
  • Для больших изменений — добавляйте новый тип события, а старый постепенно «гасите».

Пример: конверт JSON-события

{
  "type": "customer.updated",
  "version": 3,
  "id": "c0a801b2-8c5f-4d2e-8a2e-1f6a2c9a8c31",
  "occurred_at": "2026-01-24T12:34:56Z",
  "data": {
    "id": "7a7c7d6a-7d1a-4d3c-bd26-a6dd2e7b7a21",
    "email": "user@example.com",
    "fullName": "Иван Петров",
    "tier": "PRO",
    "tags": ["beta", "referral"]
  }
}

Потребитель, не знающий fullName и tags, должен продолжить работу: парсить знакомые поля и игнорировать лишнее.

Миграции через преобразователи (upcast/downcast)

Для совместимости по обе стороны иногда полезны преобразователи:

  • upcast: старое событие → новое представление для нового потребителя (добавляем поля по умолчанию, маппим значения);
  • downcast: новое событие → старое представление для старого потребителя.

Преобразователи можно держать на стороне подписчиков или в шине (если она это поддерживает), но лучше — в библиотеке контракта, общей для продюсеров и потребителей.

Контрактные тесты и проверки на разрывы в CI

  • Контрактные тесты от потребителя (Consumer-Driven Contracts) фиксируют ожидания клиента и автоматически проверяют, что сервер/продюсер их не нарушил.
  • Для REST — Pact, Spring Cloud Contract; для Protobuf — buf breaking; для Avro — проверки совместимости в реестре схем.
  • Включайте проверки в CI перед мерджем и релизом. Это дешевле, чем ловить инцидент в проде.

Пример минимального теста на PactJS для REST:

// package.json должен содержать зависимости @pact-foundation/pact
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import fetch from 'node-fetch';

const provider = new PactV3({ consumer: 'web-app', provider: 'customers-api' });

provider
  .addInteraction({
    states: [{ description: 'customer exists' }],
    uponReceiving: 'get customer',
    withRequest: { method: 'GET', path: '/customers/123' },
    willRespondWith: {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
      body: {
        id: '123',
        email: 'user@example.com',
        // Разрешаем дополнительные поля, чтобы не ломаться при расширениях
        name: MatchersV3.regex('John', /.+/)
      }
    }
  })
  .executeTest(async mock => {
    const res = await fetch(`${mock.url}/customers/123`);
    const body = await res.json();
    if (!body.id || !body.email) throw new Error('missing required fields');
  });

Тест допускает дополнительные поля и проверяет лишь необходимые — это залог устойчивости при расширениях.

Инструменты: OpenAPI, JSON Schema, Buf, реестры схем

  • OpenAPI + spectral: статический анализ контрактов, поиск анти-паттернов.
  • JSON Schema: валидация входящих/исходящих данных, «мягкие» расширения.
  • Buf (Protobuf): линтинг и проверка ломающих изменений.
  • Реестр схем (Avro/Protobuf/JSON Schema): хранение версий, режимы совместимости (BACKWARD/FORWARD/FULL), автоматические проверки.
  • Pact/CDC: тесты ожиданий потребителей.

Пошаговый план внедрения за 2 недели

День 1–2:

  • Выберите формат схем (OpenAPI/JSON Schema, Protobuf или Avro).
  • Определите целевой режим совместимости: по умолчанию FULL.

День 3–5:

  • Включите линтеры и проверки в CI: spectral/buf/schema-registry.
  • Обновите документацию: «что считаем ломающим», «как депрекейтить».

День 6–8:

  • Добавьте заголовки Deprecation/Sunset в REST.
  • Внедрите конверт событий с type/version.

День 9–11:

  • Запустите метрики использования устаревших полей (логирование доступов, проценты клиентов).
  • Настройте алерты при попытке деплоя ломающих изменений.

День 12–14:

  • Напишите 2–3 контрактных теста от ключевых потребителей.
  • Проведите «сухой» деплой изменения с добавлением поля, проверьте телеметрию.

Частые ошибки и как их не допустить

  • «Переименуем поле, это же мелочь» — нет, всегда добавляйте новое поле и деактивируйте старое по плану.
  • «Ограничим additionalProperties: false — так надёжнее» — вы лишите себя возможности безопасно расширять контракт.
  • «Удалим поле прямо сейчас, его никто не использует» — проверьте телеметрию, уведомите, поставьте Sunset и только потом удаляйте.
  • «Поменяем enum с 1 на 2, кто заметит?» — заметят. Никогда не меняйте числовые значения enum в Protobuf.
  • «Вернём ошибку, если пришло незнакомое поле» — не нужно. Игнорируйте лишнее (если это не вопрос безопасности).

Краткий чек-лист для команды

  • Новые поля — необязательные, с понятным default на стороне клиента.
  • Не меняем тип/смысл существующих полей.
  • Не переиспользуем номера тегов (Protobuf) — удалённые поля резервируем.
  • Для событий — конверт с type/version, потребители игнорируют неизвестные поля.
  • Включены проверки на разрывы в CI (spectral/buf/registry).
  • Процесс депрекейта: Deprecation/Sunset, метрики, дата удаления.
  • Есть 2–3 контрактных теста от ключевых потребителей.

Эти практики позволяют выпускать ценность каждую неделю, а не ждать «идеальную v2». В результате снижаются инциденты, ускоряется time‑to‑market и уменьшаются расходы на поддержку интеграций.


APIсхемысовместимость