Essentials
Events & listeners
Not every consequence belongs in the handler that causes it. Emitting a domain event lets the rest of the app react, sending a mail, updating a projection, without the emitter knowing or caring who listens. Coupling drops; the code that matters stays readable.
#Emitting an event
The event emitter is injected like any service. Emit a named event with a payload from wherever the thing happens.
constructor ({ eventEmitter }) { this.events = eventEmitter }
create (event: IncomingHttpEvent) {
const task = this.tasks.add(event.get('title'))
this.events.emit('task.created', { task }) // fire and forget
return task
}const create = ({ tasks, eventEmitter }) => (event) => {
const task = tasks.add(event.get('title'))
eventEmitter.emit('task.created', { task })
return task
}The handler states what happened. Listeners decide what to do about it.
#Listening
A listener reacts to one event. Mark a class with @Listener (or register a defineEventListener), and it runs whenever that event is emitted, with its dependencies injected.
import { Listener } from '@stone-js/core'
@Listener({ event: 'task.created' })
export class NotifyOnCreate {
constructor ({ mailer }: { mailer: Mailer }) { this.mailer = mailer }
handle (event: { task: Task }) {
return this.mailer.send('a task was created', event.task)
}
}import { defineEventListener } from '@stone-js/core'
const NotifyOnCreate = ({ mailer }) => (event) =>
mailer.send('a task was created', event.task)
export const listeners = [
defineEventListener(NotifyOnCreate, { event: 'task.created' }, true)
]#Subscribers
When one unit handles several related events, a subscriber groups them: mark it with @Subscriber (or defineEventSubscriber) and map each event to a method, with its dependencies injected once for the whole group.
import { Subscriber } from '@stone-js/core'
@Subscriber()
export class TaskSubscriber {
constructor ({ metrics }: { metrics: Metrics }) { this.metrics = metrics }
subscribe (emitter) {
emitter.on('task.created', (e) => this.metrics.inc('tasks.created'))
emitter.on('task.deleted', (e) => this.metrics.inc('tasks.deleted'))
}
}#Wildcards
A wildcard name catches a whole family of events with one listener, useful for audit logs or metrics that should not enumerate every event by hand.
@Listener({ event: 'task.*' }) // task.created, task.updated, task.deleted, ...
export class AuditTaskEvents {
handle (event, name) { this.audit.record(name, event) }
}