This walkthrough assumes you've read the layering rules — each step below maps to one of them.
Say you're adding publisher.
-
Describe the API surface. Add request/response shapes to
api/books/v1/openapi.yaml(for REST) or a new.protounderapi/<svc>/v1/(for ConnectRPC). -
Add a migration —
internal/database/pgdb/migrations/002_publishers.up.sqland the matching.down.sql. -
Write sqlc queries in
internal/database/pgdb/queries/publishers.sql. -
Run
just generateto regenerate API code anddbgenqueries. -
Create a data-access Service at
internal/database/publishers/service.gowith the usual shape:type Service struct { Postgres *pgdb.Postgres } func (s *Service) GetByID(ctx context.Context, id string) (*Publisher, error) { ... }
Every method bodies out as
s.Postgres.Queries(ctx, func(ctx, q) error { ... }). -
Create a handler at
internal/publishers/handler.gothat implements the relevant generated server interface and converts between domain and API types. -
Wire it in
internal/cmd/server/runner.go— construct the Service, construct the handler, register it on the mux. Keep the order: services first, handlers second, mux registration last. If the entity owns a resource that needs ordered teardown (a worker, a producer), add it to the shutdown sequence too — see Extending the shutdown sequence.
If you ever feel like adding a new layer to accommodate an entity, stop and question it. The layering rules are the contract.