-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Implement CacheStrategy and CacheService
Relates to #3043
- Loading branch information
1 parent
8f22ef8
commit 489c9c0
Showing
6 changed files
with
207 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,13 @@ | ||
import { Module } from '@nestjs/common'; | ||
|
||
import { ConfigModule } from '../config/config.module'; | ||
|
||
import { CacheService } from './cache.service'; | ||
import { RequestContextCacheService } from './request-context-cache.service'; | ||
|
||
@Module({ | ||
providers: [RequestContextCacheService], | ||
exports: [RequestContextCacheService], | ||
imports: [ConfigModule], | ||
providers: [RequestContextCacheService, CacheService], | ||
exports: [RequestContextCacheService, CacheService], | ||
}) | ||
export class CacheModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { JsonCompatible } from '@vendure/common/lib/shared-types'; | ||
|
||
import { ConfigService } from '../config/config.service'; | ||
import { Logger } from '../config/index'; | ||
import { CacheStrategy, SetCacheKeyOptions } from '../config/system/cache-strategy'; | ||
|
||
/** | ||
* @description | ||
* The CacheService is used to cache data in order to optimize performance. | ||
* | ||
* Internally it makes use of the configured {@link CacheStrategy} to persist | ||
* the cache into a key-value store. | ||
* | ||
* @since 3.1.0 | ||
*/ | ||
@Injectable() | ||
export class CacheService { | ||
protected cacheStrategy: CacheStrategy; | ||
constructor(private configService: ConfigService) { | ||
this.cacheStrategy = this.configService.systemOptions.cacheStrategy; | ||
} | ||
|
||
/** | ||
* @description | ||
* Gets an item from the cache, or returns undefined if the key is not found, or the | ||
* item has expired. | ||
*/ | ||
async get<T extends JsonCompatible<T>>(key: string): Promise<T | undefined> { | ||
try { | ||
const result = await this.cacheStrategy.get(key); | ||
if (result) { | ||
Logger.debug(`CacheService hit for key [${key}]`); | ||
} | ||
return result as T; | ||
} catch (e: any) { | ||
Logger.error(`Could not get key [${key}] from CacheService`, undefined, e.stack); | ||
} | ||
} | ||
|
||
/** | ||
* @description | ||
* Sets a key-value pair in the cache. The value must be serializable, so cannot contain | ||
* things like functions, circular data structures, class instances etc. | ||
* | ||
* Optionally a "time to live" (ttl) can be specified, which means that the key will | ||
* be considered stale after that many milliseconds. | ||
*/ | ||
async set<T extends JsonCompatible<T>>( | ||
key: string, | ||
value: T, | ||
options?: SetCacheKeyOptions, | ||
): Promise<void> { | ||
try { | ||
await this.cacheStrategy.set(key, value, options); | ||
Logger.debug(`Set key [${key}] in CacheService`); | ||
} catch (e: any) { | ||
Logger.error(`Could not set key [${key}] in CacheService`, undefined, e.stack); | ||
} | ||
} | ||
|
||
/** | ||
* @description | ||
* Deletes an item from the cache. | ||
*/ | ||
async delete(key: string): Promise<void> { | ||
try { | ||
await this.cacheStrategy.delete(key); | ||
Logger.debug(`Deleted key [${key}] from CacheService`); | ||
} catch (e: any) { | ||
Logger.error(`Could not delete key [${key}] from CacheService`, undefined, e.stack); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { JsonCompatible } from '@vendure/common/lib/shared-types'; | ||
|
||
import { InjectableStrategy } from '../../common/types/injectable-strategy'; | ||
|
||
/** | ||
* @description | ||
* Options available when setting the value in the cache. | ||
*/ | ||
export interface SetCacheKeyOptions { | ||
/** | ||
* @description | ||
* The time-to-live for the cache key in milliseconds. This means | ||
* that after this time period, the key will be considered stale | ||
* and will no longer be returned from the cache. Omitting | ||
* this is equivalent to having an infinite ttl. | ||
*/ | ||
ttl?: number; | ||
} | ||
|
||
/** | ||
* @description | ||
* The CacheStrategy defines how the underlying shared cache mechanism is implemented. | ||
* | ||
* It is used by the {@link CacheService} to take care of storage and retrieval of items | ||
* from the cache. | ||
* | ||
* @since 3.1.0 | ||
*/ | ||
export interface CacheStrategy extends InjectableStrategy { | ||
/** | ||
* @description | ||
* Gets an item from the cache, or returns undefined if the key is not found, or the | ||
* item has expired. | ||
*/ | ||
get<T extends JsonCompatible<T>>(key: string): Promise<T | undefined>; | ||
|
||
/** | ||
* @description | ||
* Sets a key-value pair in the cache. The value must be serializable, so cannot contain | ||
* things like functions, circular data structures, class instances etc. | ||
* | ||
* Optionally a "time to live" (ttl) can be specified, which means that the key will | ||
* be considered stale after that many milliseconds. | ||
*/ | ||
set<T extends JsonCompatible<T>>(key: string, value: T, options?: SetCacheKeyOptions): Promise<void>; | ||
|
||
/** | ||
* @description | ||
* Deletes an item from the cache. | ||
*/ | ||
delete(key: string): Promise<void>; | ||
} |
63 changes: 63 additions & 0 deletions
63
packages/core/src/config/system/in-memory-cache-strategy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { JsonCompatible } from '@vendure/common/lib/shared-types'; | ||
|
||
import { CacheStrategy, SetCacheKeyOptions } from './cache-strategy'; | ||
|
||
export interface CacheItem<T> { | ||
value: JsonCompatible<T>; | ||
expires?: number; | ||
} | ||
|
||
/** | ||
* A {@link CacheStrategy} that stores the cache in memory using a simple | ||
* JavaScript Map. | ||
* | ||
* **Caution** do not use this in a multi-instance deployment because | ||
* cache invalidation will not propagate to other instances. | ||
* | ||
* @since 3.1.0 | ||
*/ | ||
export class InMemoryCacheStrategy implements CacheStrategy { | ||
protected cache = new Map<string, CacheItem<any>>(); | ||
protected cacheSize = 10_000; | ||
|
||
constructor(config?: { cacheSize?: number }) { | ||
if (config?.cacheSize) { | ||
this.cacheSize = config.cacheSize; | ||
} | ||
} | ||
|
||
async get<T extends JsonCompatible<T>>(key: string): Promise<T | undefined> { | ||
const hit = this.cache.get(key); | ||
if (hit) { | ||
const now = new Date().getTime(); | ||
if (!hit.expires || (hit.expires && now < hit.expires)) { | ||
return hit.value; | ||
} else { | ||
this.cache.delete(key); | ||
} | ||
} | ||
} | ||
|
||
async set<T extends JsonCompatible<T>>(key: string, value: T, options?: SetCacheKeyOptions) { | ||
if (this.cache.has(key)) { | ||
// delete key to put the item to the end of | ||
// the cache, marking it as new again | ||
this.cache.delete(key); | ||
} else if (this.cache.size === this.cacheSize) { | ||
// evict oldest | ||
this.cache.delete(this.first()); | ||
} | ||
this.cache.set(key, { | ||
value, | ||
expires: options?.ttl ? new Date().getTime() + options.ttl : undefined, | ||
}); | ||
} | ||
|
||
async delete(key: string) { | ||
this.cache.delete(key); | ||
} | ||
|
||
private first() { | ||
return this.cache.keys().next().value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters