HN 표시: Laravel용 API 버전 관리 – 하나의 코드베이스, 이전 버전을 영원히 지원
hackernews
|
|
💼 비즈니스
#api
#laravel
#stripe
#버전관리
#팁
원문 출처: hackernews · Genesis Park에서 요약 및 분석
요약
이 패키지는 단일 코드베이스를 유지하면서도 2014년 이전 버전의 API와의 호환성을 지원하는 Stripe 스타일의 버전 관리 기능을 Laravel에 구현합니다. 각 버전은 중복 컨트롤러가 아닌 변환 레이어를 통해 요청과 응답의 데이터 형식을 자동으로 변환하여 처리합니다. 또한 URL 접두사나 헤더 등 4가지 버전 감지 전략을 지원하며, 변경 로그 생성 및 감사를 위한 아티즌 명령어를 포함하고 있습니다.
본문
Your API changes. Your controllers don't. Inspired by Stripe's API versioning architecture — you write small transformer classes that describe what changed between versions. The package upgrades old requests and downgrades new responses automatically. Your controllers always speak the latest version. Supports Laravel 10, 11 & 12 · PHP 8.1+ You shipped v1. A mobile app depends on it. Now you need v2 with different field names. Your options are: duplicate every controller per version (bug fixes applied N times), scatter if/else version checks everywhere, or use this package — one controller, one version, transformers handle the rest. // Your controller. Always latest version. That's it. class UserController extends Controller { public function show(User $user) { return response()->json([ 'handle' => $user->handle, 'roles' => $user->roles, ]); } } A v1 client hits this endpoint and gets back username , role — automatically downgraded by the transformer you wrote once. ┌──────────┐ upgrade chain ┌────────────┐ │ v1 Client│ ───────────────► │ Controller │ ← always writes v3 │ Request │ v1 → v2 → v3 │ (latest) │ └──────────┘ └─────┬──────┘ │ v3 response ┌──────────┐ downgrade chain │ │ v1 Client│ ◄────────────────┌─────▼──────┐ │ Response │ v3 → v2 → v1 │ Response │ └──────────┘ └────────────┘ Old request comes in → upgraded to latest → controller processes it → response downgraded back to the client's version. Your controller never changes. composer require jayanta/laravel-api-versionist php artisan vendor:publish --tag=api-versionist-config Laravel auto-discovers the service provider. No manual registration. 1. Generate a transformer: php artisan api:make-transformer v2 2. Define what changed: // app/Api/Transformers/V2Transformer.php final class V2Transformer extends ApiVersionTransformer { public function version(): string { return 'v2'; } public function description(): string { return 'Renamed username to handle, converted role string to roles array.'; } public function upgradeRequest(array $data): array { if (isset($data['username'])) { $data['handle'] = $data['username']; unset($data['username']); } if (isset($data['role']) && is_string($data['role'])) { $data['roles'] = [$data['role']]; unset($data['role']); } return $data; } public function downgradeResponse(array $data): array { if (isset($data['handle'])) { $data['username'] = $data['handle']; unset($data['handle']); } if (isset($data['roles']) && is_array($data['roles'])) { $data['role'] = $data['roles'][0] ?? 'user'; unset($data['roles']); } return $data; } } 3. Register it: // config/api-versionist.php 'latest_version' => 'v2', 'transformers' => [ App\Api\Transformers\V2Transformer::class, ], 'response_data_key' => null, // null for flat JSON, 'data' for wrapped responses If you leave latest_version as 'v1' after adding a V2Transformer, no transformers will run. Always update this value. 4. Add middleware: // routes/api.php Route::middleware('api.version')->group(function () { Route::get('/users/{user}', [UserController::class, 'show']); }); // or use the shorthand macro Route::versioned()->group(function () { /* ... */ }); 5. Test it: curl -H "X-Api-Version: v2" http://your-app.test/api/users/1 # → {"handle": "janedoe", "roles": ["admin"]} curl -H "X-Api-Version: v1" http://your-app.test/api/users/1 # → {"username": "janedoe", "role": "admin"} Each transformer describes one version transition. A V2Transformer handles v1→v2. A V3Transformer handles v2→v3. The package chains them automatically. final class V2Transformer extends ApiVersionTransformer { public function version(): string { return 'v2'; } public function description(): string { return 'Renamed username to handle, role string to roles array.'; } public function releasedAt(): ?string { return '2025-03-01'; } public function upgradeRequest(array $data): array { if (isset($data['username'])) { $data['handle'] = $data['username']; unset($data['username']); } if (isset($data['role']) && is_string($data['role'])) { $data['roles'] = [$data['role']]; unset($data['role']); } return $data; } public function downgradeResponse(array $data): array { if (isset($data['handle'])) { $data['username'] = $data['handle']; unset($data['handle']); } if (isset($data['roles']) && is_array($data['roles'])) { $data['role'] = $data['roles'][0] ?? 'user'; unset($data['roles']); } return $data; } } Fields you don't touch pass through unchanged. A request with age , city , custom_field keeps all of them — transformers only modify what they explicitly reference. Transformers are resolved through Laravel's service container, so DI works: final class V3Transformer extends ApiVersionTransformer { public function __construct( private readonly UserRepository $repo ) {} public function version(): string { return 'v3'; } public function description(): string { return 'Added legacy_role lookup for v2 clients.'; } public function downgradeResponse(array $data): array { if (isset($data['user_id'])) { $data['legacy_role'] = $this
Genesis Park 편집팀이 AI를 활용하여 작성한 분석입니다. 원문은 출처 링크를 통해 확인할 수 있습니다.
공유