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
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;
}
Using caching in YAML tasks
Caching instruction
Caching instruction has the following specifications.
caching:
before: # execute before the task execution
datasource: <the name of the datasource instance to use instead of default cache>
key: <key name which is used to read the cached result from>
invalidate: <Key name which we want to delete/remove from cache e.g. this feature can be used in CRUD types task. While delete operation, invalidate the cache of read or update task>
after: # execute after the task execution
datasource: <the name of the datasource instance to use instead of default cache>
key: <key name which is used to write the cached result>
invalidate: <Key name which we want to delete/remove from cache e.g. this feature can be used in CRUD types task. While delete operation, invalidate the cache of read or update task>
cache_on_failure: <true|false, whether you want to cache the failure result or not. By default, it is false>
options:
EX: 200 <timer in seconds, until the cached result is valid> # Can pass any of RedisOptions, if supported by the specific caching Datasource
options
are redis options supported by redis cache. mem-cache does not support these options.caching.before
instruction is used to read the result from cache and gets executed before the task execution.caching.after
instruction is used to write the result in cache and gets executed after the task execution.- In case where the result is present and returned from the cache,
caching.after
instruction doesn't get executed for that task. datasource
specified in the caching instruction overrides the default cache datasource.
Sample
Here is the caching spec to write in the workflow.
caching: redis
# Events
"http.get./helloworld2":
fn: helloworld2
"http.get./helloworld3":
fn: helloworld3
# Functions (Helloworld2 workflow)
id: helloworld2_workflow
tasks:
- id: helloworld2_workflow_first_task
fn: com.gs.transform
args:
name: helloworld2
caching:
after:
key: xyz
# Functions (Helloworld3 workflow)
id: helloworld3_workflow
tasks:
- id: helloworld3_workflow_first_task
caching:
before:
key: abc
invalidate: xyz #helloworld2 key
after:
key: abc
datasource: mem-cache #overrides the default cache `redis`
fn: com.gs.transform
args:
name: helloworld3
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, args: PlainObject): Promise<any> {
throw new Error("Method not implemented.");
}
}