Skip to content

Protean

Your whiteboard, shipped.

Python Release Build Status Coverage Tests Maintainability

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.toml to 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
  1. Domain. The central registry that wires all elements together.
  2. Aggregate. The core building block, holding fields and business logic.
  3. Raising an event. raise_() emits a domain event to notify the rest of the system.
  4. Event. An immutable record of something that happened in the domain.
  5. Command. An intent to change state, carrying the data needed to do it.
  6. Command handler. Receives a command, creates or updates aggregates, and persists them.
  7. 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.

Full quality report


Explore the documentation

  • Hello, Protean!


    Define, save, and load your first aggregate in under 20 lines.

    Hello, Protean!

  • Quickstart


    Commands, events, and handlers in 5 minutes.

    Quickstart

  • Tutorial


    A 22-chapter tutorial, from your first aggregate to running in production.

    Tutorial

  • How Do I...?


    A task-oriented index. Look up what you're trying to do and go straight to the right guide.

    How Do I...?

  • Guides


    Step-by-step instructions for every task Protean supports.

    Guides

  • Core concepts


    Domain-driven design, CQRS, and event sourcing explained.

    Core concepts

  • Adapters


    PostgreSQL, SQL Server, Redis, Elasticsearch, MessageDB, and more.

    Adapters

  • Patterns & recipes


    Recurring designs, with the trade-offs spelled out.

    Patterns

  • Upgrading?


    Migration guides per version, covering required changes, behavioural differences, and what's new.

    Migration guides