From harness-claude
Runs event storming workshops to discover domain events, commands, aggregates, and bounded contexts. Useful for domain design, service boundaries, monolith-to-microservices migration, and event-sourced systems.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Run event storming workshops to discover domain events, commands, and bounded contexts.
Simulates event storming workshops with multi-persona agents to discover domain events, commands, actors, and bounded contexts. Use full-simulation, quick, or guided modes for domain modeling.
Guides advanced Event Storming for process, design, and software levels using color-coded sticky notes, facilitation steps, and Brandolini's methodology. For deep domain modeling.
Share bugs, ideas, or general feedback.
Run event storming workshops to discover domain events, commands, and bounded contexts.
Event storming process — Big Picture format:
Phase 1: Chaotic exploration (30-60 min)
Phase 2: Enforce timeline (20-30 min)
Phase 3: Identify pain points (10-15 min)
Phase 4: Commands (blue stickies)
Phase 5: Aggregates (yellow stickies)
Phase 6: Bounded contexts (pink/purple lines)
Event map template:
[Command] → [Aggregate] → [Domain Event] → [Read Model]
↓ ↓
[Policy/Rule] [External System]
Translating storming output to code:
// From event storming: "Order" aggregate with events
// Commands discovered: PlaceOrder, CancelOrder, ShipOrder
// Aggregate root
class Order {
private events: DomainEvent[] = [];
static place(data: PlaceOrderInput): Order {
const order = new Order(data);
order.record(new OrderPlaced({ orderId: order.id, ...data }));
return order;
}
cancel(reason: string): void {
if (this.status === 'SHIPPED') throw new Error('Cannot cancel shipped order');
this.status = 'CANCELLED';
this.record(new OrderCancelled({ orderId: this.id, reason }));
}
ship(trackingNumber: string): void {
if (this.status !== 'PAID') throw new Error('Order must be paid before shipping');
this.status = 'SHIPPED';
this.record(new OrderShipped({ orderId: this.id, trackingNumber }));
}
pullEvents(): DomainEvent[] {
const events = [...this.events];
this.events = [];
return events;
}
private record(event: DomainEvent): void {
this.events.push(event);
}
}
// Domain events discovered in storming
class OrderPlaced implements DomainEvent {
readonly type = 'order.placed';
constructor(public readonly payload: { orderId: string; userId: string; items: Item[] }) {}
}
class OrderCancelled implements DomainEvent {
readonly type = 'order.cancelled';
constructor(public readonly payload: { orderId: string; reason: string }) {}
}
class OrderShipped implements DomainEvent {
readonly type = 'order.shipped';
constructor(public readonly payload: { orderId: string; trackingNumber: string }) {}
}
Bounded context communication:
// Events that cross bounded context boundaries become integration events
// Integration event from Order context → Inventory context
interface OrderPlacedIntegrationEvent {
eventId: string;
type: 'order.placed';
occurredAt: string;
// Only include data the consuming context needs
orderId: string;
items: { productId: string; quantity: number }[];
}
// Inventory context maps this to its own domain language
class InventoryContext {
handleOrderPlaced(event: OrderPlacedIntegrationEvent): void {
// Translate to Inventory language: "stock reservation request"
const reservationRequest: StockReservationRequest = {
referenceId: event.orderId,
items: event.items.map((i) => ({
sku: this.mapProductToSKU(i.productId),
quantity: i.quantity,
})),
};
this.reserveStock(reservationRequest);
}
}
Sticky note color convention:
Bounded context warning signs:
Remote workshop tools: Miro, Mural, FigJam. Use virtual stickies with the same color coding. Allow async contribution before live sessions.
Transition to code: Each bounded context maps to:
Anti-patterns:
eventstorming.com/