Architecture
This guide describes the implementation in this checkout. The application profile records its purpose and ownership gaps; conventions define the rules for changing the design.
Components and boundaries
The composition root runs one Go process containing HTTP and gRPC servers, a shared authentication service, persistence and cache adapters, an event router, and background workers.
The HTTP adapters and gRPC adapters translate transport requests into service inputs. HTTP exposes signup, login, token refresh, logout, and user management; gRPC exposes user management. The OpenAPI contracts and Protobuf contract define those interfaces.
The authentication service owns business operations, sessions, and token behavior.
Feature domain/ packages hold service inputs and errors; models/ packages hold persistence models; repositories own
database access. Dependency interfaces live beside their consumers.
Source map
Use this map to find the owner of a behavior before editing it. Follow a request from its contract through the adapter, service, and repository; follow asynchronous effects through the outbox and subscriber.
| Concern | Source owner | Use it to locate |
|---|---|---|
| Composition and lifecycle | cmd/app | Dependency construction, registration, probes, and shutdown |
| API definitions | api/openapi and api/proto | HTTP and gRPC source contracts; generated output lives in api/gen/ |
| Authentication behavior | internal/auth | Service operations, transport adapters, authorization, models, and repository |
| Shared transport behavior | internal/api | Servers, middleware, interceptors, validation, and error conversion |
| Persistence and migrations | internal/database and migrate | Pools, transaction context, engine-specific schema and seed data |
| Asynchronous effects | internal/outbox and internal/events | Durable message records, publishing, retries, and broker adapters |
| Cache and configuration | internal/cache and internal/config | Cache adapters, settings, defaults, and validation |
| Operational signals | internal/metrics and infra | Metric helpers, database observers, dashboard, and deployment configuration |
| Behavioral evidence | Adjacent *_test.go files and tests | Unit cases, integration checks, resilience scenarios, and load scripts |
Edit source contracts and consumer interfaces before using the generation workflow. Generated clients, handlers, and mocks describe the generated interface; their source definitions own changes to it.
Requests and persistence
Services and worker handlers establish transaction boundaries through
WithTransaction. They pass the resulting context to all participating
repository calls so a business write and its required outbox message commit or roll back together.
Repositories use the primary database for writes and reads requiring immediate consistency. GetReadDB can use the
configured replica where lag is acceptable; within a transaction it uses that transaction. Backend selection and
persistence defaults are documented in configuration.
For signup and user creation, update, and deletion, the authentication service records the user change and its event in the same transaction. Login and logout record events on a best-effort basis after creating or revoking a session: an event write failure is logged and does not undo the successful authentication operation. These events therefore do not provide a complete audit guarantee for every operation.
Event delivery
The outbox service stores pending messages in the application's SQL database. The publisher polls due messages, sends them through the event router, and records successful publication. Transient failures remain pending with a bounded exponential retry delay; permanent failures are marked failed.
Publication and the database update that marks a message processed are separate operations. A message can be delivered again if publication succeeds but that update does not commit. Consumers must handle duplicate delivery. The authentication subscriber records user history and deduplicates inserts by event ID in its repository.
The router applies consumer retries and dead-letter handling. Outbox publication retries and consumer retries are
different stages: a processed outbox row establishes publication, while consumer completion must be observed separately.
The default gochan backend runs in process and is not durable. The none backend accepts and discards publication.
External broker options and provisioning settings are described in configuration.
Background work
| Worker | Responsibility |
|---|---|
| Outbox publisher | Publish pending messages and record retry or completion state |
| Outbox cleaner | Remove processed messages after retention; retain pending and failed messages |
| Authentication event subscriber | Turn authentication events into user history records |
| Token usage updater | Persist buffered token usage information |
| Session cleaner | Remove sessions according to the configured retention policy |
The workers are implemented under outbox and authentication. Their polling, batch, timeout, and retention settings are defined in the configuration model.
Process lifecycle
Startup loads and validates configuration, initializes dependencies, and applies migrations before serving requests. SQLite migrations use the application's database pool so an in-memory database survives migration initialization. MySQL and PostgreSQL migrations run against the primary before opening the application's pool.
The composition root registers event subscriptions before starting the router and the outbox publisher. It then starts HTTP and gRPC serving. Readiness includes database and cache checks; liveness tracks terminal serving and router failures. Broker connectivity and completion of individual messages are not readiness checks.
On a termination signal, the process cancels its application context, becomes unready, waits for probe propagation, and closes components within configured deadlines. A terminal server or router error fails liveness and readiness; the process relies on supervision for restart and still waits for a signal to enter shutdown. See operations for probes, shutdown settings, and failure investigation.
Evidence and open decisions
These flows are described by the current source and exercised by authentication unit tests, outbox publisher tests, and integration tests. Follow the development workflow to run the relevant suites.
Application-specific business boundaries, capacity targets, availability objectives, and production topology are still undecided. Record required outcomes in requirements and significant choices in decisions; update this guide when they change the implementation.
Source: docs/handbook/architecture.md. Revision: 3841815ceb9e.