The Small Footprint Principle
Your aggregate handler is fifty lines. Your saga is twenty. The framework handles the other ten thousand.
The Problem
Event sourcing implementations typically require thousands of lines of infrastructure code: event persistence, snapshot management, optimistic concurrency, message bus integration, retry logic, dead letter handling. Teams spend months building plumbing before writing a single business rule.
And then they maintain it forever.
The Separation
Angzarr draws a surgical line between your code and framework code:
| You Write | Framework Handles |
|---|---|
| Command validation | Event persistence |
| Business rules | Optimistic concurrency |
| Event construction | Snapshot management |
| State reconstruction | Message bus integration |
| Retry and backoff | |
| Dead letter queues | |
| Schema evolution | |
| Cross-domain routing |
Your aggregate focuses on one job: given state and command, decide the outcome and produce events. No message bus configuration. No retry logic. No infrastructure concerns leaking into business decisions.
Aggregates may query external systems when they need additional context to decide on a command—checking inventory availability, validating with a payment provider, or fetching current exchange rates. This is one of the integration points where your DDD/CQRS-ES system connects with non-DDD systems, legacy applications, or third-party services. But the core responsibility remains: your aggregate decides the disposition of commands within its domain.
Note that aggregates should query external systems, not mutate them. Side effects to external systems belong in projectors, which react to committed events.
Why Small Matters
AI Assistance
Modern AI assistants have context windows. A 50-line aggregate fits comfortably. A 5,000-line framework integration doesn't.
When your business logic is small and isolated, AI can:
- Review entire handlers in one pass
- Suggest improvements with full context
- Generate tests that exercise real behavior
- Refactor without breaking infrastructure
Team Onboarding
New team members read your domain in an afternoon, not a month. The learning curve is business rules, not framework internals.
Code Review
Reviews focus on business logic correctness, not infrastructure bugs. When the aggregate is 50 lines, reviewers catch edge cases. When it's 5,000 lines, they skim.
The Pattern
Every aggregate handler follows guard → validate → compute:
@command_handler(DepositFunds)
def handle_deposit(cmd: DepositFunds, state: PlayerState, seq: int):
# Guard: check preconditions
if not state.exists:
raise CommandRejectedError("Player does not exist")
# Validate: check command inputs
if cmd.amount <= 0:
raise CommandRejectedError("Amount must be positive")
# Compute: produce event
return FundsDeposited(
amount=cmd.amount,
new_balance=state.bankroll + cmd.amount,
)
That's it. No persistence code. No retry logic. No message publishing.
Line Count Comparison
A realistic aggregate implementation:
| Approach | Lines of Code |
|---|---|
| Raw implementation (no framework) | 2,000-5,000 |
| Typical ES framework | 500-1,000 |
| Angzarr | 50-200 |
The difference compounds across a system with dozens of aggregates.
See It In Action
The poker example demonstrates this principle across six languages. Each aggregate handler is small enough to read in minutes.