1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-18 13:48:58 +02:00

refactor: Switch client feature toggles to segment read model (#6425)

This commit is contained in:
Mateusz Kwasniewski 2024-03-05 11:15:22 +01:00 committed by GitHub
parent 62361847f5
commit 454f44dec5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 32 additions and 75 deletions

View File

@ -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())

View File

@ -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[]> {

View File

@ -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(),
]);
}

View File

@ -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 },
);

View File

@ -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,
},
{

View File

@ -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(

View File

@ -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);
}

View File

@ -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(

View File

@ -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 [];
}