Версионирование API в Laravel: Как сделать это правильно

Источник: «API Versioning in Laravel: The Complete Guide to Doing it Right»
Вы можете легко ориентироваться в версиях API Laravel с помощью Версионирования URI, обеспечивая последовательную и надёжную интеграцию даже по мере развития приложения.

Обеспечение постоянства и надёжности API сродни удержанию корабля на плаву во время шторма. По мере того как ваше приложение растёт и изменяется, возникает необходимость управлять различными версиями API. Laravel, популярный PHP-фреймворк, предлагает множество инструментов, помогающих ориентироваться в этих неспокойных водах. Но при таком количестве маршрутов как убедиться, что вы идёте по правильному пути? На помощь приходит версионирование API.

Вы можете версионировать Laravel API различными способами, но наиболее простой подход — это использование версионирования URI. В этом случае путь URI будет включать /v*/, где * — номер версии. Обычно вы не используете семантическое версионирование при версионировании API, так как это может вызвать болезненные проблемы для тех, кто интегрируется с вашим API. Вместо этого вы фокусируетесь на ограничении мажорных версий и стараетесь держать изменения в этих версиях как можно дальше.

Обычно в приложении Laravel есть несколько файлов маршрутов в каталоге routes вашего приложения. Основные из них, это web.php и api.php, в которых мы регистрируем все наши Web- и API-маршруты для Laravel. Они загружаются через app/Providers/RouteServiceProvider.php, по крайней мере, во время написания статьи, где Laravel 10 является основной версией, которую мы рассматриваем. Когда выйдет Laravel 11 с его новым облегчённым скелетом, нам, возможно, придётся переосмыслить некоторые вещи.

Внутри RouteServiceProvider у нас есть следующий код, загружающий файлы маршрутов для приложения.

public function boot(): void
{
// Rate Limiter

$this->routes(function (): void {
Route::middleware('api')->prefix('api')->group(
base_path('routes/api.php'),
);

Route::middleware('web')->group(
base_path('routes/web.php'),
);
});
}

Это указывает нашему приложению Laravel загружать оба маршрута — web и API, один с префиксом api, а другой — без. Здесь также указывается, какие группы middleware следует использовать для загружаемых маршрутов.

Если вы создаёте приложение с веб- и API-интерфейсами, оставьте все как есть — это абсолютно нормально. Если вы удалите веб-интерфейс и остановитесь на API-приложении, удалите префикс и определение веб-маршрута, поскольку они вам не понадобятся.

Секретный соус версионирования API в Laravel

Если я использую веб-интерфейс и API-интерфейс для своего приложения, то предпочитаю создавать дополнительные каталоги в routes — в итоге у меня получается следующая структура:

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

Возьмём пример интернет-магазина, продающего фирменную атрибутику. Если бы это была платформа только с API, у нас были бы такие маршруты, как products или orders и categories, чтобы люди могли искать, уточнять и заказывать то, что они ищут. Мы могли бы хранить все это в одном центральном файле routes.php. Однако по мере роста вашего приложения управлять таким большим файлом становится все сложнее и сложнее. Давайте посмотрим на пример этого, особенно если вы также собираетесь реализовать версионность в вашем API.

Route::prefix('v1')->as('v1:')->group(static function (): void {
Route::prefix('orders')->as('orders:')->group(static function (): void {
Route::get('/', Orders\V1\ListHandler::class)->name('list');
});

Route::prefix('products')->as('products:')->group(static function (): void {
Route::get('/', Products\V1\ListHandler::class)->name('list');
});
});

Route::prefix('v2')->as('v2:')->group(static function (): void {
Route::prefix('orders')->as('orders:')->group(static function (): void {
Route::get('/', Orders\V2\ListHandler::class)->name('list');
});

Route::prefix('products')->as('products:')->group(static function (): void {
Route::get('/', Products\V2\ListHandler::class)->name('list');
});
});

Это всего лишь две конечные точки, обе относительно простые. Однако визуально это может оказаться слишком сложным. Создавая приложение Laravel, мы должны учитывать когнитивную нагрузку при работе с этим приложением. Если вам придётся искать в приложении простое определение маршрута, как вы будете себя чувствовать к тому моменту, когда перейдёте к реальному коду, над которым нужно будет работать?

Я предпочитаю структурировать этот основной файл маршрута входа следующим образом:

Route::prefix('v1')->as('v1:')->group(
base_path('routes/v1/routes.php'),
);

Route::prefix('v2')->as('v2:')->group(
base_path('routes/v2/routes.php'),
);

Это делает точку входа простой и понятной, просто указывая на другое место. Если вам важны маршруты v1, проверьте этот файл. Он предсказуем. Как только вы откроете каталог routes с одним файлом и каталогами, названными по версиям API, вы сразу поймёте, где искать. Вы можете упростить навигацию отсюда, снизив когнитивную нагрузку при управлении приложением. Давайте заглянем в routes/v1/routes.php, чтобы понять, как мы можем это разделить.

Route::prefix('orders')->as('orders:')->group(
base_path('routes/v1/orders.php'),
);

Route::prefix('products')->as('orders:')->group(
base_path('routes/v1/products.php'),
);

Мы ещё больше разделили маршруты, разнеся сгруппированные маршруты по отдельным файлам, что ещё больше снижает когнитивную нагрузку. Таким образом, когда мы открываем routes/v1, мы можем сразу увидеть все группы маршрутов в нашем приложении и перейти непосредственно к той группе, которая, как мы знаем, нам нужна. Кроме того, включение нескольких файлов не влияет на производительность приложения, поскольку в любом случае вы обычно кэшируете свои маршруты в продакшене — это позволит объединить все маршруты в один легко читаемый фреймворком файл.

Чтобы отразить это, наши контроллеры или обработчики также управляются в соответствующих пространствах имён, что позволяет легко обходить их внутри вашего приложения. Я видел людей, которые переопределяют RouteServiceProvider для групп маршрутов и загрузки. Раньше я тоже был одним из них. Однако мне показалось не интуитивным обращаться к сервис-провайдеру маршрутов для понимания группировки по сравнению с обращением непосредственно к самим файлам маршрутов.

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

Дополнительные материалы

Предыдущая Статья

Laravel под капотом: CSRF

Следующая Статья

Противодействие Login CSRF с помощью Symfony