When this skill is active, follow this 7-step discipline when building a Go service:
1. Project Structure
Organize the service using standard Go project layout:
cmd/<service>/main.go — entry point, wiring only, no business logic
internal/ — private application code: internal/handler/, internal/service/, internal/repo/
pkg/ only for code genuinely intended for external consumption (rare)
- Keep
go.mod clean: go mod tidy after every dependency change
2. Dependency Injection
Wire dependencies explicitly in main.go — no global state, no init() functions:
- Draw the dependency graph before coding: which service depends on which repository, logger, or config?
- Constructor injection only:
func NewUserService(repo UserRepo, logger *slog.Logger) *UserService
func main() is the composition root — construct all dependencies, wire them together, then start
- Verify testability: every constructor should accept interfaces, making it possible to pass mocks in tests
3. Structured Logging with slog
Use log/slog (standard library) for all logging:
- Create the logger once in
main.go: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
- Pass the logger as a dependency — never use the global
slog.Default()
- Log with structured fields:
logger.Info("user created", "user_id", id, "email", email)
- Use
logger.With("request_id", reqID) to create request-scoped loggers in middleware
4. Graceful Shutdown
Handle termination signals so in-flight requests complete:
- Listen for
os.Interrupt and syscall.SIGTERM with signal.NotifyContext
- Call
server.Shutdown(ctx) with a timeout context (e.g., 15 seconds)
- Close database connections, flush logs, and release resources in deferred cleanup
- Log shutdown progress: "shutting down", "connections drained", "shutdown complete"
5. Error Handling Strategy
Design the error flow across layers before implementing:
- Map every error to an HTTP status in the handler layer — business logic returns domain errors, handlers translate
- Wrap with context at each layer boundary:
fmt.Errorf("userService.Create: %w", err) — the chain reads like a call stack
- List sentinel errors upfront:
var ErrNotFound, var ErrConflict, etc. — define them before writing the code that returns them
- Log once, at the top: handlers log the full error chain; lower layers wrap and propagate, never log
6. Testing with Race Detection
Write tests that catch concurrency bugs:
- Always run with
-race: go test -race ./... — make this the default in CI and local dev
- Test each layer independently:
httptest.NewServer for handlers, mock interfaces for services, real DB for repos
- Stress-test concurrent paths: launch N goroutines hitting the same endpoint and assert no races or data corruption
- Measure coverage on critical paths:
go test -coverprofile=cover.out ./internal/service/ — review uncovered branches
7. Verify Before Shipping
Run the full verification chain before declaring the service ready:
go vet ./... — catch common mistakes
golangci-lint run — comprehensive lint check
go test -race -count=1 ./... — all tests pass with race detection, no caching
go build ./cmd/<service> — binary compiles cleanly
- If any step fails, fix and re-run the entire chain
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: go-service3description: Production Go service construction. Covers project structure, dependency injection, structured logging, graceful shutdown, and race testing. Use when this capability is needed.4---56When this skill is active, follow this 7-step discipline when building a Go service:78## 1. Project Structure910Organize the service using standard Go project layout:11- `cmd/<service>/main.go` — entry point, wiring only, no business logic12- `internal/` — private application code: `internal/handler/`, `internal/service/`, `internal/repo/`13- `pkg/` only for code genuinely intended for external consumption (rare)14- Keep `go.mod` clean: `go mod tidy` after every dependency change1516## 2. Dependency Injection1718Wire dependencies explicitly in `main.go` — no global state, no `init()` functions:19- **Draw the dependency graph** before coding: which service depends on which repository, logger, or config?20- **Constructor injection only**: `func NewUserService(repo UserRepo, logger *slog.Logger) *UserService`21- **`func main()` is the composition root** — construct all dependencies, wire them together, then start22- **Verify testability**: every constructor should accept interfaces, making it possible to pass mocks in tests2324## 3. Structured Logging with slog2526Use `log/slog` (standard library) for all logging:27- Create the logger once in `main.go`: `slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))`28- Pass the logger as a dependency — never use the global `slog.Default()`29- Log with structured fields: `logger.Info("user created", "user_id", id, "email", email)`30- Use `logger.With("request_id", reqID)` to create request-scoped loggers in middleware3132## 4. Graceful Shutdown3334Handle termination signals so in-flight requests complete:35- Listen for `os.Interrupt` and `syscall.SIGTERM` with `signal.NotifyContext`36- Call `server.Shutdown(ctx)` with a timeout context (e.g., 15 seconds)37- Close database connections, flush logs, and release resources in deferred cleanup38- Log shutdown progress: "shutting down", "connections drained", "shutdown complete"3940## 5. Error Handling Strategy4142Design the error flow across layers before implementing:43- **Map every error to an HTTP status** in the handler layer — business logic returns domain errors, handlers translate44- **Wrap with context at each layer boundary**: `fmt.Errorf("userService.Create: %w", err)` — the chain reads like a call stack45- **List sentinel errors upfront**: `var ErrNotFound`, `var ErrConflict`, etc. — define them before writing the code that returns them46- **Log once, at the top**: handlers log the full error chain; lower layers wrap and propagate, never log4748## 6. Testing with Race Detection4950Write tests that catch concurrency bugs:51- **Always run with `-race`**: `go test -race ./...` — make this the default in CI and local dev52- **Test each layer independently**: `httptest.NewServer` for handlers, mock interfaces for services, real DB for repos53- **Stress-test concurrent paths**: launch N goroutines hitting the same endpoint and assert no races or data corruption54- **Measure coverage on critical paths**: `go test -coverprofile=cover.out ./internal/service/` — review uncovered branches5556## 7. Verify Before Shipping5758Run the full verification chain before declaring the service ready:59- `go vet ./...` — catch common mistakes60- `golangci-lint run` — comprehensive lint check61- `go test -race -count=1 ./...` — all tests pass with race detection, no caching62- `go build ./cmd/<service>` — binary compiles cleanly63- If any step fails, fix and re-run the entire chain6465---66> Converted and distributed by [TomeVault](https://tomevault.io/claim/fricklers) — claim your Tome and manage your conversions.67<!-- tomevault:4.0:skill_md:2026-04-15 -->