Home
Blog
Product

Announcing Cache Modules

Apr 04, 2023 by

Frane Polić

Frane avatar

Frane Polić

We are excited to announce that we introduced cache modules into Medusa. This article gives an under-the-hood look at this feature.

Announcing Cache Modules - Featured image

Medusa provides commerce building blocks for developers to build rich commerce applications. With the latest evolution of our toolkit towards a more modular architecture, we are taking steps to make Medusa more agnostic to infrastructure. As a big step in this direction, we are excited to announce Medusa’s new cache modules.

In this article, we will look under the hood to see how cache modules work, which cache module you can use in your project today, and, finally, we will demonstrate how easily a Memcached module can be built.

How it works

Cache modules in Medusa implement the Copy to clipboardICacheService interface and are used internally to cache data like product prices and tax rates.

Medusa’s default starter project comes with an in-memory cache module (Copy to clipboard@medusajs/cache-inmemory) pre-installed, which uses a plain JavaScript Map object to store data. This is great for testing and development purposes, but not recommended for production environments. You can have one cache module installed at a time and for production environments, we have built a cache module that uses Redis (Copy to clipboard@medusajs/cache-redis). To enable the Redis cache module, simply install the package:

yarn add @medusajs/cache-redis

And configure it in your Copy to clipboardmedusa-config.js file:

module.exports = {
//...
modules: {
// ...
"cacheService": {
resolve: "@medusajs/cache-redis",
options: { ttl: 30, redisUrl: REDIS_URL }
}
},
};

And that’s it. With just a couple of changed config lines, you use a different data store for our caching layer.

Building a cache module

As mentioned above, we are making in-memory and Redis cache modules available today. Building a new cache module is, however, very easy if you have a different caching tool in place already or prefer another caching technology. To demonstrate how to build a cache module, we will implement Copy to clipboardICacheService to work with Memcached. For communication with the Memcached instance, we will be using the Memcached client for Node.js.

The first thing to do is to create our project. We need a basic TypeScript project configured for Node. You can clone a preconfigured starter here, and after running Copy to clipboardyarn install, start building.

To implement the service, create a new TypeScript file in Copy to clipboardsrc/services:

// src/services/memcached-cache.ts
import Memcached from "memcached" // 1. Memcached client
import { ICacheService } from "@medusajs/medusa" // 2. Interface from medusa core that all caching modules need to implement
const DEFAULT_CACHE_TIME = 30 // 3. Keep data cached for 30 seconds
/**
* 4. Module config type.
* Defines which options can be passed to the module in the medusa-config file.
*/
type MemcachedCacheModuleOptions = {
/**
* Time to keep data in the cache (in seconds)
*/
ttl?: number
/**
* Allow passing the configuration for Memcached client
*/
location: Memcached.Location
options?: Memcached.options
}
type InjectedDependencies = {}
class MemcachedCacheService implements ICacheService {
protected readonly memcached: Memcached
protected readonly TTL: number
constructor(
{}: InjectedDependencies,
options: MemcachedCacheModuleOptions // 5. Options passed as module config will be injected here by the modules loader
) {
this.memcached = new Memcached(options.location, options.options) // 6. Instantiate Memcached client for communication with the Memcached instance
this.TTL = options.ttl || DEFAULT_CACHE_TIME
}
/**
* Set a key/value pair to the cache.
* It is also possible to manage the `ttl` through environment variables using CACHE_TTL. If the ttl is 0 it will
* act like the value should not be cached at all.
*/
async set(
key: string,
data: Record<string, unknown>,
ttl: number = this.TTL
): Promise<void> {
return new Promise((res, rej) =>
this.memcached.set(key, JSON.stringify(data), ttl, (err) => {
if (err) {
rej(err)
} else {
res()
}
})
)
}
/**
* Retrieve a cached value belonging to the given key.
*/
async get<T>(cacheKey: string): Promise<T | null> {
return new Promise((res, rej) => {
this.memcached.get(cacheKey, (err, data) => {
if (err) {
res(null)
} else {
if (data) {
res(JSON.parse(data))
} else {
res(null)
}
}
})
})
}
/**
* Invalidate cache for a specific key.
*/
async invalidate(key: string): Promise<void> {
return new Promise((res, rej) => {
this.memcached.del(key, (err) => {
if (err) {
rej(err)
} else {
res()
}
})
})
}
}
export default MemcachedCacheService

And that’s it. The final thing to add is export from the Copy to clipboardindex.ts file like so:

// src/index.ts
import { ModuleExports } from "@medusajs/modules-sdk"
import { MemcachedCacheService } from "./services"
const service = MemcachedCacheService
const moduleDefinition: ModuleExports = {
service,
}
export default moduleDefinition

You can find the entire implementation here.

To test the module with a Medusa instance, link your module project with a Medusa instance:

// first build the package from the root
yarn build
// register package
yarn link

Next, we need to add this package to our Medusa project and tweak our Copy to clipboardmedusa-config.js file:

// in the root of your Medusa project run:
yarn link medusa-cache-memcached
// medusa-config.js
module.exports = {
// ...
modules:{
// ...
"cacheService":
{
resolve: "medusa-cache-memcached",
options: { location: "localhost:55000" }}
},
};

With this example, we see how straightforward it is to build a (cache) module for Medusa. This, combined with the fact that Medusa is an open-source project, shows the advantages users of such projects have in terms of a growing ecosystem of existing modules.

What’s next

The introduction of cache modules is a step in adding infrastructure agnosticism to Medusa’s architecture. This will make it easier to integrate Medusa into existing stacks and improve the developer experience. Our introduction of EventBus modules is a similar evolution to Medusa’s architecture. We are excited to see what you build with these new tools and can’t wait to launch more modules in the near future.

Learn more about Cache Modules in our documentation.

Share this post

Try Medusa

Spin up your environment in a few minutes.