Refactor event bus and queue system; detect cycles in DI realization and make
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
33
src/support/bus/queue/CacheQueue.ts
Normal file
33
src/support/bus/queue/CacheQueue.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {Queue} from './Queue'
|
||||
import {Inject, Injectable} from '../../../di'
|
||||
import {Cache, Maybe} from '../../../util'
|
||||
import {Queueable, ShouldQueue} from '../types'
|
||||
import {Serialization} from '../serial/Serialization'
|
||||
|
||||
/**
|
||||
* Queue implementation that uses the configured cache driver as a queue.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CacheQueue extends Queue {
|
||||
@Inject()
|
||||
protected readonly cache!: Cache
|
||||
|
||||
@Inject()
|
||||
protected readonly serial!: Serialization
|
||||
|
||||
protected get queueIdentifier(): string {
|
||||
return `extollo__queue__${this.name}`
|
||||
}
|
||||
|
||||
protected async push<T extends Queueable>(item: ShouldQueue<T>): Promise<void> {
|
||||
const json = await this.serial.encodeJSON(item)
|
||||
await this.cache.arrayPush(this.queueIdentifier, json)
|
||||
}
|
||||
|
||||
async pop(): Promise<Maybe<ShouldQueue<Queueable>>> {
|
||||
const popped = await this.cache.arrayPop(this.queueIdentifier)
|
||||
if ( popped ) {
|
||||
return this.serial.decodeJSON(popped)
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/support/bus/queue/Queue.ts
Normal file
31
src/support/bus/queue/Queue.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {BusQueue, Queueable, shouldQueue, ShouldQueue} from '../types'
|
||||
import {Inject, Injectable} from '../../../di'
|
||||
import {Awaitable, Maybe} from '../../../util'
|
||||
import {Bus} from '../Bus'
|
||||
import {PushingToQueue} from './event/PushingToQueue'
|
||||
import {PushedToQueue} from './event/PushedToQueue'
|
||||
|
||||
@Injectable()
|
||||
export abstract class Queue implements BusQueue {
|
||||
@Inject()
|
||||
protected readonly bus!: Bus
|
||||
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
) {}
|
||||
|
||||
async dispatch<T extends Queueable>(item: T): Promise<void> {
|
||||
if ( shouldQueue(item) ) {
|
||||
await this.bus.push(new PushingToQueue(item))
|
||||
await this.push(item)
|
||||
await this.bus.push(new PushedToQueue(item))
|
||||
return
|
||||
}
|
||||
|
||||
await item.execute()
|
||||
}
|
||||
|
||||
protected abstract push<T extends Queueable>(item: ShouldQueue<T>): Awaitable<void>
|
||||
|
||||
abstract pop(): Promise<Maybe<ShouldQueue<Queueable>>>
|
||||
}
|
||||
87
src/support/bus/queue/QueueFactory.ts
Normal file
87
src/support/bus/queue/QueueFactory.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
AbstractFactory,
|
||||
Container,
|
||||
DependencyRequirement,
|
||||
PropertyDependency,
|
||||
isInstantiable,
|
||||
DEPENDENCY_KEYS_METADATA_KEY,
|
||||
DEPENDENCY_KEYS_PROPERTY_METADATA_KEY,
|
||||
StaticInstantiable,
|
||||
FactoryProducer,
|
||||
} from '../../../di'
|
||||
import {Collection, ErrorWithContext} from '../../../util'
|
||||
import {Logging} from '../../../service/Logging'
|
||||
import {Config} from '../../../service/Config'
|
||||
import {Queue} from './Queue'
|
||||
import {SyncQueue} from './SyncQueue'
|
||||
|
||||
/**
|
||||
* Dependency container factory that matches the abstract Queue token, but
|
||||
* produces an instance of whatever Queue driver is configured in the `server.queue.driver` config.
|
||||
*/
|
||||
@FactoryProducer()
|
||||
export class QueueFactory extends AbstractFactory<Queue> {
|
||||
/** true if we have printed the synchronous queue driver warning once. */
|
||||
private static loggedSyncQueueWarningOnce = false
|
||||
|
||||
private di(): [Logging, Config] {
|
||||
return [
|
||||
Container.getContainer().make(Logging),
|
||||
Container.getContainer().make(Config),
|
||||
]
|
||||
}
|
||||
|
||||
produce(): Queue {
|
||||
return new (this.getQueueClass())()
|
||||
}
|
||||
|
||||
match(something: unknown): boolean {
|
||||
return something === Queue
|
||||
}
|
||||
|
||||
getDependencyKeys(): Collection<DependencyRequirement> {
|
||||
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getQueueClass())
|
||||
if ( meta ) {
|
||||
return meta
|
||||
}
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
|
||||
getInjectedProperties(): Collection<PropertyDependency> {
|
||||
const meta = new Collection<PropertyDependency>()
|
||||
let currentToken = this.getQueueClass()
|
||||
|
||||
do {
|
||||
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
|
||||
if ( loadedMeta ) {
|
||||
meta.concat(loadedMeta)
|
||||
}
|
||||
currentToken = Object.getPrototypeOf(currentToken)
|
||||
} while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype)
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured queue driver and return some Instantiable<Queue>.
|
||||
* @protected
|
||||
*/
|
||||
protected getQueueClass(): StaticInstantiable<Queue> {
|
||||
const [logging, config] = this.di()
|
||||
const QueueClass = config.get('server.queue.driver', SyncQueue)
|
||||
if ( QueueClass === SyncQueue && !QueueFactory.loggedSyncQueueWarningOnce ) {
|
||||
logging.warn(`You are using the default synchronous queue driver. It is recommended you configure a background queue driver instead.`)
|
||||
QueueFactory.loggedSyncQueueWarningOnce = true
|
||||
}
|
||||
|
||||
if ( !isInstantiable(QueueClass) || !(QueueClass.prototype instanceof Queue) ) {
|
||||
const e = new ErrorWithContext('Provided queue class does not extend from @extollo/lib.Queue')
|
||||
e.context = {
|
||||
configKey: 'server.queue.driver',
|
||||
class: QueueClass.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
return QueueClass
|
||||
}
|
||||
}
|
||||
22
src/support/bus/queue/SyncQueue.ts
Normal file
22
src/support/bus/queue/SyncQueue.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Queue} from './Queue'
|
||||
import {Inject, Injectable} from '../../../di'
|
||||
import {Logging} from '../../../service/Logging'
|
||||
import {Queueable, ShouldQueue} from '../types'
|
||||
import {Maybe} from '../../../util'
|
||||
|
||||
/**
|
||||
* Simple queue implementation that executes items immediately in the current process.
|
||||
*/
|
||||
@Injectable()
|
||||
export class SyncQueue extends Queue {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
protected async push<T extends Queueable>(item: ShouldQueue<T>): Promise<void> {
|
||||
await item.execute()
|
||||
}
|
||||
|
||||
async pop(): Promise<Maybe<ShouldQueue<Queueable>>> {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
17
src/support/bus/queue/event/PushedToQueue.ts
Normal file
17
src/support/bus/queue/event/PushedToQueue.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {Event, Queueable, ShouldQueue} from '../../types'
|
||||
import {uuid4} from '../../../../util'
|
||||
|
||||
/**
|
||||
* Event fired after an item is pushed to the queue.
|
||||
*/
|
||||
export class PushedToQueue<T extends ShouldQueue<Queueable>> implements Event {
|
||||
public readonly eventName = '@extollo/lib:PushedToQueue'
|
||||
|
||||
public readonly eventUuid = uuid4()
|
||||
|
||||
public readonly shouldBroadcast = true
|
||||
|
||||
constructor(
|
||||
public readonly item: T,
|
||||
) {}
|
||||
}
|
||||
17
src/support/bus/queue/event/PushingToQueue.ts
Normal file
17
src/support/bus/queue/event/PushingToQueue.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {Event, Queueable, ShouldQueue} from '../../types'
|
||||
import {uuid4} from '../../../../util'
|
||||
|
||||
/**
|
||||
* Event fired before an item is pushed to the queue.
|
||||
*/
|
||||
export class PushingToQueue<T extends ShouldQueue<Queueable>> implements Event {
|
||||
public readonly eventName = '@extollo/lib:PushingToQueue'
|
||||
|
||||
public readonly eventUuid = uuid4()
|
||||
|
||||
public readonly shouldBroadcast = true
|
||||
|
||||
constructor(
|
||||
public readonly item: T,
|
||||
) {}
|
||||
}
|
||||
Reference in New Issue
Block a user