Refactor event bus and queue system; detect cycles in DI realization and make
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2022-01-26 19:37:54 -06:00
parent 506fb55c74
commit 6d1cf18680
69 changed files with 1673 additions and 720 deletions

View 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)
}
}
}

View 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>>>
}

View 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
}
}

View 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
}
}

View 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,
) {}
}

View 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,
) {}
}