NestJS v9: implement durable providers

855 views Asked by At

[SOLVED] I'm pretty new to NestJS and trying to get my head around durable providers but i can't get them to work.

My scenario is that i have a service with some logic and two providers that implement the same interface to get some data. Depending on a custom header value i want to use Provider1 or Provider2 and the service itself does not have to know about the existing provider implementations.

Since i'm in a request scoped scenario but i know there are only 2 possible dependency-subtrees i want to use durable providers that the dependencies are not newly initialised for each request but reused instead.

I set up the ContextIdStrategy as described in the official docs and it is executed on each request but i miss the part how to connect my provider implementations with the ContextSubtreeIds created in the ContextIdStrategy.

Interface:

export abstract class ITest {
  abstract getData(): string;
}

Implementations:

export class Test1Provider implements ITest {
  getData() {
    return "TEST1";
  }
}
export class Test2Provider implements ITest {
  getData() {
    return "TEST2";
  }
}

Service:

@Injectable()
export class AppService {
  constructor(private readonly testProvider: ITest) {}

  getHello(): string {
    return this.testProvider.getData();
  }
}

Controller:

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getData(): string {
    return this.appService.getData();
  }
}

ContextIdStrategy:

const providers = new Map<string, ContextId>([
  ["provider1", ContextIdFactory.create()],
  ["provider2", ContextIdFactory.create()],
]);

export class AggregateByProviderContextIdStrategy implements ContextIdStrategy {
  attach(contextId: ContextId, request: Request) {
    const providerId = request.headers["x-provider-id"] as string;
    let providerSubTreeId: ContextId;

    if (providerId == "provider1") {
      providerSubTreeId = providers["provider1"];
    } else if (providerId == "provider2") {
      providerSubTreeId = providers["provider2"];
    } else {
      throw Error(`x-provider-id ${providerId} not supported`);
    }

    // If tree is not durable, return the original "contextId" object
    return (info: HostComponentInfo) =>
      info.isTreeDurable ? providerSubTreeId : contextId;
  }
}

Main:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  ContextIdFactory.apply(new AggregateByProviderContextIdStrategy());
  await app.listen(3000);
}
bootstrap();

Module:

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    {
      provide: ITest,
      useFactory: () => {
        // THIS IS THE MISSING PIECE. 
        // Return either Test1Provider or Test2Provider based on the ContextSubtreeId 
        // which is created by the ContextIdStrategy
        return new Test1Provider();
      },
    },
    AppService,
  ],
})
export class AppModule {}
1

There are 1 answers

0
mago On

The missing part was a modification of the ContextIdStrategy return statement:

return {
  resolve: (info: HostComponentInfo) => {
    const context = info.isTreeDurable ? providerSubTreeId : contextId;
    return context;
  },
  payload: { providerId },
}

after that change, the request object can be injected in the module and where it will only contain the providerId property and based on that, the useFactory statement can return different implementations