
Your whiteboard, shipped.
Protean is a Python framework for domain-driven systems. You sketch aggregates, events, and bounded contexts on a whiteboard, then write them in Python as you drew them.
Start with plain domain-driven design, move to CQRS or event sourcing where you need it, and change infrastructure through configuration.
It is built for ambitious systems: the ones whose shape you can't fully see on day one, and that have to grow safely instead of being rewritten.
Your domain model is the architecture.
Ship the Whiteboard Tutorial Why Protean? How Do I...?
Why Protean?
-
Domain compiler
Your domain model is a machine-readable specification. Protean builds an Intermediate Representation of it, which other tools read to derive docs, API specs, contracts, and visual exploration.
-
Always-valid domain
Domain objects are always valid, or they don't exist. Four layers of validation run on every change: field constraints, value object invariants, aggregate rules, and handler guards.
-
Progressive architecture
Start with domain-driven design, move to CQRS, adopt event sourcing, all within the same framework. You can mix patterns per aggregate.
-
Infrastructure portability
Start with in-memory adapters, so there is no database, broker, or setup to deal with. When you're ready, change
domain.tomlto point at PostgreSQL, Redis, Elasticsearch, or MessageDB. Your domain code stays as it is.
Read more about why Protean exists
See it in action
from protean import Domain, handle
from protean.fields import Identifier, String, Text
from protean.utils.globals import current_domain
domain = Domain() # (1)!
@domain.aggregate # (2)!
class Post:
title: String(max_length=100, required=True)
body: Text(required=True)
status: String(max_length=20, default="DRAFT")
def publish(self):
self.status = "PUBLISHED"
self.raise_(PostPublished(post_id=self.id, title=self.title)) # (3)!
@domain.event(part_of="Post") # (4)!
class PostPublished:
post_id: Identifier(required=True)
title: String(required=True)
@domain.command(part_of="Post") # (5)!
class CreatePost:
title: String(max_length=100, required=True)
body: Text(required=True)
@domain.command_handler(part_of=Post) # (6)!
class PostCommandHandler:
@handle(CreatePost)
def create_post(self, command: CreatePost):
post = Post(title=command.title, body=command.body)
current_domain.repository_for(Post).add(post) # (7)!
return post.id
- Domain. The central registry that wires all elements together.
- Aggregate. The core building block, holding fields and business logic.
- Raising an event.
raise_()emits a domain event to notify the rest of the system. - Event. An immutable record of something that happened in the domain.
- Command. An intent to change state, carrying the data needed to do it.
- Command handler. Receives a command, creates or updates aggregates, and persists them.
- Repository. The built-in persistence layer for adding, getting, and removing aggregates without touching the database directly.
Aggregates, commands, events, and handlers are all pure Python, with decorators that wire them together. You need no infrastructure to get started.
Choose your path
Protean supports three architectural approaches, and each builds on the one before it. Start simple and add sophistication as your needs change.
| Path | Best for | |
|---|---|---|
| Domain-Driven Design | Clean domain modeling, and the simplest way to start | |
| CQRS | Separating reads from writes with commands and projections | |
| Event Sourcing | A full audit trail, temporal queries, and event replay |
If you have no strong reason to choose, start with domain-driven design and evolve later. Choose a path compares them in detail.
To work out whether Protean suits your system at all, the Applicability Charter states plainly what it is built for, and the shapes of systems it is not.
Built to last
-
12,000+ tests
About three lines of test per line of code. Every commit runs against PostgreSQL, Redis, Elasticsearch, MessageDB, and SQL Server.
-
Zero lint violations
Clean Ruff linting and formatting, enforced on every commit through pre-commit hooks.
-
A-grade maintainability
95% of source files score in the highest maintainability tier, at an average cyclomatic complexity of 3.38.
-
12 adapters, 4 ports
Pluggable infrastructure across databases, brokers, event stores, and caches, tested on four Python versions.
Explore the documentation
-
Hello, Protean!
Define, save, and load your first aggregate in under 20 lines.
-
Quickstart
Commands, events, and handlers in 5 minutes.
-
Tutorial
A 22-chapter tutorial, from your first aggregate to running in production.
-
How Do I...?
A task-oriented index. Look up what you're trying to do and go straight to the right guide.
-
Guides
Step-by-step instructions for every task Protean supports.
-
Core concepts
Domain-driven design, CQRS, and event sourcing explained.
-
Adapters
PostgreSQL, SQL Server, Redis, Elasticsearch, MessageDB, and more.
-
Patterns & recipes
Recurring designs, with the trade-offs spelled out.
-
Upgrading?
Migration guides per version, covering required changes, behavioural differences, and what's new.