mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-13 11:17:26 +02:00
## About the changes This PR introduces environment-specific etags. This way clients will not react by updating features when there are changes in environments the SDK doesn't care about. ## Details There's a bit of scouting work (please don't make me split this 🙏) and other details are in comments, but the most relevant for the lazy ones: - Important **decision** on how we detect changes, unifying polling and delta: https://github.com/Unleash/unleash/pull/10512#discussion_r2285677129 - **Decision** on how we update revision id per environment: https://github.com/Unleash/unleash/pull/10512#discussion_r2291888401 - and how we do initial fetch on the read path: https://github.com/Unleash/unleash/pull/10512#discussion_r2291884777 - The singleton pattern that gave me **nightmares**: https://github.com/Unleash/unleash/pull/10512#discussion_r2291848934 - **Do we still have ALL_ENVS tokens?** https://github.com/Unleash/unleash/pull/10512#discussion_r2291913249 ## Feature flag To control the rollout introduced `etagByEnv` feature: [0da567d
](0da567dd9b
)
117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import type { Logger } from '../../logger.js';
|
|
import type {
|
|
IEventStore,
|
|
IFlagResolver,
|
|
IUnleashConfig,
|
|
IUnleashStores,
|
|
} from '../../types/index.js';
|
|
import EventEmitter from 'events';
|
|
|
|
export const UPDATE_REVISION = 'UPDATE_REVISION';
|
|
|
|
export default class ConfigurationRevisionService extends EventEmitter {
|
|
private static instance: ConfigurationRevisionService | undefined;
|
|
|
|
private logger: Logger;
|
|
|
|
private eventStore: IEventStore;
|
|
|
|
private revisionId: number;
|
|
|
|
private maxRevisionId: Map<string, number> = new Map();
|
|
|
|
private flagResolver: IFlagResolver;
|
|
|
|
private constructor(
|
|
{ eventStore }: Pick<IUnleashStores, 'eventStore'>,
|
|
{
|
|
getLogger,
|
|
flagResolver,
|
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
|
) {
|
|
super();
|
|
this.logger = getLogger('configuration-revision-service.ts');
|
|
this.eventStore = eventStore;
|
|
this.flagResolver = flagResolver;
|
|
this.revisionId = 0;
|
|
}
|
|
|
|
static getInstance(
|
|
{ eventStore }: Pick<IUnleashStores, 'eventStore'>,
|
|
{
|
|
getLogger,
|
|
flagResolver,
|
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
|
|
) {
|
|
if (!ConfigurationRevisionService.instance) {
|
|
ConfigurationRevisionService.instance =
|
|
new ConfigurationRevisionService(
|
|
{ eventStore },
|
|
{ getLogger, flagResolver },
|
|
);
|
|
}
|
|
return ConfigurationRevisionService.instance;
|
|
}
|
|
|
|
async getMaxRevisionId(environment?: string): Promise<number> {
|
|
if (environment && !this.maxRevisionId[environment]) {
|
|
await this.updateMaxEnvironmentRevisionId(environment);
|
|
}
|
|
if (
|
|
environment &&
|
|
this.maxRevisionId[environment] &&
|
|
this.maxRevisionId[environment] > 0
|
|
) {
|
|
return this.maxRevisionId[environment];
|
|
}
|
|
if (this.revisionId > 0) {
|
|
return this.revisionId;
|
|
} else {
|
|
return this.updateMaxRevisionId();
|
|
}
|
|
}
|
|
|
|
async updateMaxEnvironmentRevisionId(environment: string): Promise<number> {
|
|
const envRevisionId = await this.eventStore.getMaxRevisionId(
|
|
this.maxRevisionId[environment],
|
|
environment,
|
|
);
|
|
if (this.maxRevisionId[environment] ?? 0 < envRevisionId) {
|
|
this.maxRevisionId[environment] = envRevisionId;
|
|
}
|
|
|
|
return this.maxRevisionId[environment];
|
|
}
|
|
|
|
async updateMaxRevisionId(emit: boolean = true): Promise<number> {
|
|
if (this.flagResolver.isEnabled('disableUpdateMaxRevisionId')) {
|
|
return 0;
|
|
}
|
|
|
|
const revisionId = await this.eventStore.getMaxRevisionId(
|
|
this.revisionId,
|
|
);
|
|
if (this.revisionId !== revisionId) {
|
|
this.logger.debug(
|
|
`Updating feature configuration with new revision Id ${revisionId} and all envs: ${Object.keys(this.maxRevisionId).join(', ')}`,
|
|
);
|
|
await Promise.allSettled(
|
|
Object.keys(this.maxRevisionId).map((environment) =>
|
|
this.updateMaxEnvironmentRevisionId(environment),
|
|
),
|
|
);
|
|
this.revisionId = revisionId;
|
|
if (emit) {
|
|
this.emit(UPDATE_REVISION, revisionId);
|
|
}
|
|
}
|
|
|
|
return this.revisionId;
|
|
}
|
|
|
|
destroy(): void {
|
|
ConfigurationRevisionService.instance?.removeAllListeners();
|
|
ConfigurationRevisionService.instance = undefined;
|
|
}
|
|
}
|