mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: Switch client feature toggles to segment read model (#6425)
This commit is contained in:
		
							parent
							
								
									62361847f5
								
							
						
					
					
						commit
						454f44dec5
					
				@ -1,10 +1,5 @@
 | 
			
		||||
import { ISegmentStore } from '../types/stores/segment-store';
 | 
			
		||||
import {
 | 
			
		||||
    IClientSegment,
 | 
			
		||||
    IConstraint,
 | 
			
		||||
    IFeatureStrategySegment,
 | 
			
		||||
    ISegment,
 | 
			
		||||
} from '../types/model';
 | 
			
		||||
import { IConstraint, IFeatureStrategySegment, ISegment } from '../types/model';
 | 
			
		||||
import { Logger, LogProvider } from '../logger';
 | 
			
		||||
import EventEmitter from 'events';
 | 
			
		||||
import NotFoundError from '../error/notfound-error';
 | 
			
		||||
@ -284,30 +279,6 @@ export default class SegmentStore implements ISegmentStore {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getActive(): Promise<ISegment[]> {
 | 
			
		||||
        const rows: ISegmentRow[] = await this.db
 | 
			
		||||
            .distinct(this.prefixColumns())
 | 
			
		||||
            .from(T.segments)
 | 
			
		||||
            .orderBy('name', 'asc')
 | 
			
		||||
            .join(
 | 
			
		||||
                T.featureStrategySegment,
 | 
			
		||||
                `${T.featureStrategySegment}.segment_id`,
 | 
			
		||||
                `${T.segments}.id`,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        return rows.map(this.mapRow);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getActiveForClient(): Promise<IClientSegment[]> {
 | 
			
		||||
        const fullSegments = await this.getActive();
 | 
			
		||||
 | 
			
		||||
        return fullSegments.map((segments) => ({
 | 
			
		||||
            id: segments.id,
 | 
			
		||||
            name: segments.name,
 | 
			
		||||
            constraints: segments.constraints,
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getByStrategy(strategyId: string): Promise<ISegment[]> {
 | 
			
		||||
        const rows = await this.db
 | 
			
		||||
            .select(this.prefixColumns())
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import {
 | 
			
		||||
    IFeatureToggleClientStore,
 | 
			
		||||
    IFeatureToggleQuery,
 | 
			
		||||
    ISegmentReadModel,
 | 
			
		||||
    IUnleashConfig,
 | 
			
		||||
    IUnleashStores,
 | 
			
		||||
} from '../../types';
 | 
			
		||||
@ -14,16 +15,24 @@ export class ClientFeatureToggleService {
 | 
			
		||||
 | 
			
		||||
    private clientFeatureToggleStore: IFeatureToggleClientStore;
 | 
			
		||||
 | 
			
		||||
    private segmentReadModel: ISegmentReadModel;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        {
 | 
			
		||||
            clientFeatureToggleStore,
 | 
			
		||||
        }: Pick<IUnleashStores, 'clientFeatureToggleStore'>,
 | 
			
		||||
        segmentReadModel: ISegmentReadModel,
 | 
			
		||||
        { getLogger }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.logger = getLogger('services/client-feature-toggle-service.ts');
 | 
			
		||||
        this.segmentReadModel = segmentReadModel;
 | 
			
		||||
        this.clientFeatureToggleStore = clientFeatureToggleStore;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getActiveSegmentsForClient() {
 | 
			
		||||
        return this.segmentReadModel.getActiveForClient();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getClientFeatures(
 | 
			
		||||
        query?: IFeatureToggleQuery,
 | 
			
		||||
    ): Promise<FeatureConfigurationClient[]> {
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,6 @@ import {
 | 
			
		||||
    clientFeaturesSchema,
 | 
			
		||||
    ClientFeaturesSchema,
 | 
			
		||||
} from '../../openapi/spec/client-features-schema';
 | 
			
		||||
import { ISegmentService } from '../../segments/segment-service-interface';
 | 
			
		||||
import ConfigurationRevisionService from '../feature-toggle/configuration-revision-service';
 | 
			
		||||
import { ClientFeatureToggleService } from './client-feature-toggle-service';
 | 
			
		||||
 | 
			
		||||
@ -53,8 +52,6 @@ export default class FeatureController extends Controller {
 | 
			
		||||
 | 
			
		||||
    private clientFeatureToggleService: ClientFeatureToggleService;
 | 
			
		||||
 | 
			
		||||
    private segmentService: ISegmentService;
 | 
			
		||||
 | 
			
		||||
    private clientSpecService: ClientSpecService;
 | 
			
		||||
 | 
			
		||||
    private openApiService: OpenApiService;
 | 
			
		||||
@ -73,7 +70,6 @@ export default class FeatureController extends Controller {
 | 
			
		||||
    constructor(
 | 
			
		||||
        {
 | 
			
		||||
            clientFeatureToggleService,
 | 
			
		||||
            segmentService,
 | 
			
		||||
            clientSpecService,
 | 
			
		||||
            openApiService,
 | 
			
		||||
            configurationRevisionService,
 | 
			
		||||
@ -81,7 +77,6 @@ export default class FeatureController extends Controller {
 | 
			
		||||
        }: Pick<
 | 
			
		||||
            IUnleashServices,
 | 
			
		||||
            | 'clientFeatureToggleService'
 | 
			
		||||
            | 'segmentService'
 | 
			
		||||
            | 'clientSpecService'
 | 
			
		||||
            | 'openApiService'
 | 
			
		||||
            | 'configurationRevisionService'
 | 
			
		||||
@ -92,7 +87,6 @@ export default class FeatureController extends Controller {
 | 
			
		||||
        super(config);
 | 
			
		||||
        const { clientFeatureCaching } = config;
 | 
			
		||||
        this.clientFeatureToggleService = clientFeatureToggleService;
 | 
			
		||||
        this.segmentService = segmentService;
 | 
			
		||||
        this.clientSpecService = clientSpecService;
 | 
			
		||||
        this.openApiService = openApiService;
 | 
			
		||||
        this.configurationRevisionService = configurationRevisionService;
 | 
			
		||||
@ -162,7 +156,7 @@ export default class FeatureController extends Controller {
 | 
			
		||||
    ): Promise<[FeatureConfigurationClient[], IClientSegment[]]> {
 | 
			
		||||
        return Promise.all([
 | 
			
		||||
            this.clientFeatureToggleService.getClientFeatures(query),
 | 
			
		||||
            this.segmentService.getActiveForClient(),
 | 
			
		||||
            this.clientFeatureToggleService.getActiveSegmentsForClient(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ import { Db } from '../../db/db';
 | 
			
		||||
import { IUnleashConfig } from '../../types';
 | 
			
		||||
import FakeClientFeatureToggleStore from './fakes/fake-client-feature-toggle-store';
 | 
			
		||||
import { ClientFeatureToggleService } from './client-feature-toggle-service';
 | 
			
		||||
import { SegmentReadModel } from '../segment/segment-read-model';
 | 
			
		||||
import { FakeSegmentReadModel } from '../segment/fake-segment-read-model';
 | 
			
		||||
 | 
			
		||||
export const createClientFeatureToggleService = (
 | 
			
		||||
    db: Db,
 | 
			
		||||
@ -17,10 +19,13 @@ export const createClientFeatureToggleService = (
 | 
			
		||||
        flagResolver,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const segmentReadModel = new SegmentReadModel(db);
 | 
			
		||||
 | 
			
		||||
    const clientFeatureToggleService = new ClientFeatureToggleService(
 | 
			
		||||
        {
 | 
			
		||||
            clientFeatureToggleStore: featureToggleClientStore,
 | 
			
		||||
        },
 | 
			
		||||
        segmentReadModel,
 | 
			
		||||
        { getLogger, flagResolver },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -34,10 +39,13 @@ export const createFakeClientFeatureToggleService = (
 | 
			
		||||
 | 
			
		||||
    const fakeClientFeatureToggleStore = new FakeClientFeatureToggleStore();
 | 
			
		||||
 | 
			
		||||
    const fakeSegmentReadModel = new FakeSegmentReadModel();
 | 
			
		||||
 | 
			
		||||
    const clientFeatureToggleService = new ClientFeatureToggleService(
 | 
			
		||||
        {
 | 
			
		||||
            clientFeatureToggleStore: fakeClientFeatureToggleStore,
 | 
			
		||||
        },
 | 
			
		||||
        fakeSegmentReadModel,
 | 
			
		||||
        { getLogger, flagResolver },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -70,15 +70,16 @@ test('should get empty getFeatures via client', () => {
 | 
			
		||||
 | 
			
		||||
test('if caching is enabled should memoize', async () => {
 | 
			
		||||
    const getClientFeatures = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActive = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActiveForClient = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActiveSegmentsForClient = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const respondWithValidation = jest.fn().mockReturnValue({});
 | 
			
		||||
    const validPath = jest.fn().mockReturnValue(jest.fn());
 | 
			
		||||
    const clientSpecService = new ClientSpecService({ getLogger });
 | 
			
		||||
    const openApiService = { respondWithValidation, validPath };
 | 
			
		||||
    const clientFeatureToggleService = { getClientFeatures };
 | 
			
		||||
    const clientFeatureToggleService = {
 | 
			
		||||
        getClientFeatures,
 | 
			
		||||
        getActiveSegmentsForClient,
 | 
			
		||||
    };
 | 
			
		||||
    const featureToggleService = { getClientFeatures };
 | 
			
		||||
    const segmentService = { getActive, getActiveForClient };
 | 
			
		||||
    const configurationRevisionService = { getMaxRevisionId: () => 1 };
 | 
			
		||||
 | 
			
		||||
    const controller = new FeatureController(
 | 
			
		||||
@ -91,8 +92,6 @@ test('if caching is enabled should memoize', async () => {
 | 
			
		||||
            // @ts-expect-error due to partial implementation
 | 
			
		||||
            featureToggleService,
 | 
			
		||||
            // @ts-expect-error due to partial implementation
 | 
			
		||||
            segmentService,
 | 
			
		||||
            // @ts-expect-error due to partial implementation
 | 
			
		||||
            configurationRevisionService,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
@ -112,13 +111,14 @@ test('if caching is enabled should memoize', async () => {
 | 
			
		||||
 | 
			
		||||
test('if caching is not enabled all calls goes to service', async () => {
 | 
			
		||||
    const getClientFeatures = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActive = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActiveForClient = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const getActiveSegmentsForClient = jest.fn().mockReturnValue([]);
 | 
			
		||||
    const respondWithValidation = jest.fn().mockReturnValue({});
 | 
			
		||||
    const validPath = jest.fn().mockReturnValue(jest.fn());
 | 
			
		||||
    const clientSpecService = new ClientSpecService({ getLogger });
 | 
			
		||||
    const clientFeatureToggleService = { getClientFeatures };
 | 
			
		||||
    const segmentService = { getActive, getActiveForClient };
 | 
			
		||||
    const clientFeatureToggleService = {
 | 
			
		||||
        getClientFeatures,
 | 
			
		||||
        getActiveSegmentsForClient,
 | 
			
		||||
    };
 | 
			
		||||
    const featureToggleService = { getClientFeatures };
 | 
			
		||||
    const openApiService = { respondWithValidation, validPath };
 | 
			
		||||
    const configurationRevisionService = { getMaxRevisionId: () => 1 };
 | 
			
		||||
@ -133,8 +133,6 @@ test('if caching is not enabled all calls goes to service', async () => {
 | 
			
		||||
            // @ts-expect-error due to partial implementation
 | 
			
		||||
            featureToggleService,
 | 
			
		||||
            // @ts-expect-error due to partial implementation
 | 
			
		||||
            segmentService,
 | 
			
		||||
            // @ts-expect-error due to partial implementation
 | 
			
		||||
            configurationRevisionService,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { ChangeRequestStrategy } from '../features/change-request-segment-usage-service/change-request-segment-usage-read-model';
 | 
			
		||||
import { UpsertSegmentSchema } from '../openapi';
 | 
			
		||||
import { IClientSegment, IFeatureStrategy, ISegment, IUser } from '../types';
 | 
			
		||||
import { IFeatureStrategy, ISegment, IUser } from '../types';
 | 
			
		||||
 | 
			
		||||
export type StrategiesUsingSegment = {
 | 
			
		||||
    strategies: IFeatureStrategy[];
 | 
			
		||||
@ -33,8 +33,6 @@ export interface ISegmentService {
 | 
			
		||||
 | 
			
		||||
    validateName(name: string): Promise<void>;
 | 
			
		||||
 | 
			
		||||
    getActiveForClient(): Promise<IClientSegment[]>;
 | 
			
		||||
 | 
			
		||||
    getAll(): Promise<ISegment[]>;
 | 
			
		||||
 | 
			
		||||
    create(
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import { IUnleashConfig } from '../types/option';
 | 
			
		||||
import {
 | 
			
		||||
    IClientSegment,
 | 
			
		||||
    IFlagResolver,
 | 
			
		||||
    IUnleashStores,
 | 
			
		||||
    SKIP_CHANGE_REQUEST,
 | 
			
		||||
@ -79,10 +78,6 @@ export class SegmentService implements ISegmentService {
 | 
			
		||||
        return this.segmentStore.getAll(this.config.isEnterprise);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getActiveForClient(): Promise<IClientSegment[]> {
 | 
			
		||||
        return this.segmentStore.getActiveForClient();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getByStrategy(strategyId: string): Promise<ISegment[]> {
 | 
			
		||||
        return this.segmentStore.getByStrategy(strategyId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,10 @@
 | 
			
		||||
import { IClientSegment, IFeatureStrategySegment, ISegment } from '../model';
 | 
			
		||||
import { IFeatureStrategySegment, ISegment } from '../model';
 | 
			
		||||
import { Store } from './store';
 | 
			
		||||
import User from '../user';
 | 
			
		||||
 | 
			
		||||
export interface ISegmentStore extends Store<ISegment, number> {
 | 
			
		||||
    getAll(includeChangeRequestUsageData?: boolean): Promise<ISegment[]>;
 | 
			
		||||
 | 
			
		||||
    getActive(): Promise<ISegment[]>;
 | 
			
		||||
 | 
			
		||||
    getActiveForClient(): Promise<IClientSegment[]>;
 | 
			
		||||
 | 
			
		||||
    getByStrategy(strategyId: string): Promise<ISegment[]>;
 | 
			
		||||
 | 
			
		||||
    create(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								src/test/fixtures/fake-segment-store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/test/fixtures/fake-segment-store.ts
									
									
									
									
										vendored
									
									
								
							@ -1,9 +1,5 @@
 | 
			
		||||
import { ISegmentStore } from '../../lib/types/stores/segment-store';
 | 
			
		||||
import {
 | 
			
		||||
    IClientSegment,
 | 
			
		||||
    IFeatureStrategySegment,
 | 
			
		||||
    ISegment,
 | 
			
		||||
} from '../../lib/types/model';
 | 
			
		||||
import { IFeatureStrategySegment, ISegment } from '../../lib/types/model';
 | 
			
		||||
 | 
			
		||||
export default class FakeSegmentStore implements ISegmentStore {
 | 
			
		||||
    count(): Promise<number> {
 | 
			
		||||
@ -34,14 +30,6 @@ export default class FakeSegmentStore implements ISegmentStore {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getActive(): Promise<ISegment[]> {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getActiveForClient(): Promise<IClientSegment[]> {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getByStrategy(): Promise<ISegment[]> {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user