Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Refactor Introduce Events

ASecurity

Introduce domain events to replace direct cross-aggregate calls, tight coupling, and synchronous side effects. Detects transaction boundary violations — handlers that modify multiple aggregates — and refactors them into event-driven flows with proper aggregate isolation. Use when the user says "introduce events", "add events", "decouple aggregates", "fix transaction boundary", "two aggregates in one handler", "make it event-driven", "break the coupling", or when an audit identifies transactio...

45 stars
0 votes
0 copies
0 views
Added 9/20/2026
developmentpythongoreactrefactoring

Works with

cli

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add proteanhq/protean --skill refactor-introduce-events --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Refactor Introduce Events?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Refactor Introduce Events
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/proteanhq-refactor-introduce-events/badge)](https://www.skillsdirectory.com/skills/proteanhq-refactor-introduce-events)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: refactor-introduce-events
description: >
  Introduce domain events to replace direct cross-aggregate calls, tight coupling, and synchronous
  side effects. Detects transaction boundary violations — handlers that modify multiple aggregates —
  and refactors them into event-driven flows with proper aggregate isolation. Use when the user
  says "introduce events", "add events", "decouple aggregates", "fix transaction boundary",
  "two aggregates in one handler", "make it event-driven", "break the coupling",
  or when an audit identifies transaction boundary violations or missing events.
license: Apache-2.0
compatibility: "Requires Python 3.11+, protean framework"
metadata:
  author: proteanhq
  version: "0.1"
  category: workflow
  composes: [event, event-handler, aggregate, command-handler]
---

# Refactor: Introduce Events

> **Illustrative, guided refactoring.** This is a before→after walkthrough, not an
> automated transform: recognize the smell and apply the change yourself, adapting to the
> code at hand. The `introduce_events_*_before.py` asset is the intentional starting point
> (cross-aggregate coupling); the `*_after.py` asset shows the event-driven target.

Replace direct cross-aggregate calls with domain events and event handlers, establishing
proper transaction boundaries and enabling eventual consistency.

## What this produces

| Output | Purpose |
|--------|---------|
| **Domain events** | `@domain.event` classes for each state change |
| **Event raises** | `self.raise_()` calls in aggregate methods |
| **Event handlers** | `@domain.event_handler` for cross-aggregate side effects |
| **Updated handlers** | Single-aggregate command handlers |

## Detection patterns

### Transaction boundary violation

The primary signal — a handler that modifies two or more aggregate types:

```python
# RED FLAG: two aggregates modified in one handler
@handle(PlaceOrder)
def place_order(self, command):
    order = Order.create(...)
    domain.repository_for(Order).add(order)

    # VIOLATION: second aggregate in same transaction
    inventory = domain.repository_for(Inventory).get(command.product_id)
    inventory.reduce(command.quantity)
    domain.repository_for(Inventory).add(inventory)
```

### Direct side effects

Operations that should be reactions to events but are coded as direct calls:

```python
# RED FLAG: side effects in command handler
@handle(CompleteOrder)
def complete_order(self, command):
    order = domain.repository_for(Order).get(command.order_id)
    order.complete()
    domain.repository_for(Order).add(order)

    # These should be event-driven reactions
    send_confirmation_email(order)
    update_analytics(order)
    notify_warehouse(order)
```

### Tight coupling between aggregates

Aggregate methods that reference other aggregates:

```python
# RED FLAG: aggregate knows about another aggregate
class Order:
    def place(self):
        self.status = "PLACED"
        # Aggregate should not access other aggregates
        inventory = domain.repository_for(Inventory).get(self.product_id)
        inventory.reduce(self.quantity)
```

## Process

### Step 1: Identify the boundary violation

Map which aggregates are modified in the handler:

```
place_order handler:
  ├── Order (create + persist)     ← Source aggregate
  └── Inventory (load + mutate + persist)  ← Should be event-driven
```

### Step 2: Define the domain event

Create an event representing what happened to the source aggregate:

```python
@domain.event(part_of="Order")
class OrderPlaced:
    order_id = Identifier(required=True)
    customer_id = String(required=True)
    product_id = String(required=True)
    quantity = Integer(required=True)
    total_amount = Float(required=True)
```

**Event naming**: past tense of what happened — `OrderPlaced`, `PaymentProcessed`, `TicketEscalated`.

**Event fields**: include everything the event handler needs. The handler shouldn't need to
look up the source aggregate to get context.

### Step 3: Raise the event in the source aggregate

```python
@domain.aggregate
class Order:
    def place(self, product_id: str, quantity: int, price: float) -> None:
        self.status = "PLACED"
        self.total = price * quantity
        self.raise_(
            OrderPlaced(
                order_id=self.id,
                customer_id=self.customer_id,
                product_id=product_id,
                quantity=quantity,
                total_amount=self.total,
            )
        )
```

### Step 4: Create an event handler for the target aggregate

```python
@domain.event_handler(
    part_of=Inventory,
    stream_category=Order.meta_.stream_category,
)
class OrderEventsHandler:
    @handle(OrderPlaced)
    def reserve_inventory(self, event: OrderPlaced) -> None:
        inventory = domain.repository_for(Inventory).get(event.product_id)
        inventory.reserve(event.quantity)
        domain.repository_for(Inventory).add(inventory)
```

Key points:
- `part_of` = the **target** aggregate (Inventory)
- `stream_category` = the **source** aggregate's stream (Order)
- Handler loads and mutates only its own aggregate

### Step 5: Slim down the command handler

Remove the cross-aggregate logic — the event handler takes over:

```python
@domain.command_handler(part_of=Order)
class OrderCommandHandler:
    @handle(PlaceOrder)
    def place_order(self, command: PlaceOrder) -> None:
        order = Order(customer_id=command.customer_id)
        order.place(
            product_id=command.product_id,
            quantity=command.quantity,
            price=command.unit_price,
        )
        domain.repository_for(Order).add(order)
        # Inventory update happens via OrderPlaced event handler
```

### Step 6: Configure sync processing for tests

```python
# In conftest.py or test setup
domain.config["event_processing"] = "sync"
```

This ensures events are processed inline during tests.

## Common mistakes

1. **Event too thin** — Including only the aggregate ID in the event forces the handler
   to look up the source aggregate, defeating the purpose. Include all data the handler needs.

2. **Event too fat** — Including the entire aggregate state in every event wastes space
   and couples consumers to the full schema. Include only what changed and context needed.

3. **Forgetting stream_category** — Without `stream_category=Source.meta_.stream_category`,
   the event handler won't receive events from the other aggregate.

4. **Returning values from event handlers** — Event handlers are fire-and-forget. They
   don't return values. If you need a return, use a command handler.

5. **Circular events** — A handles B's events, B handles A's events. This creates
   infinite loops. Design event flows as DAGs (directed acyclic graphs).

## Quick example

```python
# BEFORE: direct coupling
@handle(ShipOrder)
def ship_order(self, command):
    order = domain.repository_for(Order).get(command.order_id)
    order.ship(tracking_number=command.tracking)
    domain.repository_for(Order).add(order)
    # VIOLATION
    notification = domain.repository_for(Notification).get(order.customer_id)
    notification.add_message(f"Order {order.id} shipped!")
    domain.repository_for(Notification).add(notification)

# AFTER: event-driven
@handle(ShipOrder)
def ship_order(self, command):
    order = domain.repository_for(Order).get(command.order_id)
    order.ship(tracking_number=command.tracking)  # Raises OrderShipped
    domain.repository_for(Order).add(order)

@handle(OrderShipped)  # In separate event handler
def notify_customer(self, event):
    notification = domain.repository_for(Notification).get(event.customer_id)
    notification.add_message(f"Order {event.order_id} shipped!")
    domain.repository_for(Notification).add(notification)
```

## Detailed references

- [Event design guide](references/event-design-guide.md) — How to design good events
- [Anti-patterns](references/anti-patterns.md) — Common mistakes when introducing events

## Related skills

- [event](../event/SKILL.md) — Event definition patterns
- [event-handler](../event-handler/SKILL.md) — Event handler patterns
- [add-cross-aggregate-sync](../add-cross-aggregate-sync/SKILL.md) — Full cross-aggregate workflow
- [audit-domain](../audit-domain/SKILL.md) — Detects transaction boundary violations

Attribution

proteanhqproteanhq
View sourceMore from proteanhq →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →