Skip to content

Commit

Permalink
feat(api): documentation and logs for learning content repository and…
Browse files Browse the repository at this point in the history
… cache
  • Loading branch information
nlepage committed Dec 4, 2024
1 parent d46494e commit 2deb874
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 16 deletions.
13 changes: 11 additions & 2 deletions api/src/shared/infrastructure/caches/learning-content-cache.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as learningContentPubSub from '../caches/learning-content-pubsub.js';
import { child } from '../utils/logger.js';

const logger = child('learningcontent:cache', { event: 'learningcontent' });

export class LearningContentCache {
#map;
Expand Down Expand Up @@ -39,8 +42,14 @@ export class LearningContentCache {

async #subscribe() {
for await (const message of this.#pubSub.subscribe(this.#name)) {
if (message.type === 'clear') this.#map.clear();
if (message.type === 'delete') this.#map.delete(message.key);
if (message.type === 'clear') {
logger.debug({ name: this.#name }, 'clearing cache');
this.#map.clear();
}
if (message.type === 'delete') {
logger.debug({ name: this.#name, key: message.key }, 'deleting cache key');
this.#map.delete(message.key);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,31 @@ import Dataloader from 'dataloader';

import { knex } from '../../../../db/knex-database-connection.js';
import { LearningContentCache } from '../caches/learning-content-cache.js';
import { child } from '../utils/logger.js';

const logger = child('learningcontent:repository', { event: 'learningcontent' });

/**
* @typedef {(knex: import('knex').QueryBuilder) => Promise<string[]|number[]>} QueryBuilderCallback
*/

/**
* Datasource for learning content repositories.
* This datasource uses a {@link Dataloader} to load and cache entities.
*/
export class LearningContentRepository {
#tableName;
#idType;
#dataloader;
#findCache;
#findCacheMiss;

/**
* @param {{
* tableName: string
* idType?: 'text' | 'integer'
* }} config
*/
constructor({ tableName, idType = 'text' }) {
this.#tableName = tableName;
this.#idType = idType;
Expand All @@ -23,21 +40,15 @@ export class LearningContentRepository {
this.#findCacheMiss = new Map();
}

/**
* Finds several entities using a request and caches results.
* The request is built using a knex query builder given to {@link callback}.
* {@link cacheKey} must vary according to params given to the query builder.
* @param {string} cacheKey
* @param {QueryBuilderCallback} callback
* @returns {Promise<object[]>}
*/
async find(cacheKey, callback) {
return this.#findDtos(callback, cacheKey);
}

async load(id) {
if (!id) return null;
return this.#dataloader.load(id);
}

async loadMany(ids) {
const notNullIds = ids.filter((id) => id);
return this.#dataloader.loadMany(notNullIds);
}

#findDtos(callback, cacheKey) {
let dtos = this.#findCache.get(cacheKey);
if (dtos) return dtos;

Expand All @@ -52,14 +63,51 @@ export class LearningContentRepository {
return dtos;
}

/**
* Loads one entity by ID.
* @param {string|number} id
* @returns {Promise<object>}
*/
async load(id) {
if (!id) return null;
return this.#dataloader.load(id);
}

/**
* Loads several entities by ID.
* @param {string[]|number[]} ids
* @returns {Promise<object[]>}
*/
async loadMany(ids) {
const notNullIds = ids.filter((id) => id);
return this.#dataloader.loadMany(notNullIds);
}

/**
* Loads entities from database using a request and writes result to cache.
* @param {string} cacheKey
* @param {QueryBuilderCallback} callback
* @returns {Promise<object[]>}
*/
async #loadDtos(callback, cacheKey) {
const ids = await callback(knex.pluck(`${this.#tableName}.id`).from(this.#tableName));
const dtos = await this.#dataloader.loadMany(ids);

logger.debug({ tableName: this.#tableName, cacheKey }, 'caching find result');
this.#findCache.set(cacheKey, dtos);

return dtos;
}

/**
* Loads a batch of entities from database by ID.
* Entities are returned in the same order as {@link ids}.
* If an ID is not found, it is null in results.
* @param {string[]|number[]} ids
* @returns {Promise<(object|null)[]>}
*/
async #batchLoad(ids) {
logger.debug({ tableName: this.#tableName, count: ids.length }, 'loading from PG');
const dtos = await knex
.select(`${this.#tableName}.*`)
.from(knex.raw(`unnest(?::${this.#idType}[]) with ordinality as ids(id, idx)`, [ids])) // eslint-disable-line knex/avoid-injections
Expand All @@ -68,7 +116,15 @@ export class LearningContentRepository {
return dtos.map((dto) => (dto.id ? dto : null));
}

/**
* Clears repository’s cache.
* If {@link id} is undefined, all cache is cleared.
* If {@link id} is given, cache is partially cleared.
* @param {string|number|undefined} id
*/
clearCache(id) {
logger.debug({ tableName: this.#tableName, id }, 'trigerring cache clear');

if (id) {
this.#dataloader.clear(id);
} else {
Expand Down

0 comments on commit 2deb874

Please sign in to comment.