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

Для чего нужно версионирование API?

2.0 Middle🔥 111 комментариев
#REST API и микросервисы

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

# Для чего нужно версионирование API?

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

Основные причины версионирования

1. Обратная совместимость

Высвобождение новой версии API не должно ломать существующих клиентов. Разные клиенты обновляются в разное время, поэтому сервер должен поддерживать несколько версий одновременно.

// Версия 1: простой ответ
@GetMapping("/api/v1/users/{id}")
public User getUserV1(@PathVariable Long id) {
    return userService.findById(id);
}

// Версия 2: расширенный ответ
@GetMapping("/api/v2/users/{id}")
public UserDetailedResponse getUserV2(@PathVariable Long id) {
    User user = userService.findById(id);
    return new UserDetailedResponse(user.getId(), user.getName(), user.getStats());
}

2. Независимое развитие

Даже когда вы контролируете оба конца (фронтенд и бэкенд), версионирование позволяет:

  • Разрабатывать новые функции независимо
  • Откатываться на старую версию при критических ошибках
  • Тестировать новую версию отдельно
@Service
public class ApiVersionManager {
    // Логика для постепенного перехода с v1 на v2
    public ApiVersion getVersion(String clientVersion) {
        if (clientVersion.startsWith("1.")) {
            return ApiVersion.V1;
        }
        return ApiVersion.V2;
    }
}

3. Удаление устаревшего функционала

После достаточного времени поддержки старую версию можно прекратить. Без версионирования пришлось бы поддерживать весь устаревший код вечно.

// Помечаем старую версию как deprecated
@Deprecated(since = "2.0", forRemoval = true)
@GetMapping("/api/v1/users/{id}")
public User getUserV1(@PathVariable Long id) {
    logger.warn("Using deprecated API v1. Please upgrade to v2");
    return userService.findById(id);
}

Стратегии версионирования

URI Versioning (URL Path)

Версия указывается в URL пути. Наиболее распространённый подход.

@RestController
@RequestMapping("/api")
public class UserController {
    @GetMapping("/v1/users/{id}")
    public UserV1 getUserV1(@PathVariable Long id) { ... }
    
    @GetMapping("/v2/users/{id}")
    public UserV2 getUserV2(@PathVariable Long id) { ... }
}

Преимущества: простота, явность, разные эндпоинты для разных версий Недостатки: дублирование кода, усложнение маршрутизации

Query Parameter Versioning

Версия передаётся как параметр запроса.

@GetMapping("/users/{id}")
public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "1") int version) {
    if (version == 1) {
        return new UserV1(userService.findById(id));
    }
    return new UserV2(userService.findById(id));
}
// GET /users/123?version=1
// GET /users/123?version=2

Преимущества: один контроллер, гибкость Недостатки: менее явно, сложнее с кешированием, легко упустить версию

Header Versioning

Версия передаётся в заголовке запроса.

@GetMapping("/users/{id}")
public Object getUser(@PathVariable Long id, 
                      @RequestHeader("Accept-Version") String version) {
    if ("1".equals(version)) {
        return buildUserV1Response(userService.findById(id));
    }
    return buildUserV2Response(userService.findById(id));
}

Преимущества: чистые URL, стандартный HTTP подход Недостатки: менее очевидно, сложнее с браузерными запросами

Практический пример миграции

@RestController
@RequestMapping("/api")
public class ProductController {
    
    @GetMapping("/v1/products/{id}")
    public ProductResponse_v1 getProductV1(@PathVariable Long id) {
        Product product = productService.findById(id);
        return new ProductResponse_v1(
            product.getId(),
            product.getName(),
            product.getPrice()
        );
    }
    
    @GetMapping("/v2/products/{id}")
    public ProductResponse_v2 getProductV2(@PathVariable Long id) {
        Product product = productService.findById(id);
        return new ProductResponse_v2(
            product.getId(),
            product.getName(),
            product.getPrice(),
            product.getDescription(),
            product.getStock(),
            product.getCategory()
        );
    }
}

// Версия 1: минимальный ответ
record ProductResponse_v1(Long id, String name, BigDecimal price) {}

// Версия 2: расширенный ответ
record ProductResponse_v2(
    Long id, 
    String name, 
    BigDecimal price,
    String description,
    Integer stock,
    String category
) {}

Семантическое версионирование (SemVer)

Общепринятый стандарт для версий API: MAJOR.MINOR.PATCH

  • MAJOR (v1 → v2): несовместимые изменения, ломающие изменения
  • MINOR (v1.0 → v1.1): новые функции, обратно совместимые
  • PATCH (v1.0.0 → v1.0.1): исправления ошибок
// Пример версионирования через фасад
@Service
public class ApiVersionFacade {
    private static final String CURRENT_VERSION = "2.1.0";
    private static final String DEPRECATED_VERSION = "1.5.0"; // последняя v1
    
    public boolean isVersionSupported(String clientVersion) {
        // Поддерживаем все v2.x и последнюю v1
        return clientVersion.startsWith("2.") || 
               clientVersion.compareTo(DEPRECATED_VERSION) >= 0;
    }
}

Лучшие практики

  1. Не переусложняй — используй версионирование, только если действительно нужно
  2. Документируй изменения — помечай deprecated методы, веди CHANGELOG
  3. Установи срок жизни — объявляй дату прекращения поддержки версии
  4. Минимизируй дублирование — используй общие сервисы, отличайся только в DTO
  5. Тестируй обе версии — убедись, что обе версии работают корректно
  6. Мониторь использование — отслеживай, какие версии используют клиенты

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