«Ya declararemos obsoleta la v1 en algún momento» es una manera de asegurarse de que la v1 no se vaya nunca. Pon las fechas por escrito donde el código pueda leerlas:
// config/api.php
return [
'versions' => [
'v1' => [
'deprecated' => '2027-03-01',
'sunset' => '2027-09-01',
],
'v2' => [],
],
];
Un middleware convierte esas fechas en las cabeceras de respuesta estándar, para que cualquier consumidor que las consulte se entere automáticamente:
// app/Http/Middleware/AnnounceDeprecation.php
public function handle(
Request $request,
Closure $next,
): Response {
$response = $next($request);
$version = $request->attributes->get('api_version');
$dates = config("api.versions.{$version->value}", []);
if (! isset($dates['deprecated'], $dates['sunset'])) {
return $response;
}
$deprecated = Carbon::parse($dates['deprecated']);
if ($deprecated->isPast()) {
$response->headers->add([
'Deprecation' => '@'.$deprecated->timestamp,
'Sunset' => Carbon::parse($dates['sunset'])
->toRfc7231String(),
]);
}
return $response;
}
Va en el grupo api después de ResolveApiVersion, porque lee lo que aquel fijó.
Dos fechas, no una. La primera abre el periodo de aviso: todavía funciona, pero ya no es la recomendada. La segunda es el día en que deja de funcionar. El hueco entre ellas es la ventana de migración. Debería ser lo bastante larga para que un consumidor planifique el trabajo y lo bastante corta para que alguien sienta urgencia. Noventa días es un suelo razonable. Un socio atado al calendario de lanzamientos de otro puede necesitar más.
Ese es el último middleware que añade este libro, así que aquí está el closure entero tal como queda. Dentro de cada llamada a api(), el orden es el orden en que se ejecutan los middleware:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(at: ['10.0.0.0/8']);
$middleware->throttleApi();
$middleware->api(prepend: [
AssignTraceId::class,
SetLocale::class,
]);
$middleware->api(append: [
LogApiRequest::class,
'cache.headers:no_store',
ResolveApiVersion::class,
AnnounceDeprecation::class,
]);
$middleware->alias([
'ability' => CheckForAnyAbility::class,
'idempotent' => Idempotent::class,
]);
$middleware->prependToPriorityList(
before: SubstituteBindings::class,
prepend: CheckForAnyAbility::class,
);
})
Avisar a los consumidores antes de que se enteren
Las cabeceras llegan solo a los consumidores que inspeccionan cabeceras, que en la práctica es casi nadie hasta el día en que su integración se rompe. Una cabecera de respuesta es una cortesía para los cuidadosos. No es una estrategia de notificación.
Una vez vi caerse durante cuatro horas la integración de un socio porque pasó la fecha de retirada de una versión y nadie de su lado había estado leyendo las cabeceras Sunset. El arreglo llevó diez minutos una vez que alguien lo notó. Enterarse llevó toda la mañana. Diez minutos de arreglo frente a cuatro horas de no saber es el argumento para enviar el aviso de forma activa y no esperar a que alguien vaya a buscarlo.
Ya sabes a quién avisar. El Capítulo 6 registra cada petición con su consumidor. Añade una columna api_version a api_requests, y una línea a LogApiRequest que la rellene a partir del atributo de la petición, y registrará también la versión. Los consumidores que siguen llamando a v1 están a una consulta de distancia, y las notificaciones de Laravel entregan el mensaje:
// app/Console/Commands/NotifyDeprecatedVersion.php
$stillOnV1 = ApiRequest::query()
->where('api_version', ApiVersion::V1)
->where('created_at', '>=', now()->subDays(30))
->distinct()
->pluck('consumer_id');
Notification::send(
Consumer::query()->whereKey($stillOnV1)->get(),
new VersionDeprecated(ApiVersion::V1),
);
Consumer necesita el trait Notifiable y un método routeNotificationForMail() que devuelva su contact_email, y VersionDeprecated es una clase de notificación corriente salida de php artisan make:notification. Como se dirige solo a los consumidores afectados, nadie aprende a ignorarla. (Documentación de Laravel: Notifications.)
Envíala el día que se fija la fecha de obsolescencia, otra vez a mitad de la ventana, y una vez más una semana antes de la retirada. Tres avisos, espaciados, valen más que uno que nadie recuerda haber leído. El silencio se lee como «nada cambió», justo hasta que algo ha cambiado.