Background jobs and events — inside the owning domain
Background jobs enqueue from the domain that owns the work — callers must not know another domain's queue shape.
nestjs-data-jobs-in-owning-domain
Why it matters
| Stake | If ignored |
|---|---|
| Hidden coupling |
|
How to fix
If a domain needs async work (send an email, fire a webhook, recompute statistics), it defines the job inside its own module. Another domain does not push a job onto domain X's queue.
Cross-domain communication via events — a typed event bus, not a direct call into another module's job.
Examples
ts
// libs/workspace/backend-feature/src/lib/workflows.service.ts
@Injectable()
export class WorkflowsService {
constructor(
@InjectQueue('catalog-cleanup') private catalogQueue: Queue, // ← another domain's queue
) {}
async deleteWorkflow(id: string) {
await this.repo.delete(id);
await this.catalogQueue.add('rebuild-index', { workflowId: id }); // ← workspace knows catalog internals
}
}ts
// libs/shared/backend-events/src/lib/events.ts
export type WorkflowDeletedEvent = {
type: 'workflow.deleted';
workflowId: string;
at: string;
};
// libs/workspace/backend-feature/src/lib/workflows.service.ts
@Injectable()
export class WorkflowsService {
constructor(private events: EventBus) {}
async deleteWorkflow(id: string) {
await this.repo.delete(id);
await this.events.publish<WorkflowDeletedEvent>({
type: 'workflow.deleted',
workflowId: id,
at: new Date().toISOString(),
});
}
}
// libs/catalog/backend-feature/src/lib/workflow-deleted.handler.ts
@EventHandler('workflow.deleted')
export class WorkflowDeletedHandler {
async handle(event: WorkflowDeletedEvent) {
// catalog decides what to do, at its own pace
}
}