Files
core/docs/adr/ADR-001-core-module-composition.md

65 lines
3.3 KiB
Markdown
Raw Normal View History

feat(core): initial implementation — launcher, logz, xerrors, valid Introduces `code.nochebuena.dev/einherjar/core` — the foundational implementation module of the Einherjar framework. Provides four sub-packages that together cover every service's baseline needs: lifecycle management, structured logging, typed errors, and struct validation. - launcher: Launcher interface — three-phase managed lifecycle (OnInit → BeforeStart hooks → OnStart → OS signal wait → OnStop in reverse). Accepts lifecycle.Component and logging.Logger from contracts. Prints an ASCII art banner at startup (EINHERJAR_BANNER=off to suppress). Banner includes core version via runtime/debug.ReadBuildInfo() and a loaded-module list for every registered component that implements observability.Identifiable. Config struct with EINHERJAR_COMPONENT_STOP_TIMEOUT env tag (caarlos0/env syntax, default 15s). - logz: Logger implementation backed by log/slog. Returns contracts/logging.Logger. Detects errs.CodedError and errs.ContextualError (from contracts/errs) to enrich log records automatically — replaces the private duck-typed bridge from micro-lib. Context helpers: WithRequestID, WithField, WithFields, GetRequestID. Config struct with EINHERJAR_LOG_LEVEL (default INFO) and EINHERJAR_LOG_JSON (default false) env tags (caarlos0/env syntax); programmatic-only fields StaticArgs and Writer carry no tags. - xerrors: Typed error codes with context enrichment. Complete gRPC canonical set (16 codes) plus HTTP 410 ErrGone. Adds ErrOutOfRange, ErrAborted, ErrDataLoss over micro-lib. One convenience constructor per code. *Err declares compile-time satisfaction of errs.CodedError and errs.ContextualError. - valid: Struct validation wrapping go-playground/validator/v10. Validator interface + MessageProvider interface with full built-in tag coverage (~150 tags) in both DefaultMessages (English) and SpanishMessages (Spanish). Backend fully hidden; returns *xerrors.Err with ErrInvalidInput or ErrInternal. FieldLevel interface abstracts the backend's field-level access for custom validators. WithCustomValidator registers custom validation tags at construction time; OverrideProvider chains a tag→handler map with a fallback MessageProvider for custom tag messages without re-implementing built-ins. Compliance test enforces CT-6 (at most one exported TypeSpec per file via AST) and verifies behavioural correctness of all four sub-packages, including custom validator registration and OverrideProvider composition. Compile-time var _ assertions prove interface satisfaction. docs: ADR-001 (core module composition), ADR-002 (logz contracts/errs adoption), ADR-003 (Config naming convention and caarlos0/env tag standard)
2026-05-29 15:45:12 +00:00
# ADR-001: core module composition — four sub-packages in one Go module
- **Date:** 2026-05-28
- **Module:** `code.nochebuena.dev/einherjar/core`
- **Status:** Accepted
## Context
In micro-lib, the four packages that become `core` are independent Go modules:
- `code.nochebuena.dev/go/launcher`
- `code.nochebuena.dev/go/logz`
- `code.nochebuena.dev/go/xerrors`
- `code.nochebuena.dev/go/valid`
Each has its own `go.mod`, version tag, and release cycle. A dependency update to
`xerrors` requires a version bump on `xerrors`, then updating `valid` to consume the
new version, then informing all callers of both. Four packages → four coordinated
version bumps per change.
In the Einherjar module structure, each entry in the module inventory is one
independently versionable unit. The Spring Boot starter model places the
distribution boundary at the starter level (`db-postgres`, `cache-valkey`, `auth`),
not at the foundational implementation level.
## Decision
`launcher`, `logz`, `xerrors`, and `valid` are sub-packages of a single
`code.nochebuena.dev/einherjar/core` Go module.
## Rationale
**They always ship together.** Every Einherjar service needs all four:
- `launcher` requires a `logging.Logger` — it calls `logz.New` in the same main function
- `valid` returns `*xerrors.Err` — they are semantically coupled at the type level
- No known use case requires `logz` without `xerrors`, or `launcher` without `logz`
**Version coordination cost is real.** With four separate modules, a one-line fix to
`xerrors` becomes four coordinated operations: tag xerrors, update valid's go.mod,
tag valid, inform consumers. With one module, it is one operation.
**The Spring Boot analogy holds at the right level.** Spring Boot's parent POM bundles
dozens of interdependent libraries into a single versioned unit. Starters (`spring-boot-starter-data-jpa`) are the distribution boundary, not the individual `spring-data-commons` jar inside them. Einherjar's `core` is the parent; the starters are the distribution units.
## Alternatives Considered
**Four separate modules (`core-launcher`, `core-logz`, etc.).** Rejected — over-engineering at the foundational layer. There is no consumer that needs `logz` but not `xerrors`, or `launcher` but not `logz`. The additional version coordination overhead has no compensating benefit.
**One flat `core` package (no sub-packages).** Rejected — importing `core` would pull all four concerns into any module that only needs errors or validation. Sub-packages preserve the ability to import only what is needed.
## Consequences
**Easier:** A single `go get code.nochebuena.dev/einherjar/core@v1.x.x` installs
everything. A single version bump covers all four sub-packages. Dependency graph for
downstream starters stays simple.
**Harder:** A change to any sub-package bumps the entire `core` version, even if the
change is isolated to `valid`. This is the accepted trade-off — the coupling is real;
the version increment reflects it.
**New obligations:** Sub-packages within `core` may import each other in one direction:
`valid` may import `xerrors`; nothing else crosses package boundaries within `core`.
Circular imports between sub-packages are prohibited. `launcher` uses
`contracts/logging` — not `core/logz` directly — to preserve the contracts layer.