Architecture
Oridecon’s most important design decision isn’t a feature — it’s a boundary rule enforced across every package. Understanding it explains why the framework stays coherent as it grows from two packages to dozens.
1. Three Layers, One Direction
Section titled “1. Three Layers, One Direction”graph TB
subgraph L1["oridecon-contracts — zero dependencies"]
P[Protocols]
T[Types & Value Objects]
E[Exceptions]
end
subgraph L2["oridecon — depends only on contracts"]
C[Container / DI]
A[Application & Lifecycle]
M[Modules & Providers]
end
subgraph L3["oridecon-* extensions"]
W[oridecon-web]
S[oridecon-sql]
AI[oridecon-ai-*]
More[the rest of the catalog]
end
L1 --> L2
L2 --> L3
L1 --> L3
| Layer | May depend on | Never depends on |
|---|---|---|
oridecon-contracts | nothing | anything |
oridecon (core) | oridecon-contracts | any extension |
oridecon-* (extension) | oridecon + oridecon-contracts | another extension |
The dependency arrows only point downward. Contracts never import implementations; core never imports an extension; and — the rule that does the most work — extensions never import each other.
2. Why “Extensions Never Import Each Other”
Section titled “2. Why “Extensions Never Import Each Other””This single constraint is what makes packages genuinely pluggable.
oridecon-sqldoesn’t importoridecon-cache. If a SQL feature wants caching, it depends onCacheBackendProtocol(a contract), and the container injects whatever cache implementation is registered — Redis, in-memory, or a test fake.- Swap without ripple. Because dependencies are expressed as protocols in
oridecon-contracts, replacing one implementation never forces a change in another package. - Install à la carte. You can install
oridecon-webwithout pulling inoridecon-ai-llm, and vice versa. There is no hidden web of inter-package coupling.
When two extensions genuinely need to collaborate, they do it through a shared contract in oridecon-contracts, not a direct import. Documented exceptions (admin, AI/multimedia orchestrators, testing) live on Compatibility.
3. The application tree (one layout)
Section titled “3. The application tree (one layout)”The package graph above is not the application tree. oridecon new project writes one shape. Templates add packages, not a second layout. There is no --structure flag and no models/ directory.
| Kind | Where it lives |
|---|---|
| Composition root | src/<app>/app.py — create_app(), ASGI target <app>.app:app |
| Unscoped domain types | src/<app>/domains/ |
| App providers | src/<app>/di/*_provider.py |
| Module provider | src/<app>/modules/<slug>/provider.py |
| Cross-cutting | src/<app>/shared/ |
If a skill or the CLI dump disagrees, this site wins. Full map: Project Structure.
4. The Namespace Package Layout
Section titled “4. The Namespace Package Layout”All packages publish into the shared oridecon import namespace (a PEP 420 namespace package), even though they are separate distributions:
oridecon-web/ → src/oridecon/web/ → import: from oridecon.web import ...oridecon-sql/ → src/oridecon/sql/ → import: from oridecon.sql import ...oridecon-ai/ → src/oridecon/ai/ → import: from oridecon.ai import ...So installing the oridecon-web distribution gives you the oridecon.web module. One consistent import root; many independently versioned packages underneath.
from oridecon import Application, Provider # corefrom oridecon.web import WebModule, get # oridecon-web distributionfrom oridecon.contracts.core.di import BootContainerProtocol # contracts5. How Extensions Plug In
Section titled “5. How Extensions Plug In”An extension contributes to an application in three ways, all built on the core primitives:
| Mechanism | Role | Covered in |
|---|---|---|
| Provider | Registers the extension’s services in the container and manages their lifecycle | Providers |
| Module | Bundles providers with import/export boundaries; usually exposes configure() | Modules |
| Contract | The protocol(s) the extension implements or depends on, defined in oridecon-contracts | Container Protocols |
Most extensions ship a configure() classmethod on their module so you add them in one line. The scaffold uses modules — create_app() in src/<app>/app.py:
from oridecon import Applicationfrom oridecon.sql import DatabaseModulefrom oridecon.web import WebModule
app = Application(name="my-app")app.add_module(DatabaseModule.configure()) # reads sql: from application.yamlapp.add_module(WebModule.configure(discover=["my_app.controllers", "my_app.modules"]))WebModule.configure() constructs a WebProvider internally. You rarely add that provider by hand.
Boot order follows provider priority, so infrastructure (database, cache) is ready before the web layer starts serving. Full tree: Project Structure.
6. What This Buys You
Section titled “6. What This Buys You”| Property | How the boundary rule delivers it |
|---|---|
| Testability | Depend on contracts → substitute fakes from oridecon-testing with no production code change. |
| Replaceability | Swap Redis for Memcached, Postgres for SQLite, one LLM provider for another — through config, not refactors. |
| Incremental adoption | Start with two packages; add extensions one at a time without untangling dependencies. |
| Clear ownership | Each package has one purpose and a well-defined surface; large teams can own packages independently. |
Next Steps
Section titled “Next Steps”- Core Concepts — providers, DI, modules, and the Result type in one place
- Project Structure — the tree generators write
- The Ecosystem — the full set of extensions and what each one does
- Container Protocols — the type-safe contracts at the heart of the boundary
- Compatibility — extras, drivers, documented exceptions