Skip to content

Commit

Permalink
fix(core): Fix transaction-related issues with in-memory caching
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Sep 29, 2021
1 parent f178276 commit d35306f
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 26 deletions.
28 changes: 16 additions & 12 deletions packages/core/src/common/self-refreshing-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Logger } from '../config/logger/vendure-logger';
* @description
* A cache which automatically refreshes itself if the value is found to be stale.
*/
export interface SelfRefreshingCache<V> {
export interface SelfRefreshingCache<V, RefreshArgs extends any[] = []> {
/**
* @description
* The current value of the cache. If the value is stale, the data will be refreshed and then
Expand All @@ -28,13 +28,16 @@ export interface SelfRefreshingCache<V> {
* Force a refresh of the value, e.g. when it is known that the value has changed such as after
* an update operation to the source data in the database.
*/
refresh(): Promise<V>;
refresh(...args: RefreshArgs): Promise<V>;
}

export interface SelfRefreshingCacheConfig<V> {
export interface SelfRefreshingCacheConfig<V, RefreshArgs extends any[]> {
name: string;
ttl: number;
refreshFn: () => Promise<V>;
refresh: {
fn: (...args: RefreshArgs) => Promise<V>;
defaultArgs: RefreshArgs;
};
}

/**
Expand All @@ -47,18 +50,19 @@ export interface SelfRefreshingCacheConfig<V> {
* value has expired, it will still be returned and the `refreshFn` will be triggered to update the value in the
* background.
*/
export async function createSelfRefreshingCache<V>(
config: SelfRefreshingCacheConfig<V>,
): Promise<SelfRefreshingCache<V>> {
const { ttl, name, refreshFn } = config;
const initialValue = await refreshFn();
export async function createSelfRefreshingCache<V, RefreshArgs extends any[]>(
config: SelfRefreshingCacheConfig<V, RefreshArgs>,
): Promise<SelfRefreshingCache<V, RefreshArgs>> {
const { ttl, name, refresh } = config;
const initialValue = await refresh.fn(...refresh.defaultArgs);
let value = initialValue;
let expires = new Date().getTime() + ttl;
const memoCache = new Map<string, any>();
const hashArgs = (...args: any[]) => JSON.stringify([args, expires]);
const refreshValue = (): Promise<V> => {
const refreshValue = (...args: RefreshArgs): Promise<V> => {
Logger.debug(`Refreshing the SelfRefreshingCache "${name}"`);
return refreshFn()
return refresh
.fn(...args)
.then(newValue => {
value = newValue;
expires = new Date().getTime() + ttl;
Expand All @@ -77,7 +81,7 @@ export async function createSelfRefreshingCache<V>(
const getValue = async (): Promise<V> => {
const now = new Date().getTime();
if (expires < now) {
return refreshValue();
return refreshValue(...refresh.defaultArgs);
}
return value;
};
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/service/services/channel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { GlobalSettingsService } from './global-settings.service';

@Injectable()
export class ChannelService {
private allChannels: SelfRefreshingCache<Channel[]>;
private allChannels: SelfRefreshingCache<Channel[], [RequestContext]>;

constructor(
private connection: TransactionalConnection,
Expand All @@ -51,7 +51,7 @@ export class ChannelService {
this.allChannels = await createSelfRefreshingCache({
name: 'ChannelService.allChannels',
ttl: this.configService.entityOptions.channelCacheTtl,
refreshFn: () => this.findAll(RequestContext.empty()),
refresh: { fn: ctx => this.findAll(ctx), defaultArgs: [RequestContext.empty()] },
});
}

Expand Down Expand Up @@ -174,7 +174,7 @@ export class ChannelService {
}
const newChannel = await this.connection.getRepository(ctx, Channel).save(channel);
await this.customFieldRelationService.updateRelations(ctx, Channel, input, newChannel);
await this.allChannels.refresh();
await this.allChannels.refresh(ctx);
return channel;
}

Expand Down Expand Up @@ -207,7 +207,7 @@ export class ChannelService {
}
await this.connection.getRepository(ctx, Channel).save(updatedChannel, { reload: false });
await this.customFieldRelationService.updateRelations(ctx, Channel, input, updatedChannel);
await this.allChannels.refresh();
await this.allChannels.refresh(ctx);
return assertFound(this.findOne(ctx, channel.id));
}

Expand Down
23 changes: 13 additions & 10 deletions packages/core/src/service/services/zone.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ export class ZoneService {
/**
* We cache all Zones to avoid hitting the DB many times per request.
*/
private zones: SelfRefreshingCache<Zone[]>;
private zones: SelfRefreshingCache<Zone[], [RequestContext]>;
constructor(private connection: TransactionalConnection, private configService: ConfigService) {}

async initZones() {
this.zones = await createSelfRefreshingCache({
name: 'ZoneService.zones',
ttl: this.configService.entityOptions.zoneCacheTtl,
refreshFn: () =>
this.connection.getRepository(Zone).find({
relations: ['members'],
}),
refresh: {
fn: ctx =>
this.connection.getRepository(ctx, Zone).find({
relations: ['members'],
}),
defaultArgs: [RequestContext.empty()],
},
});
}

Expand Down Expand Up @@ -69,15 +72,15 @@ export class ZoneService {
zone.members = await this.getCountriesFromIds(ctx, input.memberIds);
}
const newZone = await this.connection.getRepository(ctx, Zone).save(zone);
await this.zones.refresh();
await this.zones.refresh(ctx);
return assertFound(this.findOne(ctx, newZone.id));
}

async update(ctx: RequestContext, input: UpdateZoneInput): Promise<Zone> {
const zone = await this.connection.getEntityOrThrow(ctx, Zone, input.id);
const updatedZone = patchEntity(zone, input);
await this.connection.getRepository(ctx, Zone).save(updatedZone, { reload: false });
await this.zones.refresh();
await this.zones.refresh(ctx);
return assertFound(this.findOne(ctx, zone.id));
}

Expand Down Expand Up @@ -115,7 +118,7 @@ export class ZoneService {
};
} else {
await this.connection.getRepository(ctx, Zone).remove(zone);
await this.zones.refresh();
await this.zones.refresh(ctx);
return {
result: DeletionResult.DELETED,
message: '',
Expand All @@ -131,7 +134,7 @@ export class ZoneService {
const members = unique(zone.members.concat(countries), 'id');
zone.members = members;
await this.connection.getRepository(ctx, Zone).save(zone, { reload: false });
await this.zones.refresh();
await this.zones.refresh(ctx);
return assertFound(this.findOne(ctx, zone.id));
}

Expand All @@ -144,7 +147,7 @@ export class ZoneService {
});
zone.members = zone.members.filter(country => !input.memberIds.includes(country.id));
await this.connection.getRepository(ctx, Zone).save(zone, { reload: false });
await this.zones.refresh();
await this.zones.refresh(ctx);
return assertFound(this.findOne(ctx, zone.id));
}

Expand Down

0 comments on commit d35306f

Please sign in to comment.