Docs Code Structure

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:

  1. Is this behavior public and stable?
  2. Is it core runtime behavior?
  3. 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.


  • 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.