4e433158cd
---ci--- project: atelier phase: 3 milestone: v0.4 status: complete phase_role: execution phase_tag: v0.3.3 requirements: covered: [ATELIER-102, ATELIER-103, ATELIER-104, ATELIER-105] partial: [] ---/ci---
89 lines
3.4 KiB
Markdown
89 lines
3.4 KiB
Markdown
# Go — Language Application
|
|
|
|
> How Atelier's domain principles apply in Go specifically. Derives from `domains/` docs.
|
|
|
|
## Derived Docs
|
|
|
|
- [go-types.md](go-types.md) — named types, generics, interfaces, type assertion discipline.
|
|
- [go-tooling.md](go-tooling.md) — go vet, golangci-lint, go test -race, module discipline.
|
|
- [go-concurrency.md](go-concurrency.md) — goroutines, channels, context, select, sync primitives.
|
|
- [go-testing.md](go-testing.md) — table-driven tests, t.Parallel, t.Cleanup, race detector.
|
|
|
|
## Type System (C1 Correctness, Data P7 Type Fidelity)
|
|
|
|
- **Named types for domain concepts:** `type UserId string`, not bare `string`.
|
|
- **No `interface{}`/`any` without justification:** Go 1.18+ generics reduce the need.
|
|
- **`any` requires a type assertion or switch:** never use the value without narrowing.
|
|
|
|
```go
|
|
type UserId string
|
|
type OrderId string
|
|
// UserId and OrderId are distinct; cannot be mixed
|
|
func GetUser(id UserId) (*User, error) { ... }
|
|
```
|
|
|
|
## Error Handling (Errors P1 Errors are Data)
|
|
|
|
- **Errors are values:** `error` is an interface, not an exception. Handle explicitly.
|
|
- **Sentinel errors with `errors.Is`:**
|
|
```go
|
|
var ErrNotFound = errors.New("not found")
|
|
if errors.Is(err, ErrNotFound) { ... }
|
|
```
|
|
- **Wrap with context:** `fmt.Errorf("get user %d: %w", id, err)`.
|
|
- **Never `_ = err`:** swallowed error (Errors P2). Handle or return.
|
|
- **Custom error types with `errors.As`:**
|
|
```go
|
|
type ValidationError struct {
|
|
Field string
|
|
Msg string
|
|
}
|
|
func (e *ValidationError) Error() string { return e.Field + ": " + e.Msg }
|
|
```
|
|
|
|
## Concurrency (Concurrency — Go's strength)
|
|
|
|
- **Goroutines + channels** for message passing (P5 Lock Minimization).
|
|
- **`context.Context` for cancellation and timeout:** every function that does I/O takes a `ctx`.
|
|
- **`sync.Mutex` scoped minimally:** not held across I/O (P3 Lock Scope).
|
|
- **Bounded channels:** `make(chan T, N)`, not unbounded (P9 Bounded Queues).
|
|
|
|
```go
|
|
func fetchWithTimeout(ctx context.Context, url string) (*Response, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
return doFetch(ctx, url)
|
|
}
|
|
```
|
|
|
|
## Immutability (Concurrency P1)
|
|
|
|
- **Pass by value for small structs; pass by pointer for large or mutable.**
|
|
- **No mutation of method receivers:** use a value receiver, not a pointer receiver, for read-only methods.
|
|
- **Copy-on-write for shared state:** return a new struct, not a mutated one.
|
|
|
|
## Nullability (C1)
|
|
|
|
- **Pointers can be nil; values cannot.** Be explicit: `*User` (nullable) vs `User` (not).
|
|
- **`nil` check before deref:** a nil deref is a panic.
|
|
- **Return `(T, error)`, not `(*T, nil)`:** avoid the "nil pointer" trap.
|
|
|
|
## Testing (Testing)
|
|
|
|
- **`testing` package + `testify/assert`** or stdlib only.
|
|
- **Table-driven tests:** `[]struct{ name string; input X; want Y }`.
|
|
- **`t.Parallel()`** for independent tests (P2 Independence).
|
|
- **`httptest` for HTTP handlers; `sqlite` or testcontainers for DB.**
|
|
|
|
## Observability (Observability P1)
|
|
|
|
- **`slog` (stdlib, Go 1.21+) or `zap`/`zerolog`:** structured logs.
|
|
- **`context.Context` carries `trace_id`:** propagated via middleware.
|
|
- **No `fmt.Println`:** use the logger.
|
|
|
|
## Tooling (DevOps P2)
|
|
|
|
- **`go vet` + `golangci-lint`:** lint.
|
|
- **`gofmt`/`goimports`:** format (automated, not debated).
|
|
- **`go test -race` in CI:** race detector (Concurrency P6 No Silent Races).
|
|
- **`go mod tidy` + committed `go.sum`:** reproducible builds. |