Skip to content

Commit 8651fc8

Browse files
refactor execute with failover policy atomic
1 parent 83c3e11 commit 8651fc8

File tree

1 file changed

+89
-64
lines changed

1 file changed

+89
-64
lines changed

src/appConfigurationImpl.ts

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
isFeatureFlag,
1313
isSecretReference,
1414
GetSnapshotOptions,
15+
ListConfigurationSettingsForSnapshotOptions,
1516
GetSnapshotResponse,
1617
KnownSnapshotComposition
1718
} from "@azure/app-configuration";
@@ -482,71 +483,50 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
482483
*/
483484
async #loadConfigurationSettings(loadFeatureFlag: boolean = false): Promise<ConfigurationSetting[]> {
484485
const selectors = loadFeatureFlag ? this.#ffSelectors : this.#kvSelectors;
485-
const funcToExecute = async (client) => {
486-
// Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
487-
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
488-
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
489-
// deep copy selectors to avoid modification if current client fails
490-
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
491-
JSON.stringify(selectors)
492-
);
493486

494-
for (const selector of selectorsToUpdate) {
495-
if (selector.snapshotName === undefined) {
496-
const listOptions: ListConfigurationSettingsOptions = {
497-
keyFilter: selector.keyFilter,
498-
labelFilter: selector.labelFilter,
499-
tagsFilter: selector.tagFilters
500-
};
501-
const pageWatchers: SettingWatcher[] = [];
502-
const pageIterator = listConfigurationSettingsWithTrace(
503-
this.#requestTraceOptions,
504-
client,
505-
listOptions
506-
).byPage();
507-
508-
for await (const page of pageIterator) {
509-
pageWatchers.push({ etag: page.etag });
510-
for (const setting of page.items) {
511-
if (loadFeatureFlag === isFeatureFlag(setting)) {
512-
loadedSettings.set(setting.key, setting);
513-
}
514-
}
515-
}
516-
selector.pageWatchers = pageWatchers;
517-
} else { // snapshot selector
518-
const snapshot = await this.#getSnapshot(selector.snapshotName);
519-
if (snapshot === undefined) {
520-
throw new InvalidOperationError(`Could not find snapshot with name ${selector.snapshotName}.`);
521-
}
522-
if (snapshot.compositionType != KnownSnapshotComposition.Key) {
523-
throw new InvalidOperationError(`Composition type for the selected snapshot with name ${selector.snapshotName} must be 'key'.`);
524-
}
525-
const pageIterator = listConfigurationSettingsForSnapshotWithTrace(
526-
this.#requestTraceOptions,
527-
client,
528-
selector.snapshotName
529-
).byPage();
530-
531-
for await (const page of pageIterator) {
532-
for (const setting of page.items) {
533-
if (loadFeatureFlag === isFeatureFlag(setting)) {
534-
loadedSettings.set(setting.key, setting);
535-
}
536-
}
537-
}
487+
// Use a Map to deduplicate configuration settings by key. When multiple selectors return settings with the same key,
488+
// the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
489+
const loadedSettings: Map<string, ConfigurationSetting> = new Map<string, ConfigurationSetting>();
490+
// deep copy selectors to avoid modification if current client fails
491+
const selectorsToUpdate: PagedSettingsWatcher[] = JSON.parse(
492+
JSON.stringify(selectors)
493+
);
494+
495+
for (const selector of selectorsToUpdate) {
496+
let settings: ConfigurationSetting[] = [];
497+
if (selector.snapshotName === undefined) {
498+
const listOptions: ListConfigurationSettingsOptions = {
499+
keyFilter: selector.keyFilter,
500+
labelFilter: selector.labelFilter,
501+
tagsFilter: selector.tagFilters
502+
};
503+
const { items, pageWatchers } = await this.#listConfigurationSettings(listOptions);
504+
selector.pageWatchers = pageWatchers;
505+
settings = items;
506+
} else { // snapshot selector
507+
const snapshot = await this.#getSnapshot(selector.snapshotName);
508+
if (snapshot === undefined) {
509+
throw new InvalidOperationError(`Could not find snapshot with name ${selector.snapshotName}.`);
510+
}
511+
if (snapshot.compositionType != KnownSnapshotComposition.Key) {
512+
throw new InvalidOperationError(`Composition type for the selected snapshot with name ${selector.snapshotName} must be 'key'.`);
538513
}
514+
settings = await this.#listConfigurationSettingsForSnapshot(selector.snapshotName);
539515
}
540516

541-
if (loadFeatureFlag) {
542-
this.#ffSelectors = selectorsToUpdate;
543-
} else {
544-
this.#kvSelectors = selectorsToUpdate;
517+
for (const setting of settings) {
518+
if (loadFeatureFlag === isFeatureFlag(setting)) {
519+
loadedSettings.set(setting.key, setting);
520+
}
545521
}
546-
return Array.from(loadedSettings.values());
547-
};
522+
}
548523

549-
return await this.#executeWithFailoverPolicy(funcToExecute) as ConfigurationSetting[];
524+
if (loadFeatureFlag) {
525+
this.#ffSelectors = selectorsToUpdate;
526+
} else {
527+
this.#kvSelectors = selectorsToUpdate;
528+
}
529+
return Array.from(loadedSettings.values());
550530
}
551531

552532
/**
@@ -754,13 +734,13 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
754734
/**
755735
* Gets a configuration setting by key and label.If the setting is not found, return undefine instead of throwing an error.
756736
*/
757-
async #getConfigurationSetting(configurationSettingId: ConfigurationSettingId, customOptions?: GetConfigurationSettingOptions): Promise<GetConfigurationSettingResponse | undefined> {
737+
async #getConfigurationSetting(configurationSettingId: ConfigurationSettingId, getOptions?: GetConfigurationSettingOptions): Promise<GetConfigurationSettingResponse | undefined> {
758738
const funcToExecute = async (client) => {
759739
return getConfigurationSettingWithTrace(
760740
this.#requestTraceOptions,
761741
client,
762742
configurationSettingId,
763-
customOptions
743+
getOptions
764744
);
765745
};
766746

@@ -777,13 +757,36 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
777757
return response;
778758
}
779759

780-
async #getSnapshot(snapshotName: string, customOptions?: GetSnapshotOptions): Promise<GetSnapshotResponse | undefined> {
760+
async #listConfigurationSettings(listOptions: ListConfigurationSettingsOptions): Promise<{ items: ConfigurationSetting[]; pageWatchers: SettingWatcher[] }> {
761+
const funcToExecute = async (client) => {
762+
const pageWatchers: SettingWatcher[] = [];
763+
const pageIterator = listConfigurationSettingsWithTrace(
764+
this.#requestTraceOptions,
765+
client,
766+
listOptions
767+
).byPage();
768+
769+
const items: ConfigurationSetting[] = [];
770+
for await (const page of pageIterator) {
771+
pageWatchers.push({ etag: page.etag });
772+
for (const setting of page.items) {
773+
items.push(setting);
774+
}
775+
}
776+
777+
return { items, pageWatchers };
778+
};
779+
780+
return await this.#executeWithFailoverPolicy(funcToExecute);
781+
}
782+
783+
async #getSnapshot(snapshotName: string, getOptions?: GetSnapshotOptions): Promise<GetSnapshotResponse | undefined> {
781784
const funcToExecute = async (client) => {
782785
return getSnapshotWithTrace(
783786
this.#requestTraceOptions,
784787
client,
785788
snapshotName,
786-
customOptions
789+
getOptions
787790
);
788791
};
789792

@@ -800,6 +803,28 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
800803
return response;
801804
}
802805

806+
async #listConfigurationSettingsForSnapshot(snapshotName: string, listOptions?: ListConfigurationSettingsForSnapshotOptions): Promise<ConfigurationSetting[]> {
807+
const funcToExecute = async (client) => {
808+
const pageIterator = listConfigurationSettingsForSnapshotWithTrace(
809+
this.#requestTraceOptions,
810+
client,
811+
snapshotName,
812+
listOptions
813+
).byPage();
814+
815+
const items: ConfigurationSetting[] = [];
816+
for await (const page of pageIterator) {
817+
for (const setting of page.items) {
818+
items.push(setting);
819+
}
820+
}
821+
822+
return items;
823+
};
824+
825+
return await this.#executeWithFailoverPolicy(funcToExecute);
826+
}
827+
803828
// Only operations related to Azure App Configuration should be executed with failover policy.
804829
async #executeWithFailoverPolicy(funcToExecute: (client: AppConfigurationClient) => Promise<any>): Promise<any> {
805830
let clientWrappers = await this.#clientManager.getClients();
@@ -875,7 +900,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
875900
#setAIConfigurationTracing(setting: ConfigurationSetting<string>): void {
876901
if (this.#requestTracingEnabled && this.#aiConfigurationTracing !== undefined) {
877902
const contentType = parseContentType(setting.contentType);
878-
// content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\"""
903+
// content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\""
879904
if (isJsonContentType(contentType) &&
880905
!isFeatureFlagContentType(contentType) &&
881906
!isSecretReferenceContentType(contentType)) {

0 commit comments

Comments
 (0)