Code Structure
Plumego’s repository structure is not accidental.
Every package boundary exists to enforce one or more guarantees:
- Explicit control flow
- Replaceability
- Minimal surface area
- Long-term maintainability
This document explains how the codebase is organized,
and what contributors must understand before changing it.
High-Level Repository Layout
At a high level, Plumego follows a small-core layout:
plumego/
├── app/
├── context/
├── router/
├── middleware/
├── response/
├── internal/
├── docs/
└── ...
Each top-level package has a single responsibility.
Cross-boundary dependencies are tightly controlled.
The Core Packages
Core packages define Plumego’s public runtime behavior.
They are intentionally small and slow to change.
app/
Responsibility:
- Application construction
- Middleware registration
- Route registration
- Composition root for the framework
Key constraints:
- No business logic
- No infrastructure assumptions
- No global state
- No environment access
The app package is the entry point into Plumego.
If a change alters how an application is assembled,
it belongs here — and must be reviewed very carefully.
context/
Responsibility:
- Request-scoped state
- Request and response access
- Minimal storage and helpers
Key constraints:
- One Context per request
- No global access
- No persistence beyond request lifecycle
- No business logic helpers
Context is a transport boundary, not a service container.
router/
Responsibility:
- Route registration
- Method + path matching
- Deterministic dispatch
Key constraints:
- No middleware logic
- No request mutation beyond routing needs
- No dynamic runtime behavior
- No auto-discovery
Routing must remain boring, predictable, and testable.
middleware/
Responsibility:
- Middleware contracts
- Middleware execution semantics
- Middleware chaining
Key constraints:
- Strict function signatures
- Synchronous execution
- Explicit
next()control flow - No hidden retries or forks
If middleware semantics change,
the entire framework behavior changes.
response/
Responsibility:
- Minimal response-writing helpers
- HTTP response consistency
Key constraints:
- Helpers only, no policy
- No global defaults
- No configuration dependency
Response helpers must never introduce behavior that
cannot be replicated manually.
internal/ Packages
The internal/ directory contains:
- Implementation details
- Helper types
- Optimizations
- Supporting utilities
Rules:
- Anything in
internal/is not public - It may change without notice
- External code must not import it
- Documentation does not guarantee its behavior
If something must be stable,
it does not belong in internal/.
Public vs Internal Boundaries
A critical rule for contributors:
Public APIs must never depend on internal types.
Allowed direction:
public → internal
Forbidden direction:
internal → public
Violating this rule leaks instability into the public surface.
Dependency Direction Rules
Plumego enforces a strict dependency direction:
app
├── middleware
├── router
├── context
└── response
Rules:
- Lower-level packages must not depend on higher-level ones
- No cyclic dependencies
- No cross-cutting shortcuts
If a dependency cycle appears,
the abstraction boundary is wrong.
Where New Code Should Live
Before adding a new file or package, ask:
- Is this behavior public and stable?
- Is it core runtime behavior?
- Does it introduce new semantics?
If any answer is no, it likely belongs:
- In
internal/ - In documentation
- In examples
- Or outside the core entirely
Most new code should not live in the core.
Adding New Packages (Rare)
Adding a new top-level package is a serious decision.
Requirements:
- Clear, unique responsibility
- No overlap with existing packages
- Justified long-term maintenance cost
- No violation of non-goals
New packages are reviewed more strictly than new features.
Common Structural Mistakes
1. Moving Logic “Just to Clean Things Up”
Refactors that:
- Increase abstraction
- Hide control flow
- Add indirection
Are often rejected unless they reduce long-term risk.
2. Introducing Shared Utility Packages
Generic “utils” packages often become dumping grounds.
Prefer:
- Specific helpers
- Localized usage
- Duplication over indirection
3. Letting Internal Helpers Leak
If users start depending on internal behavior,
the boundary has already failed.
Code Structure and Documentation
Code structure and documentation must agree.
If you change:
- A package responsibility
- A dependency direction
- An execution guarantee
You must update:
- Reference docs
- Architecture docs
- Possibly FAQ entries
Undocumented structural changes are not acceptable.
Testing and Structure
Tests should mirror structure:
- Core package tests → behavior guarantees
- Internal tests → implementation correctness
- No tests should rely on internal details indirectly
Tests that break due to refactors
often reveal hidden coupling.
A Contributor’s Mental Model
When modifying Plumego, always think:
“If someone reads this code two years from now,
will the structure help or hinder their understanding?”
Structure is not aesthetics.
It is operational memory.
Summary
Plumego’s code structure exists to:
- Make behavior obvious
- Prevent accidental coupling
- Enable safe evolution
- Protect public guarantees
As a contributor:
- Respect package boundaries
- Preserve dependency direction
- Avoid clever abstractions
- Prefer clarity over reuse
The structure is part of the contract.
Related Contributing Pages
- Design Principles — why these boundaries exist
- Non-Goals — what must not appear in the structure
- Contribution Workflow — how changes are reviewed
If you need to fight the structure to add a feature,
that feature probably does not belong in the core.