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

Event Handler

ASecurity

Define a Protean event handler - a class that consumes domain events raised by aggregates and orchestrates side effects such as syncing state across aggregates, sending notifications, or triggering downstream processes. Event handlers are always associated with an aggregate via part_of and use the @handle decorator to process specific event types. They follow a fire-and-forget pattern and do NOT return values. Also covers cross-aggregate synchronization - coordinating state between aggregates...

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

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add proteanhq/protean --skill event-handler --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Event Handler?

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

Security grade badge for Event Handler
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/proteanhq-event-handler/badge)](https://www.skillsdirectory.com/skills/proteanhq-event-handler)

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

Download Zip
Files
SKILL.md
---
name: event-handler
description: Define a Protean event handler - a class that consumes domain events raised by aggregates and orchestrates side effects such as syncing state across aggregates, sending notifications, or triggering downstream processes. Event handlers are always associated with an aggregate via part_of and use the @handle decorator to process specific event types. They follow a fire-and-forget pattern and do NOT return values. Also covers cross-aggregate synchronization - coordinating state between aggregates via events while maintaining one-aggregate-per-transaction boundaries. Use when you need to react to a domain event, handle an event, process events from another aggregate, sync state between aggregates, implement eventual consistency, add notifications or side effects for domain events, or when the user asks to "create an event handler", "handle an event", "react to an event", "sync aggregates", "add a listener for events", "update another aggregate when X happens", "add cross-aggregate coordination", "when order ships reduce inventory", or "add an event-driven sync".
license: Apache-2.0
compatibility: Requires Python 3.11+, protean framework
metadata:
  author: proteanhq
  version: "0.1"
  category: element
---

# Event Handler

## Basic structure

```python
from protean import Domain, handle
from protean.fields import Identifier, String

domain = Domain()
domain.config["event_processing"] = "sync"

@domain.event(part_of="Order")
class OrderPlaced:
    order_id: Identifier(required=True)

@domain.aggregate
class Order:
    order_id: Identifier(identifier=True)
    status: String(default="draft")

    def place(self):
        self.status = "placed"
        self.raise_(OrderPlaced(order_id=self.order_id))

@domain.event_handler(part_of=Order)
class OrderEventHandler:
    @handle(OrderPlaced)
    def on_order_placed(self, event: OrderPlaced):
        repo = domain.repository_for(Order)
        order = repo.get(event.order_id)
        order.confirmation_number = f"CONF-{event.order_id}"
        repo.add(order)
```

## Key rules

1. **part_of or stream_category required** - Handler must be associated with an aggregate or stream: `@domain.event_handler(part_of=Order)`
2. **Use @handle decorator** - Each handler method is decorated with `@handle(EventClass)` or `@handle("$any")`
3. **Handler methods take self and event** - Signature: `def method_name(self, event: EventClass)`
4. **No return values** - Event handlers do NOT return values (CQRS pattern, fire-and-forget)
5. **Implicit UnitOfWork** - Each handler method runs within a UnitOfWork context automatically
6. **Multiple handlers per event** - Unlike commands, multiple event handlers can process the same event
7. **Import handle from protean** - `from protean import handle` (not from protean.core)
8. **part_of uses class reference** - Handler uses `part_of=AggregateClass` (not a string). Define the aggregate before the handler so the class resolves; unlike events/commands, event handlers do **not** accept a string `part_of` (it raises at registration)
9. **stream_category for cross-aggregate** - Use `stream_category=OtherAggregate.meta_.stream_category` to listen to another aggregate's events

## Handler options

| Option | Purpose | Required |
|--------|---------|----------|
| `part_of` | Associate handler with an aggregate class | Yes (unless stream_category provided) |
| `stream_category` | Stream to listen to, use `Aggregate.meta_.stream_category` (defaults to own aggregate's stream) | No |
| `source_stream` | Origin stream filter | No |
| `subscription_type` | "stream" or "event_store" | No |
| `subscription_profile` | "production", "fast", "batch", "debug", "projection" | No |
| `subscription_config` | Custom config dict (messages_per_tick, max_retries, etc.) | No |

## Quick example: Cross-aggregate handler

```python
@domain.event_handler(part_of=Inventory, stream_category=Order.meta_.stream_category)
class ManageInventory:
    @handle(OrderShipped)
    def reduce_stock_level(self, event: OrderShipped):
        repo = domain.repository_for(Inventory)
        inventory = repo._dao.find_by(book_id=event.book_id)
        inventory.in_stock -= event.quantity
        repo.add(inventory)
```

## Quick example: Multiple events

```python
@domain.event_handler(part_of=Notification, stream_category=Account.meta_.stream_category)
class AccountNotifier:
    @handle(AccountRegistered)
    def on_registered(self, event: AccountRegistered):
        notification = Notification(message=f"Welcome {event.name}!")
        domain.repository_for(Notification).add(notification)

    @handle(AccountSuspended)
    def on_suspended(self, event: AccountSuspended):
        notification = Notification(message=f"Account suspended: {event.reason}")
        domain.repository_for(Notification).add(notification)
```

## Quick example: Catch-all ($any) handler

```python
@domain.event_handler(part_of=AuditLog, stream_category=Task.meta_.stream_category)
class TaskAuditor:
    @handle("$any")
    def on_any_event(self, event):
        audit = AuditLog(event_type=event.__class__.__name__)
        domain.repository_for(AuditLog).add(audit)
```

## Error handling

Override `handle_error` classmethod for custom error recovery during async processing:

```python
@domain.event_handler(part_of=ShipmentLog, stream_category=Shipment.meta_.stream_category)
class ShipmentNotifier:
    @handle(ShipmentDispatched)
    def on_dispatched(self, event):
        # ... handler logic ...
        pass

    @classmethod
    def handle_error(cls, exc: Exception, message) -> None:
        logger.error(f"Shipment event failed: {exc}")
```

## Cross-aggregate sync pattern

The primary use case for event handlers is coordinating state changes between aggregates
while maintaining the **one aggregate per transaction** rule.

### The principle

Never modify two aggregates in the same handler. Instead:

1. **Aggregate A** raises a domain event when its state changes
2. An **event handler** (belonging to Aggregate B) listens to that event
3. The handler loads **Aggregate B** and updates its state
4. This happens in a **separate transaction** (eventual consistency)

| Approach | Correct? | Why |
|----------|----------|-----|
| Handler modifies source + target in one call | No | Two aggregates in one transaction |
| Source raises event, handler on target reacts | Yes | Each handler = one aggregate transaction |
| Command handler loads and modifies two aggregates | No | Violates consistency boundary |

### Designing for eventual consistency

- **Include enough data in events** — The handler should not need to load the source aggregate
- **Make handlers idempotent** — Processing the same event twice should produce the same result
- **Accept brief staleness** — UI might show slightly outdated data
- Use `domain.config["event_processing"] = "sync"` for deterministic testing

### Common patterns

```
Order.ship()    → OrderShipped    → InventoryHandler    → Inventory.reduce_stock()
Payment.confirm() → PaymentConfirmed → SubscriptionHandler → Subscription.activate()
Task.assign()   → TaskAssigned    → WorkloadHandler     → TeamMember.increment_count()
```

### Building a cross-aggregate handler

```python
# 1. Event on source aggregate
@domain.event(part_of="Order")
class OrderShipped:
    order_id = Identifier(required=True)
    product_id = Identifier(required=True)
    quantity = Integer(required=True)

# 2. Source aggregate raises event
@domain.aggregate
class Order:
    def ship(self):
        self.status = "shipped"
        self.raise_(OrderShipped(
            order_id=self.id,
            product_id=self.product_id,
            quantity=self.quantity,
        ))

# 3. Handler on target aggregate listens to source stream
@domain.event_handler(
    part_of=Inventory,
    stream_category=Order.meta_.stream_category,
)
class InventorySyncHandler:
    @handle(OrderShipped)
    def on_order_shipped(self, event: OrderShipped):
        inventory = domain.repository_for(Inventory).get(event.product_id)
        inventory.reduce_stock(event.quantity)
        domain.repository_for(Inventory).add(inventory)
```

## Common mistakes

### Missing part_of and stream_category

```python
@domain.event_handler  # Wrong! Missing both part_of and stream_category
class OrderEventHandler:
    pass
```

Instead: Always specify at least part_of or stream_category

```python
@domain.event_handler(part_of=Order)  # Correct!
class OrderEventHandler:
    pass
```

### Returning values from event handlers

```python
@handle(OrderPlaced)
def handle(self, event):
    return order  # Wrong! Return value is discarded
```

Instead: Update aggregates or emit new events to communicate results

### Manually wrapping in UnitOfWork

```python
@handle(OrderPlaced)
def handle(self, event):
    with UnitOfWork():  # Unnecessary! Already implicit
        inventory = domain.repository_for(Inventory).get(event.id)
        domain.repository_for(Inventory).add(inventory)
```

Instead: Let the implicit UnitOfWork handle it

### Business logic in the handler

```python
@handle(OrderShipped)
def reduce_stock(self, event):
    inventory = domain.repository_for(Inventory).get(event.id)
    if inventory.in_stock < event.quantity:  # Business logic leak!
        raise ValueError("Not enough stock")
    inventory.in_stock -= event.quantity
    domain.repository_for(Inventory).add(inventory)
```

Instead: Keep business logic in the aggregate, handler only orchestrates

## Detailed references

### Core Concepts
- [Same-Aggregate Handling](references/same-aggregate.md) - Handler processes events from its own aggregate
- [Cross-Aggregate Handling](references/cross-aggregate.md) - Handler listens to another aggregate's events
- [Eventual Consistency](references/eventual-consistency.md) - Trade-offs and guarantees
- [Cross-Aggregate Patterns](references/cross-aggregate-patterns.md) - Common sync scenarios
- [Error Handling](references/error-handling.md) - Custom error recovery with handle_error
- [Catch-All ($any) Handler](references/any-handler.md) - Processing any event on the stream
- [Anti-patterns](references/anti-patterns.md) - Common mistakes and how to avoid them

### Complete Examples
- [Same-Aggregate Handler](assets/event_handler_same_aggregate.py) - Handler for its own aggregate's events
- [Cross-Aggregate Handler](assets/event_handler_cross_aggregate.py) - Inventory reacts to Order events
- [Order-Inventory Sync](assets/cross_sync_order_inventory.py) - OrderShipped reduces Inventory stock
- [Payment-Subscription Sync](assets/cross_sync_payment_subscription.py) - PaymentConfirmed activates Subscription
- [Multi-Event Sync](assets/cross_sync_multi_event.py) - Multiple events from one source trigger different target updates
- [Multiple Events](assets/event_handler_multiple_events.py) - Handler with multiple @handle methods
- [Error Handling](assets/event_handler_error_handling.py) - Custom handle_error classmethod
- [Catch-All Handler](assets/event_handler_any_event.py) - @handle("$any") for all events

### Related Skills
- [event](../event/SKILL.md) - Events are the input to event handlers
- [aggregate](../aggregate/SKILL.md) - Event handlers are always connected to aggregates
- [command-handler](../command-handler/SKILL.md) - Analogous pattern for commands (compare/contrast)
- [refactor-introduce-events](../refactor-introduce-events/SKILL.md) - Refactoring direct calls into event-driven flows

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 →