Skip to main content

Caching

Godspeed provides caching of the tasks using redis/mem-cache or any other custom cache. You can cache the result of any task in the workflows.
Godspeed provides two pre-defined types of cache i.e. redis and in-memory. It allows using multiple caches in a single service with a default cache. Cache datasource should implement abstract class GSCacheAsDatasource code is below. You can make multiple caches using this abstract class.

You can read and return from cache if data is present there, before executing any task where caching instruction is set.
The data is stored in form of GSStatus of the executed task. Both success and failure statuses can be stored and loaded.

Configuration

Add pre-defined cache in Godspeed

You can use Godspeed CLI to browse and install redis/mem-cache, maintained by Godspeed.

godspeed plugin add


,_, ╔════════════════════════════════════╗
(o,o) ║ Welcome to Godspeed ║
({___}) ║ World's First Meta Framework ║
" " ╚════════════════════════════════════╝


? Please select godspeed plugin to install: (Press <space> to select, <Up and Down> to move rows)
┌──────┬────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│ │ Name │ Description │
├──────┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ ◯ │ prisma-as-datastore │ Prisma as a datasource plugin for Godspeed Framework. │
├──────┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ ◯ │ aws-as-datasource │ aws as datasource plugin for Godspeed Framework │
├──────┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ ❯◯ │ redis-as-datasource │ redis as datasource plugin for Godspeed Framework │
├──────┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ ◯ │ graphql-as-eventsource │ graphql as eventsource plugin for Godspeed Framework │
├──────┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ ◯ │ mem-cache-as-datasource │ mem-cache as datasource plugin for Godspeed Fraework Framework │
└──────┴────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘

Alternatively, you can specify redis-as-datasource or mem-cache-as-datasource to add cache in your project directly.

godspeed plugin add <plugin-name>

Once you add the desired cache, you can find its code in src/types/redis.ts where you can reuse our existing plugins or have code of your own plugin.
You can find its configuration in the src/datasources e.g. redis.yaml. All the redis database related configuration need to be set in this file. The redis.yaml is an instance of type: redis. You can have multiple instances of type: redis called redis1.yaml, redis2.yaml etc.

├── api.yaml
├── redis.yaml
└── types
├── axios.ts
└── redis.ts

Default cache

Define default cache datasource in static configuration in caching key.

log_level: debug
defaults:
lang: coffee
server_url: https://api.example.com:8443/v1/api
caching: <redis or mem-cache>

How to use caching in your tasks

Using caching in Typescript/Javascript tasks

note

Currently caching support is not provided when you call a datasource or function/workflow from typescript code. Check this github issue for more information.

For now if you want to use cache datasource within typescript code, you need to call it like any other datasource from within a typescript code. You need to manage caching and invalidations in your code. This requires some boilerplate till the above issue is implemented.

export default async function (ctx: GSContext, args: any) {
const redis_client = ctx.datasources['redis'].client;
let res: GSStatus;
const key = 'helloworld2';

try {
const cached_value = await redis_client.get(key);
if (!cached_value) {
res = await ctx.functions['com.gs.helloworld2'](ctx, {nice: "name", ...args});
await redis_client.set(key, JSON.stringify(res));
} else {
return JSON.parse(cached_value);
}
} catch(ex) {
return new GSStatus(false, 500, undefined, {message: "Internal Server Error", info: ex.message});
}

return res;
}

How to write your own cache plugin

You need to implement abstract class GSCacheAsDatasource interface to write your own cache plugin.

export abstract class GSCachingDataSource extends GSDataSource {
//Redis options are available [here](https://redis.io/commands/set/) Client may or may not support all actions. RedisOptions is a superset based on what Redis supports
public abstract set(key:string, val: any, options: RedisOptions): any;
public abstract get(key: string): any; //Return the value stored against the key
public abstract del(key: string): any; //Delete the key
}

export type RedisOptions = {
EX? : number,
PX? : number,
EXAT?: number,
NX?: boolean,
XX?: boolean,
KEEPTTL?: boolean,
GET?: boolean
}

Sample caching datasource implementation

This code is sourced from mem-cache plugin. You can use this as a reference to make or customise your own caching implementations.

mem-cache datasource plugin

Project structure

.
├── src
│ ├── datasources
│ │ ├── api.yaml
│ │ ├── mem-cache.yaml
│ │ └── types
│ │ ├── axios.ts
│ │ ├── mem-cache.ts

mem-cache config ( src/datasources/mem-cache.yaml )

type: mem-cache

initializing client and execution ( src/datasources/types/mem-cache.ts ):

import { GSContext, GSCachingDataSource, PlainObject, logger } from "@godspeedsystems/core";

export default class DataSource extends GSCachingDataSource {
protected async initClient(): Promise<PlainObject> {
this.client = {};
return this.client;
}

set(key: string, val: any, options: { EX?: number | undefined; PX?: number | undefined; EXAT?: number | undefined; NX?: boolean | undefined; XX?: boolean | undefined; KEEPTTL?: boolean | undefined; GET?: boolean | undefined; }) {
logger.debug('set key %s %o', key, this.client);
this.client[key] = val;
}

get(key: string) {
return this.client[key];
}

del(key: string) {
delete this.client[key];
}

execute(ctx: GSContext): Promise<any> {
throw new Error("Method not implemented.");
}
}