Custom Read Model Connectors
You can implement a custom Read Model connector to define how a Read Model's data is stored. A connector implements the following functions:
- connect - Initializes a connection to a storage.
- disconnect - Closes the storage connection.
- drop - Removes the Read Model's data from storage.
- dispose - Forcefully disposes all unmanaged resources used by Read Models served by this connector.
The code sample below demonstrates how to implement a connector that provides a file-based storage for Read Models.
common/read-models/custom-read-model-connector.js:
import fs from 'fs'
const safeUnlinkSync = (filename) => {
if (fs.existsSync(filename)) {
fs.unlinkSync(filename)
}
}
const connector = (options) => {
const prefix = String(options.prefix)
const readModels = new Set()
const connect = async (readModelName) => {
fs.writeFileSync(`${prefix}${readModelName}.lock`, 'true', { flag: 'wx' })
readModels.add(readModelName)
const store = {
get() {
return JSON.parse(String(fs.readFileSync(`${prefix}${readModelName}`)))
},
set(value) {
fs.writeFileSync(`${prefix}${readModelName}`, JSON.stringify(value))
},
}
return store
}
const disconnect = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
readModels.delete(readModelName)
}
const drop = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
safeUnlinkSync(`${prefix}${readModelName}`)
}
const dispose = async () => {
for (const readModelName of readModels) {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
}
readModels.clear()
}
return {
connect,
disconnect,
drop,
dispose,
}
}
A connector is defined as a function that receives an options
argument. This argument contains a custom set of options that you can specify in the connector's configuration.
Register the connector in the application's configuration file.
config.app.js:
readModelConnectors: {
customReadModelConnector: {
module: 'common/read-models/custom-read-model-connector.js',
options: {
prefix: path.join(__dirname, 'data') + path.sep // Path to a folder that contains custom Read Model store files
}
}
}
Now you can assign the custom connector to a Read Model by name as shown below.
config.app.js:
readModels: [
{
name: 'CustomReadModel',
projection: 'common/read-models/custom-read-model.projection.js',
resolvers: 'common/read-models/custom-read-model.resolvers.js',
connectorName: 'customReadModelConnector'
}
...
]
The code sample below demonstrates how you can use the custom store's API in the Read Model's code.
common/read-models/custom-read-model.projection.js:
const projection = {
Init: async (store) => {
await store.set(0)
},
INCREMENT: async (store, event) => {
await store.set((await store.get()) + event.payload.count)
},
DECREMENT: async (store, event) => {
await store.set((await store.get()) - event.payload.count)
},
}
export default projection
common/read-models/custom-read-model.resolvers.js:
const resolvers = {
read: async (store) => {
return await store.get()
},
}
export default resolvers