Skip to main content

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 WriteFramework Handles
Command validationEvent persistence
Business rulesOptimistic concurrency
Event constructionSnapshot management
State reconstructionMessage 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:

illustrative - handler pattern
@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:

ApproachLines of Code
Raw implementation (no framework)2,000-5,000
Typical ES framework500-1,000
Angzarr50-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.

View the Player aggregate →