This will be a simple project that "simulate" a Expense Approval Engine workflow. The main objective here is apply all 9 concepts of Object Calisthenics.
It intentionally avoids frameworks, databases, and UI concerns. The focus is business rules, object collaboration, and code readability.
The main goal of this project is to:
-
Apply Object Calisthenics rules in a realistic domain
-
Model behavior instead of data
-
Build small, expressive, and immutable objects
-
Avoid anemic domain models and procedural logic
This is not a CRUD application.
Object Calisthenics is a set of programming rules proposed by Jeff Bay to encourage better object-oriented design through deliberate constraints.
Rather than focusing on frameworks or architectural patterns, Object Calisthenics emphasizes:
-
Object behavior over data structures
-
Encapsulation and responsibility-driven design
-
Readability through simplicity and discipline
The goal is not to follow the rules blindly, but to use constraints as a training tool to expose design flaws and improve object collaboration.
By applying these rules, developers are forced to:
-
Break down large classes into smaller, meaningful objects
-
Replace conditional logic with polymorphism and delegation
-
Avoid primitive obsession and anemic domain models
-
Write code that communicates intent clearly
This project applies Object Calisthenics intentionally and consistently as a way to explore clean domain modeling and object-oriented thinking.
Object Calisthenics is particularly effective in domain-driven projects because:
-
Business rules naturally map to object behavior
-
State transitions become explicit and controlled
-
Design mistakes surface early and are easier to correct
This repository treats Object Calisthenics not as a strict rulebook, but as a design lens to guide decisions and encourage high-quality code.
Each method represents a single business intention.
Complex logic is not nested. Instead, it is delegated to collaborating objects (Value Objects, Policies, Collections).
This keeps methods short, readable, and easy to reason about.
Decision paths are expressed through:
-
Guard clauses
-
Polymorphism
-
Domain objects answering questions about themselves
This avoids deeply nested conditionals and keeps decision logic explicit and linear.
Primitive values (strings, numbers, dates) are never used directly to represent domain concepts.
Examples:
-
Money instead of float
-
ExpenseStatus instead of str
-
Role instead of raw strings
This prevents primitive obsession and allows business rules to live next to the data they govern.
Collections are never exposed or manipulated directly.
Instead, they are wrapped inside dedicated domain objects that control:
-
validation
-
access
-
behavior
Example:
- ApprovalHistory encapsulates a list of ApprovalEntry objects and defines meaningful operations over it.
Objects communicate through behavior, not through navigation chains.
Instead of:
expense.history.entries[-1].approver_idThe model encourages:
expense.last_decision()This enforces proper encapsulation and reduces coupling.
Names favor clarity and intent over brevity.
Classes and methods are named after domain concepts, not technical patterns:
-
ApprovalPolicy
-
ApprovalEntry
-
ExpenseStatus
-
ApprovalHistory
If a name feels long, it usually means the concept is important.
Classes are intentionally small and focused.
Each object has:
-
one clear responsibility
-
a limited reason to change
If a class grows too large, it is treated as a design signal, not something to be worked around.
Objects do not expose internal state.
Instead, they expose meaningful behavior, such as:
-
is_pending()
-
can_approve()
-
has_rejected()
This prevents external code from bypassing domain rules and keeps invariants protected.
Objects are responsible for:
-
their own state
-
their own rules
-
their own state transitions
There are no passive data holders with logic living elsewhere.
Aggregates (like Expense) control their own lifecycle and coordinate with policies and value objects to enforce business rules.
On terminal:
python -m unittest tests/file.py