agentleFS
AlessandroMartiniAlessandroMartini/Awesome_IA_Rules/.cursor/rules/backend/go-backend.mdc

Awesome_IA_Rules / backend

Cursor rule1 starsChanged 3 days agoMITView on GitHub

Go backend engineering best practices, Clean Architecture, concurrency, error handling, and unit testing

The file

We split this go-backend.mdc into the rules it gives an agent

7 rules. About 0 are common, 1 is distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Always format with gofmt -s / goimports.

    line 45
  2. Very specific:

    Always attach explicit timeouts (context.WithTimeout) to outbound database and network operations.

    line 68
  3. Very specific:

    Check for context cancellation (errors.Is(err, context.Canceled) / context.DeadlineExceeded) before logging ERROR alerts for client-aborted …

    line 69
  4. Distinctive:

    Default to synchronous execution.

    line 72
  5. Very specific:

    Every spawned goroutine must have a deterministic termination path (errgroup.Group, sync.WaitGroup + ctx.Done()).

    line 73
  6. Very specific:

    Prefer golang.org/x/sync/errgroup with errgroup.WithContext(ctx) and SetLimit(n) for bounded concurrent fan-out:

    line 74
  7. Very specific:

    Builders & Table-Driven Tests: Use fluent test Builders for complex entities/DTOs and table-driven subtests (t.Run(tt.name, ...)) for input …

    line 136

≈ “Common” is an estimate: at least 1 in 5 of the 6,668 Cursor rule files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
go-backend.mdc
---
description: "Go backend engineering best practices, Clean Architecture, concurrency, error handling, and unit testing"
globs: "**/*.go"
alwaysApply: false
---

# Go Backend Engineering Standards

> **Scope**: All Go (`*.go`) source and test files.
> **Mandate**: Write idiomatic, readable, boring Go code following Clean Architecture, explicit context propagation, structured error handling, and leak-free concurrency.

---

## 1. Project Structure & Clean Architecture

Enforce strict inward dependencies: `entrypoint/infrastructure → application → domain`.

```text
cmd/api/main.go                 # Thin bootstrap calling runner
configs/                        # Typed config structs, loader, YAML/env bindings
internal/
├── domain/                     # Pure business logic (zero external/infra imports)
│   ├── entities/               # Aggregate roots & entities protecting invariants
│   ├── vo/                     # Immutable Value Objects (e.g., Money, Email)
│   ├── services/               # Cross-entity stateless domain services
│   └── ports/                  # Stable business capability interfaces (Repositories)
├── application/                # Use case orchestration & CQRS
│   ├── commands/               # Write handlers (Create, Update, Delete)
│   ├── queries/                # Read handlers (when business visibility rules apply)
│   ├── dtos/                   # Request (Validate) & Response (FromEntity) DTOs
│   └── ports/                  # Use-case-specific external ports (Gateways, Readers)
└── infrastructure/             # Technical implementations
    ├── entrypoint/rest/        # HTTP handlers, routers, middlewares
    ├── entrypoint/consumer/    # Message queue consumers
    ├── repository/             # DB implementations, persistence DTOs, mappers
    ├── adapter/                # External HTTP/gRPC client implementations
    └── runner/                 # Dependency wiring & graceful shutdown
```

---

## 2. Idiomatic Go & Code Style

### Formatting & Imports
- Always format with `gofmt -s` / `goimports`.
- Group imports into three blocks separated by blank lines:
  1. Standard library (`"context"`, `"fmt"`, `"time"`)
  2. Third-party dependencies
  3. Internal project packages

### Naming Conventions
- **Packages**: Short, concise, lowercase, single-word nouns (`payment`, `order`, `postgres`). Never use `util`, `common`, `helpers`, or `base`.
- **Interfaces**: Define interfaces on the **consumer side**, kept small (1–3 methods). Do not create interfaces preemptively without a concrete need for polymorphism or boundary mocking.
- **Strong Domain Typing**: Define explicit types for domain identifiers and statuses (`type OrderID string`, `type OrderStatus string`) instead of bare primitives.

### Pointers vs. Values
- **Use Pointers (`*Entity`)**: For domain entities, aggregate roots, large structs, and repository lookups where `nil` semantics or state mutation apply.
- **Use Values (`ValueObject`)**: For small, immutable Value Objects (`Money`, `DateRange`) and primitives.
- **Slices**: Return an initialized empty slice (`[]Item{}`) rather than `nil` in collection responses so JSON serialization produces `[]` instead of `null`.

---

## 3. Context & Concurrency

### `context.Context` Rules
- Pass `ctx context.Context` as the **first parameter** of every function performing I/O, orchestration, or long computation.
- **Never** store `context.Context` inside a struct.
- Always attach explicit timeouts (`context.WithTimeout`) to outbound database and network operations.
- Check for context cancellation (`errors.Is(err, context.Canceled)` / `context.DeadlineExceeded`) before logging `ERROR` alerts for client-aborted requests.

### Safe Concurrency
- Default to synchronous execution; introduce goroutines **only** when concurrent I/O or parallelism yields measurable latency/throughput gains.
- Every spawned goroutine must have a deterministic termination path (`errgroup.Group`, `sync.WaitGroup` + `ctx.Done()`).
- Prefer `golang.org/x/sync/errgroup` with `errgroup.WithContext(ctx)` and `SetLimit(n)` for bounded concurrent fan-out:

```go
g, gCtx := errgroup.WithContext(ctx)
g.SetLimit(10) // Always bound concurrency

for _, item := range items {
    g.Go(func() error {
        return s.processItem(gCtx, item)
    })
}
if err := g.Wait(); err != nil {
    return err
}
```

### Resource Lifecycle
- Immediately `defer rows.Close()`, `defer resp.Body.Close()`, or `defer tx.Rollback()` after checking the acquisition error.
- Instantiate database pools (`*sql.DB`) and HTTP clients (`*http.Client`) **once** at startup and reuse them across requests.

---

## 4. Validation & Error Handling

### Two-Tier Validation with Aggregated Errors
Aggregate all validation failures at once (using `errors.Join` or `multierror`) so callers receive complete feedback in a single round-trip:

```go
func (r *CreatePaymentRequest) Validate() error {
    var errs []error
    if r.CustomerID == "" {
        errs = append(errs, errors.New("customer_id is required"))
    }
    if r.Amount <= 0 {
        errs = append(errs, errors.New("amount must be greater than zero"))
    }
    if len(r.Currency) != 3 {
        errs = append(errs, errors.New("currency must be a 3-letter ISO code"))
    }
    if len(errs) > 0 {
        return apperror.BadRequest("validation failed", "VALIDATION_ERROR", errors.Join(errs...))
    }
    return nil
}
```

### Domain Entity Isolation
- Domain structs must **never** carry `json`, `bson`, `db`, or `gorm` tags.
- Repositories must map between `domain.Entity` and an internal `PersistenceModel` struct (`MapEntityToModel` / `MapModelToEntity`).

### Error Creation & Proximity Logging
- **Domain**: Returns semantic errors (`BadRequest`, `Conflict`, `NotFound`) without logging.
- **Infrastructure (Repositories / Adapters)**: Logs technical failures once at the source using structured logging (`slog.ErrorContext(ctx, "failed to save payment", slog.Any("err", err), slog.String("payment_id", id))`) and returns a safe domain/application error wrapping the cause.
- **Application (Handlers)**: Propagates repository/adapter errors upward **without re-logging**.
- **Entrypoint (HTTP Handlers & Error Middleware)**: Logs `4xx` client/validation rejections once at `WARN` (either at the validation boundary or in centralized error middleware, never both) and logs only unlogged unexpected `500` errors at `ERROR`. Never leak `sql.Err*` or raw driver errors to clients.

---

## 5. Unit Testing (`*_test.go`)

- **Naming**: `Test<Struct>_<Method>_Should<Expected>_When<Condition>`.
- **Pattern**: Strictly follow **Arrange-Act-Assert (AAA)** using `testify/require` (for preconditions that must halt the test) and `testify/assert` (for result verification) with descriptive English messages.
- **Builders & Table-Driven Tests**: Use fluent test Builders for complex entities/DTOs and table-driven subtests (`t.Run(tt.name, ...)`) for input validation, domain invariants, and mapper edge cases.
- **Mocks**: Generate mocks for ports using `uber-go/mock` (`gomock`). Avoid blanket `gomock.Any()` when specific argument matchers or `.DoAndReturn()` assertions verify state transitions.
- **Determinism**: Inject a `Clock` interface (`type Clock interface { Now() time.Time }`) instead of calling `time.Now()` directly. Use `t.Parallel()` on isolated unit tests.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.