Skip to content
Spice Framework on GitHub

ADR 0008: Import-path application modules

frameworkMaturity: previewSource: spice@c0641b3Exact reviewed source

Status: Accepted

Context

Go already gives every package a stable identity, import rules, and an inspectable dependency graph. Spice needs Modulith-style boundaries without inventing a second naming system or requiring runtime package scanning.

Decision

A package-documentation @Module annotation makes that package the root of an application module. The module ID is the root package’s full Go import path. The root package is the default public API. Descendant packages belong to the longest matching module root and are internal unless their package documentation declares a named interface.

@NamedInterface("name") is repeatable. Names use portable lowercase identities matching ^[a-z][a-z0-9-]*$. A named interface exposes its entire package; declaration-level export sets are outside this contract.

@Module accepts an optional list:

// @Module(allowedDependencies=["example.com/shop/inventory", "example.com/shop/payments::spi"])

The source syntax places one annotation invocation on one comment line. A plain import path selects the target module’s root-package API. module::name selects a named interface. References are exact: short names and suffix matching are not accepted.

Packages in a Go module that contains selected @Module roots but are not descendants of any root are reported as unassigned. Nested module roots are allowed and take ownership of their own descendants.

Boundaries

Discovery consumes the existing loaded program and resolved annotations. It does not reload source, execute code, inspect the filesystem, or infer modules from directory names alone.

Every selected Go import between different discovered modules becomes one distinct architecture edge. Imports of another module’s root API or named interface require an exact allowed-dependency entry. Other descendant imports are rejected as internal. Strongly connected module components produce stable member sets and representative closed paths.

spice modules --format=json|mermaid|plantuml renders this model without writing files. JSON contains complete portable canvases; diagram formats use stable synthetic node IDs and aggregate repeated package imports by module API. --focus=<module-import-path> retains the selected module and its transitively observed dependencies, excluding dependents, unrelated modules, unassigned packages, and allowed-but-unused dependencies. JSON records dependency-first composition order, and diagrams highlight the selected module.

The same ownership model labels generated provider cleanup and lifecycle-hook metadata. The runtime coordinator emits synchronous module-aware callback observations through explicitly registered observers; it does not choose or initialize a tracing/metrics implementation.

Consequences

  • Module identities remain unique wherever Go import paths are unique.
  • Refactors across module roots are explicit dependency-contract changes.
  • Root packages are intentionally small public facades.
  • Descendant packages are fail-closed by default.
  • Unassigned packages remain visible rather than silently becoming modules.