«Ya depreciaremos v1 en algún momento» no es una política de deprecación: es una forma de asegurarse de que v1 no se va nunca. Pon las fechas en el propio enum, para que la deprecación sea un hecho sobre el que tu código pueda actuar y no una nota en una wiki que nadie lee.
// app/Enums/ApiVersion.php (continued)
use Illuminate\Support\Carbon;
public function deprecatedAt(): ?Carbon
{
return match ($this) {
self::V1 => Carbon::parse('2026-09-01'),
self::V2 => null,
};
}
public function sunsetAt(): ?Carbon
{
return match ($this) {
self::V1 => Carbon::parse('2027-01-15'),
self::V2 => null,
};
}
Un middleware convierte esas fechas en las cabeceras estándar, así que cualquier consumidor que se moleste en mirarlas se entera automáticamente:
// app/Http/Middleware/AddDeprecationHeadersMiddleware.php
final readonly class AddDeprecationHeadersMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
/** @var ApiVersion $version */
$version = $request->attributes->get('api_version', ApiVersion::default());
$deprecatedAt = $version->deprecatedAt();
if ($deprecatedAt instanceof Carbon && $deprecatedAt->isPast()) {
$response->headers->set('Deprecation', 'true');
$sunsetAt = $version->sunsetAt();
if ($sunsetAt instanceof Carbon) {
$response->headers->set('Sunset', $sunsetAt->toRfc7231String());
}
}
return $response;
}
}
Dos fechas, no una. deprecatedAt marca la versión como oficialmente avisada: sigue funcionando, pero ya no es la recomendación. sunsetAt es la fecha en que deja de funcionar del todo. El hueco entre ambas es la ventana real de migración, y debería ser lo bastante largo para que un consumidor programe el trabajo, pero no tanto como para que nadie sienta urgencia. Noventa días es un suelo razonable para la mayoría de integraciones.
Avisar antes de que se enteren
Las cabeceras sólo alcanzan a quien inspecciona 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 caer la integración de un socio durante cuatro horas porque la fecha de retirada de una versión pasó en silencio y nadie de su lado leía cabeceras Sunset. El arreglo llevó diez minutos en cuanto alguien se dio cuenta. Enterarse llevó toda la mañana. Ese hueco —diez minutos de arreglo frente a cuatro horas de no saberlo— es todo el argumento a favor de empujar el aviso en lugar de esperar a que alguien lo consulte.
// app/Jobs/NotifyBearersOfDeprecationJob.php
final class NotifyBearersOfDeprecationJob implements ShouldQueue
{
use Queueable;
public int $tries = 3;
/** @var array<int, int> */
public array $backoff = [60, 300];
public function __construct(
public ApiVersion $version,
) {}
public function handle(): void
{
Bearer::query()->chunkById(100, function ($bearers) {
foreach ($bearers as $bearer) {
// dispatch the actual notification (email, webhook, whatever
// this consumer registered) per bearer, here
}
});
}
}
Despáchalo el día que se fija la fecha de deprecación, otra vez a mitad de la ventana y una tercera una semana antes de la retirada. Tres avisos espaciados ganan a uno que nadie recuerda haber leído. El silencio no es neutral aquí: el silencio se lee como «no ha cambiado nada», justo hasta que cambió.