From 3ca03fe184eaeb186270e83c69c8f21249da0c0a Mon Sep 17 00:00:00 2001 From: Lukas Reining Date: Fri, 19 Jan 2024 18:02:31 +0100 Subject: [PATCH] feat: use Nest.js SDK in backend Signed-off-by: Lukas Reining --- package-lock.json | 11 +++ package.json | 1 + packages/app/src/app/app.module.ts | 88 ++++++------------- packages/app/src/app/constants.ts | 2 - .../src/app/fibonacci/fibonacci.service.ts | 17 ++-- .../src/app/transaction-context.middleware.ts | 19 ---- packages/app/src/app/types.ts | 13 --- .../fibonacci-service/src/app/app.module.ts | 53 ++++------- .../src/app/transaction-context.middleware.ts | 22 ----- packages/openfeature-extra/src/index.ts | 1 - .../async-local-storage.ts | 13 --- .../src/lib/transaction-context/index.ts | 1 - 12 files changed, 67 insertions(+), 174 deletions(-) delete mode 100644 packages/app/src/app/constants.ts delete mode 100644 packages/app/src/app/transaction-context.middleware.ts delete mode 100644 packages/app/src/app/types.ts delete mode 100644 packages/fibonacci-service/src/app/transaction-context.middleware.ts delete mode 100644 packages/openfeature-extra/src/lib/transaction-context/async-local-storage.ts delete mode 100644 packages/openfeature-extra/src/lib/transaction-context/index.ts diff --git a/package-lock.json b/package-lock.json index 22dcc551..1982e3c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@openfeature/flagd-provider": "^0.9.0", "@openfeature/flagd-web-provider": "^0.4.1", "@openfeature/go-feature-flag-provider": "^0.6.1", + "@openfeature/nestjs-sdk": "^0.0.4-experimental", "@openfeature/open-telemetry-hooks": "^0.3.0", "@openfeature/server-sdk": "^1.7.5", "@openfeature/web-sdk": "0.4.7", @@ -5592,6 +5593,16 @@ "node": ">=16" } }, + "node_modules/@openfeature/nestjs-sdk": { + "version": "0.0.4-experimental", + "resolved": "https://registry.npmjs.org/@openfeature/nestjs-sdk/-/nestjs-sdk-0.0.4-experimental.tgz", + "integrity": "sha512-V/HN2VQUBTps4JD5pGXgXF9CPP5H4kDxJYe66xkIxAl4fz+IkbzTcKsSxcrvnwrwBJ82BPEhSLu7cuwO5qiDFA==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@openfeature/server-sdk": ">=1.7.5", + "rxjs": "^6.0.0 || ^7.0.0" + } + }, "node_modules/@openfeature/open-telemetry-hooks": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@openfeature/open-telemetry-hooks/-/open-telemetry-hooks-0.3.0.tgz", diff --git a/package.json b/package.json index 6060ac2e..28f0508d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@openfeature/flagd-provider": "^0.9.0", "@openfeature/flagd-web-provider": "^0.4.1", "@openfeature/go-feature-flag-provider": "^0.6.1", + "@openfeature/nestjs-sdk": "^0.0.4-experimental", "@openfeature/open-telemetry-hooks": "^0.3.0", "@openfeature/server-sdk": "^1.7.5", "@openfeature/web-sdk": "0.4.7", diff --git a/packages/app/src/app/app.module.ts b/packages/app/src/app/app.module.ts index b52a60fa..ff2ac3cc 100644 --- a/packages/app/src/app/app.module.ts +++ b/packages/app/src/app/app.module.ts @@ -1,50 +1,26 @@ import {HttpModule} from '@nestjs/axios'; -import {MiddlewareConsumer, Module, NestModule, Scope} from '@nestjs/common'; -import {REQUEST} from '@nestjs/core'; +import {ExecutionContext, Module} from '@nestjs/common'; import {ServeStaticModule} from '@nestjs/serve-static'; -import {AsyncLocalStorageTransactionContext, LoggingHook, OpenFeatureLogger} from '@openfeature/extra'; -import { FlagMetadata, OpenFeature } from '@openfeature/server-sdk'; -import {MetricsHook, TracingHook as SpanEventBasedTracingHook} from '@openfeature/open-telemetry-hooks'; +import {LoggingHook, OpenFeatureLogger} from '@openfeature/extra'; +import {FlagMetadata} from '@openfeature/js-sdk'; +import {TracingHook as SpanEventBasedTracingHook, MetricsHook} from '@openfeature/open-telemetry-hooks'; import {ProviderService} from '@openfeature/provider'; import {Request} from 'express'; import {Agent} from 'http'; import {LoggerModule} from 'nestjs-pino'; import {join} from 'path'; -import {OPENFEATURE_CLIENT, REQUEST_DATA} from './constants'; import {FibonacciAsAServiceController} from './fibonacci-as-a-service.controller'; import {FibonacciService} from './fibonacci/fibonacci.service'; import {ProvidersController} from './providers.controller'; -import {TransactionContextMiddleware} from './transaction-context.middleware'; -import {RequestData} from './types'; import {UtilsController} from './utils.controller'; - -/** - * Set a global logger for OpenFeature. This is logger will available in hooks. - */ -OpenFeature.setLogger(new OpenFeatureLogger('OpenFeature')); +import {EvaluationContext, OpenFeatureModule} from "@openfeature/nestjs-sdk"; function attributeMapper(flagMetadata: FlagMetadata) { return { - ...('scope' in flagMetadata && { scope: flagMetadata.scope }), + ...('scope' in flagMetadata && {scope: flagMetadata.scope}), }; } -/** - * Adding hooks to at the global level will ensure they always run - * as part of a flag evaluation lifecycle. - */ -OpenFeature.addHooks( - new LoggingHook(), - new SpanEventBasedTracingHook({attributeMapper}), - new MetricsHook({attributeMapper})); - -/** - * The transaction context propagator is an experimental feature - * that allows evaluation context to be set anywhere in a request - * and have it automatically available during a flag evaluation. - */ -OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContext()); - @Module({ imports: [ LoggerModule.forRoot({ @@ -59,11 +35,11 @@ OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionCont transport: process.env['NODE' + '_ENV'] !== 'production' ? { - target: 'pino-pretty', - options: { - hideObject: true, - }, - } + target: 'pino-pretty', + options: { + hideObject: true, + }, + } : undefined, }, }), @@ -71,41 +47,35 @@ OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionCont rootPath: join(__dirname, '..', 'ui'), }), HttpModule.register({ - httpAgent: new Agent({ keepAlive: true }), + httpAgent: new Agent({keepAlive: true}), }), - ], - controllers: [FibonacciAsAServiceController, UtilsController, ProvidersController], - providers: [ - FibonacciService, - ProviderService, - { - provide: OPENFEATURE_CLIENT, - useFactory: () => { - const client = OpenFeature.getClient('app'); - return client; - }, - }, - { - provide: REQUEST_DATA, - useFactory: (req: Request): RequestData => { + OpenFeatureModule.forRoot({ + // Set a global logger for OpenFeature. This is logger will available in hooks. + logger: new OpenFeatureLogger('OpenFeature'), + //Adding hooks to at the global level will ensure they always run as part of a flag evaluation lifecycle. + hooks: [new LoggingHook(), new SpanEventBasedTracingHook({attributeMapper}), new MetricsHook({attributeMapper})], + // This context will be used for all flag evaluations in the callstack + contextFactory: async (context: ExecutionContext): Promise => { + const req = await context.switchToHttp().getRequest() const authHeaderValue = req.header('Authorization') || 'anonymous'; const userAgent = req.header('user-agent'); return { + ts: new Date().getTime(), ip: (req.headers['x-forwarded-for'] as string) || (req.socket.remoteAddress as string), email: authHeaderValue, method: req.method, path: req.path, - ...(userAgent && { userAgent }), + ...(userAgent && {userAgent}), targetingKey: authHeaderValue, }; }, - scope: Scope.REQUEST, - inject: [REQUEST], - }, + }) + ], + controllers: [FibonacciAsAServiceController, UtilsController, ProvidersController], + providers: [ + FibonacciService, + ProviderService, ], }) -export class AppModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer.apply(TransactionContextMiddleware).forRoutes(FibonacciAsAServiceController); - } +export class AppModule { } diff --git a/packages/app/src/app/constants.ts b/packages/app/src/app/constants.ts deleted file mode 100644 index a58c127d..00000000 --- a/packages/app/src/app/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const OPENFEATURE_CLIENT = Symbol.for('OPENFEATURE_CLIENT'); -export const REQUEST_DATA = Symbol.for('REQUEST_DATA'); diff --git a/packages/app/src/app/fibonacci/fibonacci.service.ts b/packages/app/src/app/fibonacci/fibonacci.service.ts index c69d51d6..0e0da165 100644 --- a/packages/app/src/app/fibonacci/fibonacci.service.ts +++ b/packages/app/src/app/fibonacci/fibonacci.service.ts @@ -1,15 +1,16 @@ -import { HttpService } from '@nestjs/axios'; -import { Inject, Injectable } from '@nestjs/common'; -import { fibonacci } from '@openfeature/fibonacci'; -import { Client } from '@openfeature/server-sdk'; -import { OPENFEATURE_CLIENT } from '../constants'; -import { lastValueFrom, map } from 'rxjs'; +import {HttpService} from '@nestjs/axios'; +import {Injectable} from '@nestjs/common'; +import {fibonacci} from '@openfeature/fibonacci'; +import {Client} from '@openfeature/js-sdk'; +import {lastValueFrom, map} from 'rxjs'; +import {FeatureClient} from "@openfeature/nestjs-sdk"; @Injectable() export class FibonacciService { private readonly FIB_SERVICE_URL = process.env.FIB_SERVICE_URL || 'http://localhost:30001'; - constructor(private readonly httpService: HttpService, @Inject(OPENFEATURE_CLIENT) private client: Client) {} + constructor(private readonly httpService: HttpService, @FeatureClient() private client: Client) { + } async calculateFibonacci(num: number): Promise<{ result: number }> { const useRemoteFibService = await this.client.getBooleanValue('use-remote-fib-service', false); @@ -18,7 +19,7 @@ export class FibonacciService { return lastValueFrom( this.httpService .get<{ result: number }>(`${this.FIB_SERVICE_URL}/calculate`, { - params: { num }, + params: {num}, auth: { username: process.env.FIB_SERVICE_USER || '', password: process.env.FIB_SERVICE_PASS || '', diff --git a/packages/app/src/app/transaction-context.middleware.ts b/packages/app/src/app/transaction-context.middleware.ts deleted file mode 100644 index 4627a482..00000000 --- a/packages/app/src/app/transaction-context.middleware.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable, NestMiddleware, Inject } from '@nestjs/common'; -import { OpenFeature } from '@openfeature/server-sdk'; -import { NextFunction, Request, Response } from 'express'; -import { REQUEST_DATA } from './constants'; -import { RequestData } from './types'; - -@Injectable() -export class TransactionContextMiddleware implements NestMiddleware { - constructor(@Inject(REQUEST_DATA) private requestData: RequestData) {} - - /** - * Adds our request data to the OpenFeature context via the configured TransactionContextManager - */ - use(_req: Request, _res: Response, next: NextFunction) { - OpenFeature.setTransactionContext({ ts: new Date().getTime(), ...this.requestData }, () => { - next(); - }); - } -} diff --git a/packages/app/src/app/types.ts b/packages/app/src/app/types.ts deleted file mode 100644 index a398694a..00000000 --- a/packages/app/src/app/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type RequestData = { - targetingKey: string; - ip: string; - method: string; - path: string; - userAgent?: string; - email?: string; -}; - -export type InstallTemplateData = { - os: string; - installationInstruction: string; -}; diff --git a/packages/fibonacci-service/src/app/app.module.ts b/packages/fibonacci-service/src/app/app.module.ts index 40b2434c..de70a338 100644 --- a/packages/fibonacci-service/src/app/app.module.ts +++ b/packages/fibonacci-service/src/app/app.module.ts @@ -1,40 +1,19 @@ -import {MiddlewareConsumer, Module, NestModule} from '@nestjs/common'; +import {Module} from '@nestjs/common'; import {AppController} from './app.controller'; import {LoggerModule} from 'nestjs-pino'; -import { FlagMetadata, OpenFeature } from '@openfeature/server-sdk'; -import {AsyncLocalStorageTransactionContext, LoggingHook, OpenFeatureLogger} from '@openfeature/extra'; +import {FlagMetadata} from '@openfeature/server-sdk'; +import {LoggingHook, OpenFeatureLogger} from '@openfeature/extra'; import {MetricsHook, TracingHook as SpanEventBasedTracingHook} from '@openfeature/open-telemetry-hooks'; -import {TransactionContextMiddleware} from './transaction-context.middleware'; import {ProviderService} from '@openfeature/provider'; import {ProvidersController} from './providers.controller'; - -/** - * Set a global logger for OpenFeature. This is logger will available in hooks. - */ -OpenFeature.setLogger(new OpenFeatureLogger('OpenFeature')); +import {OpenFeatureModule} from "@openfeature/nestjs-sdk"; function attributeMapper(flagMetadata: FlagMetadata) { return { - ...('scope' in flagMetadata && { scope: flagMetadata.scope }), + ...('scope' in flagMetadata && {scope: flagMetadata.scope}), }; } -/** - * Adding hooks to at the global level will ensure they always run - * as part of a flag evaluation lifecycle. - */ -OpenFeature.addHooks( - new LoggingHook(), - new SpanEventBasedTracingHook({attributeMapper}), - new MetricsHook({ attributeMapper })); - -/** - * The transaction context propagator is an experimental feature - * that allows evaluation context to be set anywhere in a request - * and have it automatically available during a flag evaluation. - */ -OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionContext()); - @Module({ imports: [ LoggerModule.forRoot({ @@ -49,20 +28,22 @@ OpenFeature.setTransactionContextPropagator(new AsyncLocalStorageTransactionCont transport: process.env['NODE' + '_ENV'] !== 'production' ? { - target: 'pino-pretty', - options: { - hideObject: true, - }, - } + target: 'pino-pretty', + options: { + hideObject: true, + }, + } : undefined, }, }), + OpenFeatureModule.forRoot({ + // Set a global logger for OpenFeature. This is logger will available in hooks. + logger: new OpenFeatureLogger('OpenFeature'), + //Adding hooks to at the global level will ensure they always run as part of a flag evaluation lifecycle. + hooks: [new LoggingHook(), new SpanEventBasedTracingHook({attributeMapper}), new MetricsHook({attributeMapper})], + }) ], controllers: [AppController, ProvidersController], providers: [ProviderService], }) -export class AppModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer.apply(TransactionContextMiddleware).forRoutes(AppController); - } -} +export class AppModule {} diff --git a/packages/fibonacci-service/src/app/transaction-context.middleware.ts b/packages/fibonacci-service/src/app/transaction-context.middleware.ts deleted file mode 100644 index 7aac467f..00000000 --- a/packages/fibonacci-service/src/app/transaction-context.middleware.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable, NestMiddleware } from '@nestjs/common'; -import { EvaluationContext, OpenFeature } from '@openfeature/server-sdk'; -import { NextFunction, Request, Response } from 'express'; -import { propagation, context } from '@opentelemetry/api'; - -@Injectable() -export class TransactionContextMiddleware implements NestMiddleware { - /** - * Adds our request data to the OpenFeature context via the configured TransactionContextManager - */ - use(_req: Request, _res: Response, next: NextFunction) { - const baggage = propagation.getBaggage(context.active()); - const currentBaggage = baggage?.getAllEntries(); - const evaluationContext: EvaluationContext = {}; - - currentBaggage?.forEach(([key, value]) => { - evaluationContext[key] = value.value; - }); - - OpenFeature.setTransactionContext(evaluationContext, next); - } -} diff --git a/packages/openfeature-extra/src/index.ts b/packages/openfeature-extra/src/index.ts index 915a21d3..61642b44 100644 --- a/packages/openfeature-extra/src/index.ts +++ b/packages/openfeature-extra/src/index.ts @@ -1,3 +1,2 @@ export * from './lib/hooks'; -export * from './lib/transaction-context'; export * from './lib/logger'; diff --git a/packages/openfeature-extra/src/lib/transaction-context/async-local-storage.ts b/packages/openfeature-extra/src/lib/transaction-context/async-local-storage.ts deleted file mode 100644 index 2cc465d4..00000000 --- a/packages/openfeature-extra/src/lib/transaction-context/async-local-storage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { EvaluationContext, TransactionContextPropagator } from '@openfeature/server-sdk'; -import { AsyncLocalStorage } from 'async_hooks'; - -export class AsyncLocalStorageTransactionContext implements TransactionContextPropagator { - private asyncLocalStorage = new AsyncLocalStorage(); - - getTransactionContext(): EvaluationContext { - return this.asyncLocalStorage.getStore() ?? {}; - } - setTransactionContext(context: EvaluationContext, callback: () => void): void { - this.asyncLocalStorage.run(context, callback); - } -} diff --git a/packages/openfeature-extra/src/lib/transaction-context/index.ts b/packages/openfeature-extra/src/lib/transaction-context/index.ts deleted file mode 100644 index 80f8a2e8..00000000 --- a/packages/openfeature-extra/src/lib/transaction-context/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './async-local-storage';