122 lines
4.8 KiB
Markdown
122 lines
4.8 KiB
Markdown
|
|
# Einherjar
|
||
|
|
|
||
|
|
> *For those who come after.*
|
||
|
|
|
||
|
|
A Go framework built from battle-tested production code — organized for teams,
|
||
|
|
documented for the developer who was never in the room.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## The Name
|
||
|
|
|
||
|
|
In Norse mythology, the **Einherjar** are the chosen — warriors who fell in battle
|
||
|
|
and were carried to Valhalla by the Valkyries. There they train, day after day,
|
||
|
|
preparing for Ragnarök. Not for themselves. For what comes after.
|
||
|
|
|
||
|
|
This framework carries that name deliberately. Every interface defined here, every
|
||
|
|
architectural decision recorded, every module boundary enforced exists for the
|
||
|
|
developer who will build on this code without ever speaking to its authors. No
|
||
|
|
tribal knowledge. No implicit conventions. **The documentation is the system.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## What Is This
|
||
|
|
|
||
|
|
Einherjar is a collection of composable Go modules, each addressing one
|
||
|
|
infrastructure concern. The mental model is Spring Boot starters — not one framework
|
||
|
|
binary, but a curated set of independent modules with isolated dependency graphs.
|
||
|
|
|
||
|
|
Each module is its own `go.mod`. Import only what your service needs. A minimal CRUD
|
||
|
|
API requires exactly three: **`core` + `web` + one `db-*`**.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Modules
|
||
|
|
|
||
|
|
| Module | Purpose | Depends on |
|
||
|
|
|---|---|---|
|
||
|
|
| [`contracts`][contracts] | Pure interfaces. The constitution of the framework. | — |
|
||
|
|
| [`core`][core] | Lifecycle, logging, errors, validation | `contracts` |
|
||
|
|
| [`web`][web] | HTTP server, middleware, routing, health | `contracts` `core` |
|
||
|
|
| [`auth`][auth] | Auth middleware, RBAC | `contracts` `web` `core` |
|
||
|
|
| [`auth-jwt`][auth-jwt] | JWT signing, token pairs, refresh | `contracts` `auth` `core` |
|
||
|
|
| [`auth-firebase`][auth-firebase] | Firebase authentication | `contracts` `auth` `core` |
|
||
|
|
| [`db-postgres`][db-postgres] | PostgreSQL client | `contracts` `core` |
|
||
|
|
| [`db-mysql`][db-mysql] | MySQL client | `contracts` `core` |
|
||
|
|
| [`db-sqlite`][db-sqlite] | SQLite client | `contracts` `core` |
|
||
|
|
| [`cache-valkey`][cache-valkey] | Valkey / Redis-compatible cache | `contracts` `core` |
|
||
|
|
| [`storage-minio`][storage-minio] | Object storage | `contracts` `core` |
|
||
|
|
| [`telemetry`][telemetry] | OpenTelemetry — `main` packages only | `contracts` `core` |
|
||
|
|
| [`worker`][worker] | Concurrent background worker pool | `contracts` `core` |
|
||
|
|
| [`httpclient`][httpclient] | Resilient outbound HTTP client | `contracts` `core` |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Dependency Graph
|
||
|
|
|
||
|
|
```
|
||
|
|
contracts
|
||
|
|
└── core
|
||
|
|
├── web
|
||
|
|
│ └── auth
|
||
|
|
│ ├── auth-jwt
|
||
|
|
│ └── auth-firebase
|
||
|
|
├── db-postgres
|
||
|
|
├── db-mysql
|
||
|
|
├── db-sqlite
|
||
|
|
├── cache-valkey
|
||
|
|
├── storage-minio
|
||
|
|
├── telemetry ← import from main only
|
||
|
|
├── worker
|
||
|
|
└── httpclient
|
||
|
|
```
|
||
|
|
|
||
|
|
Data modules (`db-*`, `cache-*`, `storage-*`) never import `web`. Repositories do
|
||
|
|
not know HTTP exists. This boundary is absolute.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## The Law
|
||
|
|
|
||
|
|
`contracts` is the constitution of this framework — pure interfaces, zero
|
||
|
|
dependencies, permanent signatures. Changes flow **outward from `contracts`**, never
|
||
|
|
inward. Before any change to `contracts`, the blast radius is calculated: which
|
||
|
|
interfaces are affected, which modules implement them, which modules consume those.
|
||
|
|
Release sequence: `contracts` first, then implementors, then consumers.
|
||
|
|
|
||
|
|
Adding a method to a published interface is a breaking change in Go. It is not
|
||
|
|
permitted without a major version bump and coordinated updates across all
|
||
|
|
implementing modules.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## For Those Who Come After
|
||
|
|
|
||
|
|
Good code is not written for the machine. It is not even written for the deadline.
|
||
|
|
It is written for the person who will read it at 11pm six months from now, under
|
||
|
|
pressure, trying to understand what this thing does and why it was built this way.
|
||
|
|
|
||
|
|
Every ADR in this organization exists because a decision was made and the reasoning
|
||
|
|
behind it matters as much as the outcome. Every interface in `contracts` is as small
|
||
|
|
as it can be because the developer implementing it in a new context deserves to know
|
||
|
|
exactly what is required and nothing more.
|
||
|
|
|
||
|
|
This is the standard. Not the deadline.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
[contracts]: https://code.nochebuena.dev/einherjar/contracts
|
||
|
|
[core]: https://code.nochebuena.dev/einherjar/core
|
||
|
|
[web]: https://code.nochebuena.dev/einherjar/web
|
||
|
|
[auth]: https://code.nochebuena.dev/einherjar/auth
|
||
|
|
[auth-jwt]: https://code.nochebuena.dev/einherjar/auth-jwt
|
||
|
|
[auth-firebase]: https://code.nochebuena.dev/einherjar/auth-firebase
|
||
|
|
[db-postgres]: https://code.nochebuena.dev/einherjar/db-postgres
|
||
|
|
[db-mysql]: https://code.nochebuena.dev/einherjar/db-mysql
|
||
|
|
[db-sqlite]: https://code.nochebuena.dev/einherjar/db-sqlite
|
||
|
|
[cache-valkey]: https://code.nochebuena.dev/einherjar/cache-valkey
|
||
|
|
[storage-minio]: https://code.nochebuena.dev/einherjar/storage-minio
|
||
|
|
[telemetry]: https://code.nochebuena.dev/einherjar/telemetry
|
||
|
|
[worker]: https://code.nochebuena.dev/einherjar/worker
|
||
|
|
[httpclient]: https://code.nochebuena.dev/einherjar/httpclient
|