Cache

Core ships with a cache abstraction that makes it easy to switch from an in-memory cache to a redis-based one. The default driver that is shipped provides an in-memory cache but using your own cache driver is just as easy.

Prerequisites

Before we start, we need to establish what a few recurring variables and imports in this document refer to when they are used.

1import { app, Container, Services } from "@arkecosystem/core-kernel";
  • The app import refers to the application instance which grants access to the container, configurations, system information and more.
  • The Container import refers to a namespace that contains all of the container specific entities like binding symbols and interfaces.
  • The Services import refers to a namespace that contains all of the core services. This generally will only be needed for type hints as Core is responsible for service creation and maintenance.

Cache Usage

Create an instance

1const cacheStore: CacheStore<string, string> = app.get<CacheStore<string, string>>(Container.Identifiers.CacheFactory)()

Get all of the items in the cache

1cacheStore.all();

Get the keys of the cache items

1cacheStore.keys();

Get the values of the cache items

1cacheStore.values();

Retrieve an item from the cache by key

1cacheStore.get("key");

Retrieve multiple items from the cache by key

1cacheStore.getMany(["key1", "key2"]);

Store an item in the cache for a given number of seconds

1cacheStore.put("key", "value", 60);

Store multiple items in the cache for a given number of seconds

1cacheStore.putMany(["value1", "value2"], 60);

Determine if an item exists in the cache

1cacheStore.has("key");

Determine multiple items exist in the cache

1cacheStore.hasMany(["key1", "key2"]);

Determine if an item doesn’t exist in the cache

1cacheStore.missing("key");

Determine multiple items don’t exist in the cache

1cacheStore.missingMany(["key1", "key2"]);

Store an item in the cache indefinitely

1cacheStore.forever("key", "value");

Store multiple items in the cache indefinitely

1cacheStore.foreverMany(["value1", "value2"]);

Remove an item from the cache

1cacheStore.forget("key");

Remove multiple items from the cache

1cacheStore.forgetMany(["key1", "key2"]);

Remove all items from the cache

1cacheStore.flush();

Extending

As explained in a previous article, it is possible to extend Core services due to the fact that a Manager pattern is used. Lets go over a quick example of how you could implement your own cache store.

Implementing the Driver

Implementing a new driver is as simple as importing the cache store contract that needs to be satisfied and implement the methods specified in it.

1import { Contracts } from "@arkecosystem/core-kernel";
2 
3export class MemoryCacheStore<K, T> implements Contracts.Cache.CacheStore<K, T> {
4 private readonly store: Map<K, T> = new Map<K, T>();
5 
6 public async make(): Promise<CacheStore<K, T>> {
7 return this;
8 }
9 
10 public async all(): Promise<Array<[K, T]>> {
11 return Array.from(this.store.entries());
12 }
13 
14 public async keys(): Promise<K[]> {
15 return Array.from(this.store.keys());
16 }
17 
18 public async values(): Promise<T[]> {
19 return Array.from(this.store.values());
20 }
21 
22 public async get(key: K): Promise<T | undefined> {
23 return this.store.get(key);
24 }
25 
26 public async getMany(keys: K[]): Promise<Array<T | undefined>> {
27 return keys.map((key: K) => this.store.get(key));
28 }
29 
30 public async put(key: K, value: T, seconds?: number): Promise<boolean> {
31 this.store.set(key, value);
32 
33 return this.has(key);
34 }
35 
36 public async putMany(values: Array<[K, T]>, seconds?: number): Promise<boolean[]> {
37 return Promise.all(values.map(async (value: [K, T]) => this.put(value[0], value[1])));
38 }
39 
40 public async has(key: K): Promise<boolean> {
41 return this.store.has(key);
42 }
43 
44 public async hasMany(keys: K[]): Promise<boolean[]> {
45 return Promise.all(keys.map(async (key: K) => this.has(key)));
46 }
47 
48 public async missing(key: K): Promise<boolean> {
49 return !this.store.has(key);
50 }
51 
52 public async missingMany(keys: K[]): Promise<boolean[]> {
53 return Promise.all([...keys].map(async (key: K) => this.missing(key)));
54 }
55 
56 public async forever(key: K, value: T): Promise<boolean> {
57 // This in-memory store doesn't offer any persistence.
58 }
59 
60 public async foreverMany(values: Array<[K, T]>): Promise<boolean[]> {
61 // This in-memory store doesn't offer any persistence.
62 }
63 
64 public async forget(key: K): Promise<boolean> {
65 this.store.delete(key);
66 
67 return this.missing(key);
68 }
69 
70 public async forgetMany(keys: K[]): Promise<boolean[]> {
71 return Promise.all(keys.map(async (key: K) => this.forget(key)));
72 }
73 
74 public async flush(): Promise<boolean> {
75 this.store.clear();
76 
77 return this.store.size === 0;
78 }
79 
80 public async getPrefix(): Promise<string> {
81 // This in-memory store doesn't offer any persistence.
82 }
83}

Implementing the service provider

Now that we have implemented our memory driver for the cache service, we can create a service provider to register it.

1import { Container, Contracts, Providers, Services } from "@arkecosystem/core-kernel";
2 
3export class ServiceProvider extends Providers.ServiceProvider {
4 public async register(): Promise<void> {
5 const cacheManager: Services.Cache.CacheManager = this.app.get<Services.Cache.CacheManager>(
6 Container.Identifiers.CacheManager,
7 );
8 
9 await cacheManager.extend("memory", MemoryCacheStore);
10 }
11}
  1. We retrieve an instance of the cache manager that is responsible for managing cache drivers.
  2. We call the extend method with an asynchronous function which is responsible for creating the cache store instance.
Last updated 3 years ago
Edit Page
Share: