Hello, Protean!
Define an aggregate, save it, load it — in under 20 lines of Python.
Prerequisites
- Python 3.11+
- Protean installed (Installation)
The Code
Create a file called hello.py:
from protean import Domain
from protean.fields import Boolean, String
domain = Domain()
@domain.aggregate
class Task:
title: String(max_length=100, required=True)
done: Boolean(default=False)
domain.init(traverse=False)
if __name__ == "__main__":
with domain.domain_context():
# Create
task = Task(title="Buy groceries")
print(f"Created: {task.title} (done={task.done})")
# Save
repo = domain.repository_for(Task)
repo.add(task)
# Load
saved = repo.get(task.id)
print(f"Loaded: {saved.title} (done={saved.done})")
print(f"ID: {saved.id}")
Run it:
$ python hello.py
Created: Buy groceries (done=False)
Loaded: Buy groceries (done=False)
ID: 5eb04301-f191-4bca-9e49-8e5a948f07f6
The ID will differ on your machine — Protean generates a unique identifier for every aggregate instance automatically.
What Just Happened?
Three things:
-
You defined an aggregate.
Taskis a domain concept — a cluster of data and rules treated as a single unit. The@domain.aggregatedecorator registers it with the domain. -
You saved it.
repository_for(Task)gives you a repository — a persistence abstraction. The default in-memory adapter stores everything in a dictionary. No database required. -
You loaded it back.
repo.get(task.id)retrieves the task by its auto-generated ID. Everything round-trips cleanly.
All of this ran in-memory. No database, no configuration, no boilerplate. When you are ready for a real database, you swap in an adapter through configuration — your domain code stays exactly the same.
Next Steps
Ready for more? The Quickstart builds a complete domain with commands, events, and handlers in 5 minutes.
Or dive into the Tutorial for a guided, chapter-by-chapter journey from aggregates to production.