Skip to content

Commit

Permalink
Cache improvement (#7)
Browse files Browse the repository at this point in the history
* Adjust cache to allow filtering of parameters for cache key calculation

* Fix dependency issues
  • Loading branch information
tichon29 committed Aug 19, 2021
1 parent 5e7b233 commit 3f1f8bb
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/content/api/module/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ circuit.fn(myFirstFunction).execute(myObject)
| `ttl` | The amount of time during which a cached result is considered valid. | `6000` |
| `cacheClearInterval` | The amount of time before the cache cleans itself up. | `900000` |
| `getInformationFromCache` | Specifies if the async response contains information if the data is retrieved from Cache (in this case, the information is available in _mollitiaIsFromCache property) | false |
| `adjustCacheParams` | A filtering callback, to modify the parameters used for Cache Key. | `none` |


## Events
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { circuits, Circuit, CircuitFactory, CircuitOptions, NoFuncError } from './circuit';
import { circuits, Circuit, CircuitFactory, CircuitFunction, CircuitOptions, NoFuncError } from './circuit';
import { use, Addon } from './addon';
import { modules, Module, ModuleOptions } from './module/index';
import { Timeout, TimeoutError, TimeoutOptions } from './module/timeout';
Expand All @@ -17,6 +17,7 @@ export {
circuits,
Circuit,
CircuitFactory,
CircuitFunction,
CircuitOptions,
NoFuncError,
// Module
Expand Down
25 changes: 22 additions & 3 deletions src/module/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Module, ModuleOptions } from '.';
import { Circuit, CircuitFunction } from '../circuit';
import { MapCache } from '../helpers/map-cache';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AdjustCacheParamsCallback = (func: CircuitFunction, ...params: any[]) => any;

/**
* Properties that customizes the cache behavior.
*/
Expand All @@ -18,6 +21,12 @@ export abstract class CacheOptions extends ModuleOptions {
* The attribute name indicating if data is retrieved from cache or not
*/
getInformationFromCache? : boolean;

/**
* A filtering callback, to modify the parameters used for Cache Key.
* @returns The modified parameters
*/
adjustCacheParams?: AdjustCacheParamsCallback;
}

type CacheT<T> = T & {
Expand All @@ -37,6 +46,11 @@ export class Cache extends Module {
* The attribute name indicating if data is retrieved from cache or not
*/
public getInformationFromCache: boolean;

/**
* A filtering callback, to modify the parameters used for Cache Key.
*/
public adjustCacheParams: AdjustCacheParamsCallback|null;
// Private Attributes
private cache: MapCache;
private _cacheClearInterval: number;
Expand All @@ -60,6 +74,7 @@ export class Cache extends Module {
super(options);
this.ttl = (options?.ttl !== undefined) ? options.ttl : 6000; // 1 minute
this.getInformationFromCache = (options?.getInformationFromCache !== undefined) ? options.getInformationFromCache : false;
this.adjustCacheParams = options?.adjustCacheParams || null;
this._cacheInterval = null;
this._cacheClearInterval = 0;
this.cacheClearInterval = (options?.cacheClearInterval !== undefined) ? options.cacheClearInterval : 900000; // 15 minutes
Expand All @@ -84,7 +99,11 @@ export class Cache extends Module {
private async _promiseCache<T> (circuit: Circuit, promise: CircuitFunction, ...params: any[]): Promise<T | CacheT<T>> {
return new Promise((resolve, reject) => {
const cacheParams = this.getExecParams(circuit, params);
const cacheRes = this.cache.get<CacheT<T>>(circuit.func, ...cacheParams);
let cacheKey = cacheParams;
if (this.adjustCacheParams) {
cacheKey = this.adjustCacheParams(circuit.func, ...cacheParams);
}
const cacheRes = this.cache.get<CacheT<T>>(circuit.func, ...cacheKey);
if (cacheRes) {
if (typeof cacheRes.res === 'object' && this.getInformationFromCache) {
cacheRes.res._mollitiaIsFromCache = true;
Expand All @@ -94,7 +113,7 @@ export class Cache extends Module {
promise(...params)
.then((res: CacheT<T>) => {
if (this.ttl > 0) {
this.cache.set(this.ttl, circuit.func, ...cacheParams, res);
this.cache.set(this.ttl, circuit.func, ...cacheKey, res);
}
if (typeof res === 'object' && this.getInformationFromCache) {
res._mollitiaIsFromCache = false;
Expand All @@ -113,7 +132,7 @@ export class Cache extends Module {
promise(...params)
.then((res: CacheT<T>) => {
if (this.ttl > 0) {
this.cache.set(this.ttl, circuit.func, ...cacheParams, res);
this.cache.set(this.ttl, circuit.func, ...cacheKey, res);
}
if (typeof res === 'object' && this.getInformationFromCache) {
res._mollitiaIsFromCache = false;
Expand Down
48 changes: 48 additions & 0 deletions test/unit/module/cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ const failureAsync = jest.fn().mockImplementation((res: unknown = 'default', del
});
});

const successAsync2 = jest.fn().mockImplementation((res: unknown = 'default', res2: unknown = 'default', delay = 1) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({res, res2});
}, delay);
});
});

describe('Cache', () => {
afterEach(() => {
logger.debug.mockClear();
Expand Down Expand Up @@ -67,6 +75,46 @@ describe('Cache', () => {
circuit.dispose();
});

it('should cache the previous response - using onCache parameter', async () => {
const cacheModule = new Mollitia.Cache({
name: 'module-cache',
logger,
ttl: 100
});
const circuit = new Mollitia.Circuit({
name: 'circuit-cache',
options: {
modules: [ cacheModule ]
}
});
const objRef = { dummy: 'value1' };
const objRef2 = { dummy2: 'value2' };
const objRef3 = { dummy3: 'value3' };
await expect(circuit.fn(successAsync2).execute(objRef, objRef2)).resolves.toEqual({res: objRef, res2: objRef2});
await expect(circuit.fn(successAsync2).execute(objRef, objRef3)).resolves.toEqual({res: objRef, res2: objRef3});
expect(logger.debug).not.toHaveBeenNthCalledWith(1, 'circuit-cache/module-cache - Cache: Hit');

await delay(150);
cacheModule.adjustCacheParams = (circuitFunction: Mollitia.CircuitFunction, ...params) => {
return params.slice(1);
};
await expect(circuit.fn(successAsync2).execute(objRef, objRef2)).resolves.toEqual({res: objRef, res2: objRef2});
await expect(circuit.fn(successAsync2).execute(objRef, objRef3)).resolves.toEqual({res: objRef, res2: objRef3});
expect(logger.debug).not.toHaveBeenNthCalledWith(1, 'circuit-cache/module-cache - Cache: Hit');
await expect(circuit.fn(successAsync2).execute(objRef2, objRef)).resolves.toEqual({res: objRef2, res2: objRef});
await expect(circuit.fn(successAsync2).execute(objRef3, objRef)).resolves.toEqual({res: objRef2, res2: objRef});
expect(logger.debug).toHaveBeenNthCalledWith(1, 'circuit-cache/module-cache - Cache: Hit');

await delay(150);
cacheModule.adjustCacheParams = (circuitFunction: Mollitia.CircuitFunction, ...params) => {
return params.slice(0,1);
};
await expect(circuit.fn(successAsync2).execute(objRef, objRef2)).resolves.toEqual({res: objRef, res2: objRef2});
await expect(circuit.fn(successAsync2).execute(objRef, objRef3)).resolves.toEqual({res: objRef, res2: objRef2});
expect(logger.debug).toHaveBeenNthCalledWith(2, 'circuit-cache/module-cache - Cache: Hit');
circuit.dispose();
});

it('With multiple modules - Cache module in the middle of the modules - Cache the previous response by reference', async () => {
const moduleRetry = new Mollitia.Retry({
attempts: 2
Expand Down

0 comments on commit 3f1f8bb

Please sign in to comment.