BlueprintEventBus Integration Skill
This skill helps implement event-driven architecture using BlueprintEventBus.
Core Principles
Event-Driven Architecture
- Decoupling: Modules communicate via events, not direct calls
- Blueprint Context: All events include Blueprint information
- Type Safety: Events are strongly typed with TypeScript
- Lifecycle Management: Automatic subscription cleanup
When to Use EventBus
✅ DO use EventBus for:
- Cross-module communication
- Broadcasting state changes to multiple listeners
- Audit logging and activity tracking
- Notification triggers
- Workflow orchestration
❌ DON'T use EventBus for:
- Simple parent-child component communication (use @Output)
- Direct service-to-service calls within same module
- Synchronous data fetching
- Request-response patterns (use Services)
Event Structure
Base Event Interface
interface BlueprintEvent<T = any> {
type: string; // Format: [module].[action]
blueprintId: string;
timestamp: Date;
actor: string; // User ID who triggered event
data: T;
metadata?: Record<string, any>;
}
Event Naming Convention
[module].[action]
Examples:
task.createdtask.updatedtask.deletedtask.assignedmember.addedmember.removedfile.uploadedblueprint.archived
Publishing Events
Basic Event Publishing
import { inject } from '@angular/core';
import { BlueprintEventBus } from '@core/services/blueprint-event-bus.service';
@Injectable({ providedIn: 'root' })
export class TaskService {
private eventBus = inject(BlueprintEventBus);
private taskRepository = inject(TaskRepository);
async createTask(blueprintId: string, task: CreateTaskDto): Promise<Task> {
// 1. Execute business logic
const created = await this.taskRepository.create(blueprintId, task);
// 2. Publish domain event
this.eventBus.publish({
type: 'task.created',
blueprintId,
timestamp: new Date(),
actor: this.getCurrentUserId(),
data: created
});
return created;
}
async updateTask(taskId: string, updates: Partial<Task>): Promise<Task> {
const task = await this.taskRepository.findById(taskId);
if (!task) throw new Error('Task not found');
const updated = await this.taskRepository.update(taskId, updates);
// Publish update event with before/after data
this.eventBus.publish({
type: 'task.updated',
blueprintId: task.blueprintId,
timestamp: new Date(),
actor: this.getCurrentUserId(),
data: updated,
metadata: {
before: task,
changes: updates
}
});
return updated;
}
}
Typed Event Publishing
// Define event types
interface TaskCreatedEvent extends BlueprintEvent<Task> {
type: 'task.created';
}
interface TaskAssignedEvent extends BlueprintEvent<{
task: Task;
assignee: string;
assigneeType: 'user' | 'team' | 'partner';
}> {
type: 'task.assigned';
}
// Publish with type safety
this.eventBus.publish<TaskCreatedEvent>({
type: 'task.created',
blueprintId: created.blueprintId,
timestamp: new Date(),
actor: this.getCurrentUserId(),
data: created
});
Subscribing to Events
Basic Subscription
import { Component, inject, signal, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { BlueprintEventBus } from '@core/services/blueprint-event-bus.service';
@Component({
selector: 'app-task-list',
template: `...`
})
export class TaskListComponent {
private eventBus = inject(BlueprintEventBus);
private destroyRef = inject(DestroyRef);
tasks = signal<Task[]>([]);
ngOnInit(): void {
// Subscribe to task.created events
this.eventBus.subscribe('task.created')
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(event => {
console.log('New task created:', event.data);
this.tasks.update(tasks => [...tasks, event.data]);
});
}
}
Filtered Subscription (Blueprint-specific)
import { filter } from 'rxjs/operators';
@Component({
selector: 'app-blueprint-tasks',
template: `...`
})
export class BlueprintTasksComponent {
private eventBus = inject(BlueprintEventBus);
private destroyRef = inject(DestroyRef);
blueprintId = input.required<string>();
tasks = signal<Task[]>([]);
ngOnInit(): void {
// Only listen to events in current Blueprint
this.eventBus.subscribe('task.created')
.pipe(
filter(event => event.blueprintId === this.blueprintId()),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(event => {
this.tasks.update(tasks => [...tasks, event.data]);
});
// Listen to updates
this.eventBus.subscribe('task.updated')
.pipe(
filter(event => event.blueprintId === this.blueprintId()),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(event => {
this.tasks.update(tasks =>
tasks.map(t => t.id === event.data.id ? event.data : t)
);
});
// Listen to deletions
this.eventBus.subscribe('task.deleted')
.pipe(
filter(event => event.blueprintId === this.blueprintId()),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(event => {
this.tasks.update(tasks =>
tasks.filter(t => t.id !== event.data.id)
);
});
}
}
Multiple Event Types
import { merge } from 'rxjs';
ngOnInit(): void {
// Listen to multiple event types
merge(
this.eventBus.subscribe('task.created'),
this.eventBus.subscribe('task.updated'),
this.eventBus.subscribe('task.deleted')
)
.pipe(
filter(event => event.blueprintId === this.blueprintId()),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(event => {
console.log('Task event:', event.type, event.data);
this.refreshTasks();
});
}
Common Use Cases
1. Audit Logging
@Injectable({ providedIn: 'root' })
export class AuditLogService {
private eventBus = inject(BlueprintEventBus);
private auditLogRepository = inject(AuditLogRepository);
constructor() {
// Listen to ALL events for audit trail
this.eventBus.subscribeAll()
.pipe(takeUntilDestroyed())
.subscribe(event => {
this.logEvent(event);
});
}
private async logEvent(event: BlueprintEvent): Promise<void> {
await this.auditLogRepository.create({
eventType: event.type,
blueprintId: event.blueprintId,
actor: event.actor,
timestamp: event.timestamp,
data: event.data,
metadata: event.metadata
});
}
}
2. Notification System
@Injectable({ providedIn: 'root' })
export class NotificationService {
private eventBus = inject(BlueprintEventBus);
private notificationRepository = inject(NotificationRepository);
constructor() {
// Listen to events that trigger notifications
this.setupNotificationListeners();
}
private setupNotificationListeners(): void {
// Task assigned → notify assignee
this.eventBus.subscribe('task.assigned')
.pipe(takeUntilDestroyed())
.subscribe(async event => {
await this.notifyUser(event.data.assignee, {
title: 'New Task Assigned',
message: `You have been assigned: ${event.data.task.title}`,
blueprintId: event.blueprintId
});
});
// Member added → notify member
this.eventBus.subscribe('member.added')
.pipe(takeUntilDestroyed())
.subscribe(async event => {
await this.notifyUser(event.data.userId, {
title: 'Added to Blueprint',
message: `You have been added to ${event.data.blueprintName}`,
blueprintId: event.blueprintId
});
});
}
}
3. Real-time UI Updates