OOscar Dev BlogWeb · AI · Craft
Volver al blog
Backend#nestjs#dependency-injection#backend#fintech

El Provider de NestJS que Firmó un Contrato que tu Banco No Renovó

Tu @Injectable vive en el scope equivocado y el proveedor externo ya cambió las reglas. Lo que aprendí cuando PSE, Bre-B y Transbank dejaron de hablar con mi grafo.

OOscar Vargas13 de septiembre de 20267 min de lectura

En algún momento, entre el tercer deploy del día y la alerta de SQS a las 11:47 PM, me cayó una verdad incómoda: NestJS no gestiona tu dependency injection, te la presta. Y en consumer credit origination, prestar es peligroso. El día que el proveedor externo decide cambiar su contrato — token rotativo, ventana de 45 segundos, firma por request — tu grafo de providers no se entera. Sigue inyectando. Sigue fallando.

Esto no es teoría. Es el incidente del que casi no te cuento.

El provider que parecía correcto

Teníamos un TransbankClient declarado comosingleton, con un wrapper que cacheaba la respuesta delinitTransaction por 30 segundos. Sonaba razonable. Los tests pasaban. El throughput mejoraba. Hasta que el proveedor cambió la política de idempotencia y rompió tres cosas a la vez:

  • El TTL del cache dejó de tener sentido (el proveedor rechaza reintentos en menos de 90s)
  • La firma pasó de HMAC a RSA con rotación diaria
  • El correlation ID dejó de ser opcional

El @Injectable() no se enteró de nada. Siguió inyectando la misma instancia, con la misma firma vieja, contra un endpoint que ahora devolvía 401. ¿La pregunta incómoda? ¿quién es el dueño real del ciclo de vida: NestJS o el contrato con el proveedor?

Cuando el scope miente

El error más caro en banking no es usar Scope.REQUEST cuando no toca. Es asumir que NestJS resuelve el ciclo de vida por ti. Lo resuelvedentro de tu proceso. El contrato con PSE, Bre-B o Transbank vivefuera, y cuando cambia, te enteras por un P1 en PagerDuty.

@Injectable({ scope: Scope.DEFAULT })
export class TransbankClient {
  private signer: Signer;
  private idempotencyWindow = 90_000;

  constructor(
    private readonly http: HttpService,
    @Inject('TRANSBANK_CONFIG') private readonly cfg: TbConfig,
    private readonly clock: ClockPort,
  ) {
    this.signer = new HmacSigner(cfg.secret); // <- esto muere el martes
  }

  async init(tx: InitTx): Promise<TbResponse> {
    const signature = this.signer.sign(tx);
    return firstValueFrom(
      this.http.post(this.cfg.initUrl, tx, { headers: { Signature: signature } }),
    );
  }
}

Mira la línea del constructor. La firma se crea una vez, cuando NestJS instancia el provider. Eso es singleton behavior. El día que Transbank rota la clave, tu proceso sigue firmando con la llave muerta hasta el próximo restart. Y si el proceso está en un contenedor con liveness probe sano, ese restart puede tardar horas.

Lo que cambié (y lo que todavía no)

Después del incidente, movimos tres cosas. Primero, el signer dejó de ser estado interno: ahora es un FactoryProvider conuseFactory y un ClockPort inyectado, para poder mockear el reloj en tests. Segundo, el idempotencyWindow pasó a ser un parámetro del contrato, no una constante hardcoded. Tercero, el refresh de credenciales vive en un sidecar que el provider consulta, no dentro del provider.

Suena limpio. Lo es. Pero falta lo más importante: ¿cómo pruebas que tu grafo de providers sigue siendo válido cuando un proveedor cambia su contrato un viernes a las 6 PM? Esa pregunta se responde con contract tests, no con unit tests. Y contract tests en NestJS, con mocking de módulos, son un dolor que merece su propio post.

El próximo artículo va de eso: cómo escribir contract tests que sobreviven a tres equipos mergeando el mismo módulo, y por qué tuTest.createTestingModule no te está protegiendo de lo que crees.

Mientras tanto, hazte una pregunta antes del próximo deploy: ¿cuántos de tus @Injectable() están firmando contratos con proveedores que ya cambiaron las reglas? Si la respuesta te toma más de cinco segundos, tienes un proveedor durmiendo en tu grafo.

Compartir artículo

O

Escrito por

Oscar Vargas

Apasionado por el desarrollo web, las herramientas modernas y compartir lo que aprende en el camino. Más artículos abajo.

Siguiente artículo →

NestJS Dependency Injection en Banking: Lo que Nadie te Cuenta

Artículos relacionados