-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
poc: implement new FrameworkRepository
- Loading branch information
1 parent
561b945
commit 1cf87b8
Showing
8 changed files
with
307 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
api/src/shared/infrastructure/caches/learning-content-pubsub.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { createRedisEventTarget } from '@graphql-yoga/redis-event-target'; | ||
import { createPubSub } from '@graphql-yoga/subscription'; | ||
import { Redis } from 'ioredis'; | ||
|
||
import { config } from '../../config.js'; | ||
|
||
/** | ||
* @typedef {import('@graphql-yoga/subscription').PubSub<{ | ||
* [key: string]: [message: object] | ||
* }>} LearningContentPubSub | ||
*/ | ||
|
||
/** @type {LearningContentPubSub} */ | ||
let pubSub; | ||
|
||
/** @type {import('ioredis').Redis} */ | ||
let publishClient; | ||
/** @type {import('ioredis').Redis} */ | ||
let subscribeClient; | ||
|
||
export function getPubSub() { | ||
if (pubSub) return pubSub; | ||
|
||
if (!config.caching.redisUrl) { | ||
pubSub = createPubSub(); | ||
return pubSub; | ||
} | ||
|
||
publishClient = new Redis(config.caching.redisUrl); | ||
subscribeClient = new Redis(config.caching.redisUrl); | ||
|
||
pubSub = createPubSub({ | ||
eventTarget: createRedisEventTarget({ | ||
publishClient, | ||
subscribeClient, | ||
}), | ||
}); | ||
|
||
return pubSub; | ||
} | ||
|
||
export async function quit() { | ||
await publishClient?.quit(); | ||
await subscribeClient?.quit(); | ||
} |
114 changes: 114 additions & 0 deletions
114
api/src/shared/infrastructure/repositories/learning-content-repository.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import Dataloader from 'dataloader'; | ||
|
||
import { knex } from '../../../../db/knex-database-connection.js'; | ||
import * as learningContentPubSub from '../caches/learning-content-pubsub.js'; | ||
|
||
export class LearningContentRepository { | ||
#tableName; | ||
#toDomain; | ||
#idType; | ||
#dataloader; | ||
#findCache; | ||
#findCacheMiss; | ||
|
||
constructor({ tableName, toDomain, idType = 'text', pubSub = learningContentPubSub.getPubSub() }) { | ||
this.#tableName = tableName; | ||
this.#toDomain = toDomain; | ||
this.#idType = idType; | ||
|
||
this.#dataloader = new Dataloader((ids) => this.#batchLoad(ids), { | ||
cacheMap: new LearningContentCache({ | ||
name: `${tableName}:entities`, | ||
pubSub, | ||
}), | ||
}); | ||
|
||
this.#findCache = new LearningContentCache({ | ||
name: `${tableName}:results`, | ||
pubSub, | ||
}); | ||
|
||
this.#findCacheMiss = new Map(); | ||
} | ||
|
||
async find(cacheKey, callback) { | ||
const dtos = await this.#findDtos(callback, cacheKey); | ||
return dtos.map(this.#toDomain); | ||
} | ||
|
||
#findDtos(callback, cacheKey) { | ||
let dtos = this.#findCache.get(cacheKey); | ||
if (dtos) return dtos; | ||
|
||
dtos = this.#findCacheMiss.get(cacheKey); | ||
if (dtos) return dtos; | ||
|
||
dtos = this.#loadDtos(callback, cacheKey).finally(() => { | ||
this.#findCacheMiss.delete(cacheKey); | ||
}); | ||
this.#findCacheMiss.set(cacheKey, dtos); | ||
|
||
return dtos; | ||
} | ||
|
||
async #loadDtos(callback, cacheKey) { | ||
const ids = await callback(knex.pluck('id').from(this.#tableName)); | ||
const dtos = await this.#dataloader.loadMany(ids); | ||
this.#findCache.set(cacheKey, dtos); | ||
return dtos; | ||
} | ||
|
||
async #batchLoad(ids) { | ||
return knex | ||
.select(`${this.#tableName}.*`) | ||
.from(knex.raw(`unnest(?::${this.#idType}[]) with ordinality as ids(id, idx)`, [ids])) // eslint-disable-line knex/avoid-injections | ||
.leftJoin(this.#tableName, `${this.#tableName}.id`, 'ids.id') | ||
.orderBy('ids.idx'); | ||
} | ||
|
||
clear() { | ||
this.#dataloader.clearAll(); | ||
this.#findCache.clear(); | ||
} | ||
} | ||
|
||
class LearningContentCache { | ||
#map = new Map(); | ||
#pubSub; | ||
#name; | ||
|
||
/** | ||
* @param {{ | ||
* pubSub: import('../caches/learning-content-pubsub.js').LearningContentPubSub | ||
* name: string | ||
* }} config | ||
* @returns | ||
*/ | ||
constructor({ pubSub, name }) { | ||
this.#pubSub = pubSub; | ||
this.#name = name; | ||
|
||
(async () => { | ||
for await (const message of pubSub.subscribe(name)) { | ||
if (message.type === 'clear') this.#map.clear(); | ||
if (message.type === 'delete') this.#map.delete(this.message.key); | ||
} | ||
})(); | ||
} | ||
|
||
get(key) { | ||
return this.#map.get(key); | ||
} | ||
|
||
set(key, value) { | ||
return this.#map.set(key, value); | ||
} | ||
|
||
delete(key) { | ||
return this.#pubSub.publish(this.#name, { type: 'delete', key }); | ||
} | ||
|
||
clear() { | ||
return this.#pubSub.publish(this.#name, { type: 'clear' }); | ||
} | ||
} |
Oops, something went wrong.