feat: add services package
This commit is contained in:
117
packages/services/src/Domain/Internal/InternalEventBus.spec.ts
Normal file
117
packages/services/src/Domain/Internal/InternalEventBus.spec.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
|
||||
import { InternalEventBus } from './InternalEventBus'
|
||||
import { InternalEventPublishStrategy } from './InternalEventPublishStrategy'
|
||||
|
||||
describe('InternalEventBus', () => {
|
||||
let eventHandler1: InternalEventHandlerInterface
|
||||
let eventHandler2: InternalEventHandlerInterface
|
||||
let eventHandler3: InternalEventHandlerInterface
|
||||
|
||||
const createEventBus = () => new InternalEventBus()
|
||||
|
||||
beforeEach(() => {
|
||||
eventHandler1 = {} as jest.Mocked<InternalEventHandlerInterface>
|
||||
eventHandler1.handleEvent = jest.fn()
|
||||
|
||||
eventHandler2 = {} as jest.Mocked<InternalEventHandlerInterface>
|
||||
eventHandler2.handleEvent = jest.fn()
|
||||
|
||||
eventHandler3 = {} as jest.Mocked<InternalEventHandlerInterface>
|
||||
eventHandler3.handleEvent = jest.fn()
|
||||
})
|
||||
|
||||
it('should trigger appropriate event handlers upon event publishing', () => {
|
||||
const eventBus = createEventBus()
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_1')
|
||||
eventBus.addEventHandler(eventHandler2, 'test_event_2')
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_3')
|
||||
eventBus.addEventHandler(eventHandler3, 'test_event_2')
|
||||
|
||||
eventBus.publish({ type: 'test_event_2', payload: { foo: 'bar' } })
|
||||
|
||||
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({
|
||||
type: 'test_event_2',
|
||||
payload: { foo: 'bar' },
|
||||
})
|
||||
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({
|
||||
type: 'test_event_2',
|
||||
payload: { foo: 'bar' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should do nothing if there are no appropriate event handlers', () => {
|
||||
const eventBus = createEventBus()
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_1')
|
||||
eventBus.addEventHandler(eventHandler2, 'test_event_2')
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_3')
|
||||
eventBus.addEventHandler(eventHandler3, 'test_event_2')
|
||||
|
||||
eventBus.publish({ type: 'test_event_4', payload: { foo: 'bar' } })
|
||||
|
||||
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler2.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler3.handleEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle event synchronously in a sequential order', async () => {
|
||||
const eventBus = createEventBus()
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_1')
|
||||
eventBus.addEventHandler(eventHandler2, 'test_event_2')
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_3')
|
||||
eventBus.addEventHandler(eventHandler3, 'test_event_2')
|
||||
|
||||
await eventBus.publishSync({ type: 'test_event_2', payload: { foo: 'bar' } }, InternalEventPublishStrategy.SEQUENCE)
|
||||
|
||||
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({
|
||||
type: 'test_event_2',
|
||||
payload: { foo: 'bar' },
|
||||
})
|
||||
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({
|
||||
type: 'test_event_2',
|
||||
payload: { foo: 'bar' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle event synchronously in a random order', async () => {
|
||||
const eventBus = createEventBus()
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_1')
|
||||
eventBus.addEventHandler(eventHandler2, 'test_event_2')
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_3')
|
||||
eventBus.addEventHandler(eventHandler3, 'test_event_2')
|
||||
|
||||
await eventBus.publishSync({ type: 'test_event_2', payload: { foo: 'bar' } }, InternalEventPublishStrategy.ASYNC)
|
||||
|
||||
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({
|
||||
type: 'test_event_2',
|
||||
payload: { foo: 'bar' },
|
||||
})
|
||||
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({
|
||||
type: 'test_event_2',
|
||||
payload: { foo: 'bar' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should do nothing if there are no appropriate event handlers for synchronous handling', async () => {
|
||||
const eventBus = createEventBus()
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_1')
|
||||
eventBus.addEventHandler(eventHandler2, 'test_event_2')
|
||||
eventBus.addEventHandler(eventHandler1, 'test_event_3')
|
||||
eventBus.addEventHandler(eventHandler3, 'test_event_2')
|
||||
|
||||
await eventBus.publishSync({ type: 'test_event_4', payload: { foo: 'bar' } }, InternalEventPublishStrategy.ASYNC)
|
||||
|
||||
expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler2.handleEvent).not.toHaveBeenCalled()
|
||||
expect(eventHandler3.handleEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should clear event observers on deinit', async () => {
|
||||
const eventBus = createEventBus()
|
||||
eventBus.deinit()
|
||||
|
||||
expect(eventBus['eventHandlers']).toBeUndefined
|
||||
})
|
||||
})
|
||||
61
packages/services/src/Domain/Internal/InternalEventBus.ts
Normal file
61
packages/services/src/Domain/Internal/InternalEventBus.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { InternalEventBusInterface } from './InternalEventBusInterface'
|
||||
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
|
||||
import { InternalEventInterface } from './InternalEventInterface'
|
||||
import { InternalEventPublishStrategy } from './InternalEventPublishStrategy'
|
||||
import { InternalEventType } from './InternalEventType'
|
||||
|
||||
export class InternalEventBus implements InternalEventBusInterface {
|
||||
private eventHandlers: Map<InternalEventType, InternalEventHandlerInterface[]>
|
||||
|
||||
constructor() {
|
||||
this.eventHandlers = new Map<InternalEventType, InternalEventHandlerInterface[]>()
|
||||
}
|
||||
|
||||
deinit(): void {
|
||||
;(this.eventHandlers as unknown) = undefined
|
||||
}
|
||||
|
||||
addEventHandler(handler: InternalEventHandlerInterface, eventType: string): void {
|
||||
let handlersForEventType = this.eventHandlers.get(eventType)
|
||||
if (handlersForEventType === undefined) {
|
||||
handlersForEventType = []
|
||||
}
|
||||
|
||||
handlersForEventType.push(handler)
|
||||
|
||||
this.eventHandlers.set(eventType, handlersForEventType)
|
||||
}
|
||||
|
||||
publish(event: InternalEventInterface): void {
|
||||
const handlersForEventType = this.eventHandlers.get(event.type)
|
||||
if (handlersForEventType === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const handlerForEventType of handlersForEventType) {
|
||||
void handlerForEventType.handleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
async publishSync(event: InternalEventInterface, strategy: InternalEventPublishStrategy): Promise<void> {
|
||||
const handlersForEventType = this.eventHandlers.get(event.type)
|
||||
if (handlersForEventType === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (strategy === InternalEventPublishStrategy.SEQUENCE) {
|
||||
for (const handlerForEventType of handlersForEventType) {
|
||||
await handlerForEventType.handleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
if (strategy === InternalEventPublishStrategy.ASYNC) {
|
||||
const handlerPromises = []
|
||||
for (const handlerForEventType of handlersForEventType) {
|
||||
handlerPromises.push(handlerForEventType.handleEvent(event))
|
||||
}
|
||||
|
||||
await Promise.all(handlerPromises)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { InternalEventInterface } from './InternalEventInterface'
|
||||
import { InternalEventType } from './InternalEventType'
|
||||
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
|
||||
import { InternalEventPublishStrategy } from '..'
|
||||
|
||||
export interface InternalEventBusInterface {
|
||||
/**
|
||||
* Associate an event handler with a certain event type
|
||||
* @param handler event handler instance
|
||||
* @param eventType event type to associate with
|
||||
*/
|
||||
addEventHandler(handler: InternalEventHandlerInterface, eventType: InternalEventType): void
|
||||
/**
|
||||
* Asynchronously publish an event for handling
|
||||
* @param event internal event object
|
||||
*/
|
||||
publish(event: InternalEventInterface): void
|
||||
/**
|
||||
* Synchronously publish an event for handling.
|
||||
* This will await for all handlers to finish processing the event.
|
||||
* @param event internal event object
|
||||
* @param strategy strategy with which the handlers will process the event.
|
||||
* Either all handlers will start at once or they will do it sequentially.
|
||||
*/
|
||||
publishSync(event: InternalEventInterface, strategy: InternalEventPublishStrategy): Promise<void>
|
||||
|
||||
deinit(): void
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { InternalEventInterface } from './InternalEventInterface'
|
||||
|
||||
export interface InternalEventHandlerInterface {
|
||||
handleEvent(event: InternalEventInterface): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { InternalEventType } from './InternalEventType'
|
||||
|
||||
export interface InternalEventInterface {
|
||||
type: InternalEventType
|
||||
payload: unknown
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum InternalEventPublishStrategy {
|
||||
ASYNC = 'ASYNC',
|
||||
SEQUENCE = 'SEQUENCE',
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type InternalEventType = string
|
||||
Reference in New Issue
Block a user