La verdad incómoda sobre un paquete compartido: no elimina la coordinación, la traslada a un sitio más disciplinado.
// composer.json
"require": {
"webplo/api-infrastructure": "^1.2.11"
}
Tres servicios, un número de versión, y ese es justamente el punto: ninguno puede desviarse hacia su propia copia de lo que hace EnsureTokenIsValidMiddleware. Pero un arreglo en ese middleware tiene ahora que llegar a tres servicios, no a uno, antes de estar realmente vivo en alguna parte. Etiquetar una versión nueva no hace eso por sí solo. Hay que subir la restricción de cada consumidor, y hay que reverificar cada consumidor sobre la versión nueva, porque «el paquete cambió» y «todos los servicios que dependen del paquete siguen comportándose correctamente» son dos hechos distintos, y sólo uno de ellos es automático.
La respuesta de la flota es un ritual, no una esperanza:
composer update
↓
composer bump
↓
composer update
↓
composer check
↓
composer test
Sáltate los dos últimos pasos y habrás hecho el 90 % fácil del trabajo —traerte una versión nueva— y habrás dejado sin hacer el 10 % que de verdad importa: demostrar que la versión nueva no rompe al consumidor. Haz esto una vez por consumidor, cada vez que cambie el paquete compartido, y un arreglo de una línea cuesta tres pull requests y tres ejecuciones de pruebas en lugar de una. No porque la flota sea ineficiente, sino porque ese es el precio real y honesto de garantizar que tres servicios nunca puedan discrepar calladamente sobre cómo se valida un token. Un equipo que no esté dispuesto a pagar ese precio no debería extraer el paquete compartido en primer lugar, porque la alternativa —tres copias de la misma lógica separándose poco a poco— es una deuda que se compone en silencio en lugar de un coste que pagas por adelantado y puedes ver.
Por eso la superficie compartida tiene que seguir siendo pequeña. Cada clase que añades es una clase que cada consumidor tiene ahora que reverificar en cada subida, para siempre. Un paquete compartido se gana el sueldo siendo las cinco o seis cosas que de verdad deben ser idénticas en todas partes. No se lo gana siendo cómodo.
Credenciales que viajan con el token, no con el servicio
Una pregunta que la flota tuvo que responder directamente: cuando el proveedor externo de un inquilino necesita una credencial, ¿dónde vive esa credencial?
La respuesta ingenua es una variable de entorno por proveedor, fijada una vez por servicio. Eso funciona exactamente hasta que un segundo inquilino se registra con su propia cuenta en ese proveedor. En ese punto la variable sólo puede ser el token de un inquilino, y las peticiones de todos los demás o fallan o, peor, usan calladamente la credencial de otro. No puedes arreglarlo añadiendo una segunda variable, porque no sabes en el momento del despliegue cuántos inquilinos habrá. El número de variables de entorno que necesita una aplicación no debería escalar con el número de clientes que la usan.
El arreglo real es guardar la credencial donde el inquilino ya está: en su propio token Bearer, no en el entorno del servicio.
// app/Http/Middleware/ConfigureBearerTokenMiddleware.php
public function handle(Request $request, Closure $next): Response
{
if ($request->bearerToken()) {
$bearer = $request->attributes->get('bearer');
if ($bearer && is_array($bearer->settings)) {
$serverSettings = $bearer->settings['server'] ?? null;
if (is_array($serverSettings)) {
$driver = $serverSettings['driver'] ?? null;
if (is_string($driver)) {
// ...resolve the driver's token for this tenant
Config::set('services.cloud.server.default', $driver);
Config::set("services.cloud.server.drivers.{$driver}.token", $driverTokenStr);
}
}
}
}
return $next($request);
}
Bearer es el modelo del paquete compartido del capítulo 3, y su campo de ajustes son las preferencias y credenciales del propio inquilino, adjuntas al mismo token que ya autenticó la petición. Este middleware —local a la aplicación por diseño, no parte del paquete compartido— lee esos ajustes una vez por petición y los escribe en config() para el resto del ciclo. Para cuando un controlador o un job busca la credencial del proveedor, está leyendo la de ese inquilino concreto, resuelta desde el token que demostró quién llama, no un valor horneado en el entorno en tiempo de despliegue.
Esta es la lección tres: las credenciales por inquilino deberían viajar con el token de autenticación, no con la configuración por servicio. El token ya establece quién llama. Adjuntar las credenciales de ese llamante al mismo registro significa que todos los servicios que autentican la petición obtienen la credencial correcta gratis, sin una tabla de búsqueda, sin una segunda base de datos, sin una variable de entorno que tenga que crecer cada vez que se registre un inquilino nuevo.
Fíjate en que este middleware vive en api-server, no en el paquete compartido. Leer los ajustes del bearer y decidir qué hacer con una clave de driver es específico de lo que api-server aprovisiona. El trabajo del paquete compartido terminó al devolver un modelo Bearer validado con sus ajustes intactos. Lo que un servicio haga con esos ajustes es asunto de ese servicio, que es exactamente la frontera que trazó la sección anterior.
Resumen
Qué viaja en el paquete compartido
- [X] Autenticación por token Bearer, resuelta y cacheada de forma idéntica en cada servicio
- [X] Límite de frecuencia, registrado una vez y aplicado igual en todas partes
- [X] Sobres estándar de respuesta y error, para que un solo analizador de cliente funcione con todos los servicios
- [X] Un cliente HTTP saliente resiliente con reintento y espera horneados, no reimplementados por integración
- [X] Un endpoint de salud, para que «¿está en pie este proceso?» signifique lo mismo en toda la flota
Qué nunca
- [X] Lógica de negocio específica de un dominio: un
LicenseManagerse queda donde vive la licencia - [X] Su propio servidor, cola o estado: cada job que despacha corre sobre infraestructura que el anfitrión ya posee
- [X] Asumir una credencial global por proveedor: las credenciales por inquilino viven en el token del inquilino
Un paquete compartido es la promesa de que una docena de servicios se comportarán como un solo sistema en lugar de como una docena de variaciones accidentales sobre un tema. Mantén esa promesa pequeña, verifícala en cada servicio cada vez que cambie, y nunca dejes que le crezca un servicio propio. En cuanto lo haga, no habrás simplificado tu flota: habrás añadido un nodo más que nadie acordó mantener en pie.