A Hatmax application separates product behavior from infrastructure while keeping their assembly visible. The goal of this chapter is to locate each responsibility before examining individual packages.
An idiomatic application commonly has this shape:
main.go
config.yaml
assets/
migration/postgres/
static/
templates/
db/
queries/
internal/
dal/
feat/
<feature>/
model.go
store.go
postgres_store.go
service.go
handler.go
*_test.go
Not every application needs every directory. The boundaries matter more than the directory count: the entrypoint assembles, features own behavior, adapters own external details, and assets remain explicit inputs.
main.go is the composition root. It contains one main function and no
feature behavior. That function performs the visible assembly sequence:
- load and validate static configuration;
- create the logger, context, and router;
- construct infrastructure components;
- construct feature stores, services, and handlers;
- order lifecycle and route components;
- start the application and serve HTTP;
- coordinate cancellation and reverse-order shutdown.
Constructors receive dependencies and lightweight configuration. They do not open connections, run migrations, parse templates, or start background work. Those fallible actions belong to explicit lifecycle methods.
A value becomes an application component by implementing one or more Hatmax lifecycle interfaces:
app.Startableparticipates in ordered startup;app.Stoppableparticipates in rollback and shutdown;app.RouteRegistrarcontributes routes after startup succeeds.
Plain domain services do not need to implement these interfaces. The
entrypoint constructs them explicitly and passes them to the components that
use them. Only lifecycle and route responsibilities enter app.Setup.
A feature owns one cohesive application capability. Its normal dependency direction is:
handler -> service -> model
|
store interface <- Postgres adapter
The handler owns HTTP and rendering decisions. The service owns application workflows. The model owns durable domain invariants. The store interface is owned by its consumer, while the Postgres implementation owns queries and mapping details.
Templates belong to the feature presentation surface, even when embedded from
the application-level assets/ tree. Tests stay beside the layer whose
behavior they protect.
The later Feature Anatomy chapter develops this flow in detail. At this stage, the important point is that a feature is a complete vertical responsibility, not only a handler or database table.
Infrastructure components serve features without absorbing their product rules. Typical shared components include:
- the database connection and migrator;
- the template manager;
- authentication and session services;
- the event broker and scheduled job store;
- mail, image, and telemetry adapters.
Features depend on the smallest interfaces they need. Concrete adapters remain visible at the composition root, which keeps substitution and startup order reviewable.
To understand an unfamiliar Hatmax application, read it in this order:
- inspect
main.goto see the selected capabilities and their order; - inspect one feature from handler through service and model;
- identify the store interface and concrete adapter;
- inspect templates and routes for the user-visible behavior;
- inspect tests for the protected contracts;
- use the Hatmax reference for exact shared-package behavior.
This order reveals the product architecture without treating generated files, configuration, or framework conventions as hidden control flow.
Previous: Orientation · User Guide · Next: Lifecycle and Wiring