-
-
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 cache invalidation by tags
Relates to #3043
- Loading branch information
1 parent
0a60ee9
commit 382e314
Showing
12 changed files
with
435 additions
and
14 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 |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { CacheService, DefaultCachePlugin, mergeConfig } from '@vendure/core'; | ||
import { createTestEnvironment } from '@vendure/testing'; | ||
import path from 'path'; | ||
import { afterAll, beforeAll, describe, it } from 'vitest'; | ||
|
||
import { initialData } from '../../../e2e-common/e2e-initial-data'; | ||
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config'; | ||
import { TestingCacheTtlProvider } from '../src/cache/cache-ttl-provider'; | ||
|
||
import { | ||
deletesAKey, | ||
evictsTheOldestKeyWhenCacheIsFull, | ||
getReturnsUndefinedForNonExistentKey, | ||
invalidatesALargeNumberOfKeysByTag, | ||
invalidatesByMultipleTags, | ||
invalidatesBySingleTag, | ||
setsAKey, | ||
setsAKeyWithTtl, | ||
} from './fixtures/cache-service-shared-tests'; | ||
|
||
describe('CacheService with DefaultCachePlugin (sql)', () => { | ||
const ttlProvider = new TestingCacheTtlProvider(); | ||
|
||
let cacheService: CacheService; | ||
const { server, adminClient } = createTestEnvironment( | ||
mergeConfig(testConfig(), { | ||
plugins: [ | ||
DefaultCachePlugin.init({ | ||
cacheSize: 5, | ||
cacheTtlProvider: ttlProvider, | ||
}), | ||
], | ||
}), | ||
); | ||
|
||
beforeAll(async () => { | ||
await server.init({ | ||
initialData, | ||
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), | ||
customerCount: 1, | ||
}); | ||
await adminClient.asSuperAdmin(); | ||
cacheService = server.app.get(CacheService); | ||
}, TEST_SETUP_TIMEOUT_MS); | ||
|
||
afterAll(async () => { | ||
await server.destroy(); | ||
}); | ||
|
||
it('get returns undefined for non-existent key', () => | ||
getReturnsUndefinedForNonExistentKey(cacheService)); | ||
|
||
it('sets a key', () => setsAKey(cacheService)); | ||
|
||
it('deletes a key', () => deletesAKey(cacheService)); | ||
|
||
it('sets a key with ttl', () => setsAKeyWithTtl(cacheService, ttlProvider)); | ||
|
||
it('evicts the oldest key when cache is full', () => evictsTheOldestKeyWhenCacheIsFull(cacheService)); | ||
|
||
it('invalidates by single tag', () => invalidatesBySingleTag(cacheService)); | ||
|
||
it('invalidates by multiple tags', () => invalidatesByMultipleTags(cacheService)); | ||
|
||
it('invalidates a large number of keys by tag', () => invalidatesALargeNumberOfKeysByTag(cacheService)); | ||
}); |
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,67 @@ | ||
import { CacheService, mergeConfig } from '@vendure/core'; | ||
import { createTestEnvironment } from '@vendure/testing'; | ||
import path from 'path'; | ||
import { afterAll, beforeAll, describe, it } from 'vitest'; | ||
|
||
import { initialData } from '../../../e2e-common/e2e-initial-data'; | ||
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config'; | ||
import { TestingCacheTtlProvider } from '../src/cache/cache-ttl-provider'; | ||
import { InMemoryCacheStrategy } from '../src/config/system/in-memory-cache-strategy'; | ||
|
||
import { | ||
deletesAKey, | ||
evictsTheOldestKeyWhenCacheIsFull, | ||
getReturnsUndefinedForNonExistentKey, | ||
invalidatesALargeNumberOfKeysByTag, | ||
invalidatesByMultipleTags, | ||
invalidatesBySingleTag, | ||
setsAKey, | ||
setsAKeyWithTtl, | ||
} from './fixtures/cache-service-shared-tests'; | ||
|
||
describe('CacheService in-memory', () => { | ||
const ttlProvider = new TestingCacheTtlProvider(); | ||
|
||
let cacheService: CacheService; | ||
const { server, adminClient } = createTestEnvironment( | ||
mergeConfig(testConfig(), { | ||
systemOptions: { | ||
cacheStrategy: new InMemoryCacheStrategy({ | ||
cacheSize: 5, | ||
cacheTtlProvider: ttlProvider, | ||
}), | ||
}, | ||
}), | ||
); | ||
|
||
beforeAll(async () => { | ||
await server.init({ | ||
initialData, | ||
productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), | ||
customerCount: 1, | ||
}); | ||
await adminClient.asSuperAdmin(); | ||
cacheService = server.app.get(CacheService); | ||
}, TEST_SETUP_TIMEOUT_MS); | ||
|
||
afterAll(async () => { | ||
await server.destroy(); | ||
}); | ||
|
||
it('get returns undefined for non-existent key', () => | ||
getReturnsUndefinedForNonExistentKey(cacheService)); | ||
|
||
it('sets a key', () => setsAKey(cacheService)); | ||
|
||
it('deletes a key', () => deletesAKey(cacheService)); | ||
|
||
it('sets a key with ttl', () => setsAKeyWithTtl(cacheService, ttlProvider)); | ||
|
||
it('evicts the oldest key when cache is full', () => evictsTheOldestKeyWhenCacheIsFull(cacheService)); | ||
|
||
it('invalidates by single tag', () => invalidatesBySingleTag(cacheService)); | ||
|
||
it('invalidates by multiple tags', () => invalidatesByMultipleTags(cacheService)); | ||
|
||
it('invalidates a large number of keys by tag', () => invalidatesALargeNumberOfKeysByTag(cacheService)); | ||
}); |
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,89 @@ | ||
import { CacheService } from '@vendure/core'; | ||
import { expect } from 'vitest'; | ||
|
||
import { TestingCacheTtlProvider } from '../../src/cache/cache-ttl-provider'; | ||
|
||
export async function getReturnsUndefinedForNonExistentKey(cacheService: CacheService) { | ||
const result = await cacheService.get('non-existent-key'); | ||
expect(result).toBeUndefined(); | ||
} | ||
|
||
export async function setsAKey(cacheService: CacheService) { | ||
await cacheService.set('test-key', 'test-value'); | ||
const result = await cacheService.get('test-key'); | ||
expect(result).toBe('test-value'); | ||
} | ||
|
||
export async function deletesAKey(cacheService: CacheService) { | ||
await cacheService.set('test-key', 'test-value'); | ||
await cacheService.delete('test-key'); | ||
const result = await cacheService.get('test-key'); | ||
|
||
expect(result).toBeUndefined(); | ||
} | ||
|
||
export async function setsAKeyWithTtl(cacheService: CacheService, ttlProvider: TestingCacheTtlProvider) { | ||
ttlProvider.setTime(new Date().getTime()); | ||
await cacheService.set('test-key', 'test-value', { ttl: 1000 }); | ||
const result = await cacheService.get('test-key'); | ||
expect(result).toBe('test-value'); | ||
|
||
ttlProvider.incrementTime(2000); | ||
|
||
const result2 = await cacheService.get('test-key'); | ||
|
||
expect(result2).toBeUndefined(); | ||
} | ||
|
||
export async function evictsTheOldestKeyWhenCacheIsFull(cacheService: CacheService) { | ||
await cacheService.set('key1', 'value1'); | ||
await cacheService.set('key2', 'value2'); | ||
await cacheService.set('key3', 'value3'); | ||
await cacheService.set('key4', 'value4'); | ||
await cacheService.set('key5', 'value5'); | ||
|
||
const result1 = await cacheService.get('key1'); | ||
expect(result1).toBe('value1'); | ||
|
||
await cacheService.set('key6', 'value6'); | ||
|
||
const result2 = await cacheService.get('key1'); | ||
expect(result2).toBeUndefined(); | ||
} | ||
|
||
export async function invalidatesBySingleTag(cacheService: CacheService) { | ||
await cacheService.set('taggedKey1', 'value1', { tags: ['tag1'] }); | ||
await cacheService.set('taggedKey2', 'value2', { tags: ['tag2'] }); | ||
|
||
expect(await cacheService.get('taggedKey1')).toBe('value1'); | ||
expect(await cacheService.get('taggedKey2')).toBe('value2'); | ||
|
||
await cacheService.invalidateTags(['tag1']); | ||
|
||
expect(await cacheService.get('taggedKey1')).toBeUndefined(); | ||
expect(await cacheService.get('taggedKey2')).toBe('value2'); | ||
} | ||
|
||
export async function invalidatesByMultipleTags(cacheService: CacheService) { | ||
await cacheService.set('taggedKey1', 'value1', { tags: ['tag1'] }); | ||
await cacheService.set('taggedKey2', 'value2', { tags: ['tag2'] }); | ||
|
||
expect(await cacheService.get('taggedKey1')).toBe('value1'); | ||
expect(await cacheService.get('taggedKey2')).toBe('value2'); | ||
|
||
await cacheService.invalidateTags(['tag1', 'tag2']); | ||
|
||
expect(await cacheService.get('taggedKey1')).toBeUndefined(); | ||
expect(await cacheService.get('taggedKey2')).toBeUndefined(); | ||
} | ||
|
||
export async function invalidatesALargeNumberOfKeysByTag(cacheService: CacheService) { | ||
for (let i = 0; i < 100; i++) { | ||
await cacheService.set(`taggedKey${i}`, `value${i}`, { tags: ['tag'] }); | ||
} | ||
await cacheService.invalidateTags(['tag']); | ||
|
||
for (let i = 0; i < 100; i++) { | ||
expect(await cacheService.get(`taggedKey${i}`)).toBeUndefined(); | ||
} | ||
} |
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,51 @@ | ||
/** | ||
* @description | ||
* This interface is used to provide the current time in milliseconds. | ||
* The reason it is abstracted in this way is so that the cache | ||
* implementations can be more easily tested. | ||
* | ||
* In an actual application you would not need to change the default. | ||
*/ | ||
export interface CacheTtlProvider { | ||
/** | ||
* @description | ||
* Returns the current timestamp in milliseconds. | ||
*/ | ||
getTime(): number; | ||
} | ||
|
||
/** | ||
* @description | ||
* The default implementation of the {@link CacheTtlProvider} which | ||
* simply returns the current time. | ||
*/ | ||
export class DefaultCacheTtlProvider implements CacheTtlProvider { | ||
/** | ||
* @description | ||
* Returns the current timestamp in milliseconds. | ||
*/ | ||
getTime(): number { | ||
return new Date().getTime(); | ||
} | ||
} | ||
|
||
/** | ||
* @description | ||
* A testing implementation of the {@link CacheTtlProvider} which | ||
* allows the time to be set manually. | ||
*/ | ||
export class TestingCacheTtlProvider implements CacheTtlProvider { | ||
private time = 0; | ||
|
||
setTime(timestampInMs: number) { | ||
this.time = timestampInMs; | ||
} | ||
|
||
incrementTime(ms: number) { | ||
this.time += ms; | ||
} | ||
|
||
getTime(): number { | ||
return this.time; | ||
} | ||
} |
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 |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './request-context-cache.service'; | ||
export * from './cache.service'; |
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
Oops, something went wrong.