← GophersCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Gophers
Snapshot Sep 30, 2026 · 23:14 UTC · version 0.1.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward dependency rule, 'framework/database is a detail'. Apply when untangling a monolith or checking whether business logic is testable without HTTP or DB.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 242
},
{
"relative_path": "references/anti-patterns.md",
"size_in_bytes": 5968
},
{
"relative_path": "references/delivery.md",
"size_in_bytes": 7102
},
{
"relative_path": "references/domain.md",
"size_in_bytes": 5102
},
{
"relative_path": "references/repository.md",
"size_in_bytes": 6467
},
{
"relative_path": "references/usecase.md",
"size_in_bytes": 5952
}
],
"name": "go-clean-architecture",
"skill_md_contents": "---\nname: go-clean-architecture\ndescription: \"Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward dependency rule, 'framework/database is a detail'. Apply when untangling a monolith or checking whether business logic is testable without HTTP or DB.\"\nlicense: MIT\ncompatibility: \"Designed for Claude Code or similar AI coding agents. Requires Go 1.21+. Framework-agnostic: works with Gin, Echo, Fiber, Chi, or net/http; swap freely.\"\nallowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)\n---\n\n# Go Clean Architecture\n\nA Go service organized into four concentric layers — Domain, Usecase, Repository, Delivery — where source code depends *inward only*. Done well, the HTTP framework and the database are interchangeable details; the business logic is testable without either.\n\nThis skill is framework-agnostic. Swap Gin for Fiber, Echo, Chi, or `net/http` by replacing the delivery layer — zero changes elsewhere.\n\n## Core Rules\n\n1. **Dependency Rule.** Source depends inward: Delivery → Usecase → Domain. Repository implements interfaces declared in Domain. Domain depends on nothing.\n2. **Framework is a detail.** Gin/Fiber/Echo/Chi/net-http types live only in `internal/delivery/`. Usecases see plain Go values.\n3. **Database is a detail.** SQL, sqlx, sqlc, pgx, GORM live only in `internal/repository/`. Usecases see repository interfaces.\n4. **Domain owns the interfaces; layers below provide implementations.** `UserRepository` is an interface in `internal/domain`; the Postgres struct is in `internal/repository` and unexported.\n5. **DTOs at the edges.** Delivery layer maps HTTP request bodies to domain inputs and domain entities to response bodies. Usecases never see `*gin.Context`, `http.Request`, or DB rows.\n6. **`cmd/<binary>/main.go` is the only place that knows the whole system.** Wiring (DI) is explicit, framework-free Go code.\n\n## When This Pays Off\n\n| Symptom | What clean architecture buys you |\n|---|---|\n| HTTP handlers contain SQL | Move SQL into a repository; handlers shrink to 5 lines |\n| Tests need a running DB | Mock the repository interface; usecase tests run in milliseconds |\n| Swapping web frameworks is a rewrite | Replace `internal/delivery/http`; nothing else touched |\n| Business rules duplicated across handlers | Single usecase function, called by HTTP, gRPC, and a CLI |\n| ORM hooks fire in surprising places | Repository methods are explicit; no hidden behavior |\n\nIf the service is a 200-line cron job, this skill is overkill. If it will live 3+ years and grow features, it's the cheapest insurance you can buy.\n\n## Project Structure\n\n```\nmyapp/\n cmd/\n api/main.go # entry point: config → DI → start server\n worker/main.go # different entry, same Domain & Usecase\n internal/\n domain/ # entities, value objects, repository INTERFACES, domain errors\n user.go\n order.go\n errors.go\n usecase/ # business logic; depends only on domain\n user_usecase.go\n order_usecase.go\n repository/ # implementations of domain interfaces (Postgres, in-memory, ...)\n user_postgres.go\n order_postgres.go\n delivery/ # framework-specific adapters\n http/ # Gin/Echo/Chi/net-http handlers and routes\n user_handler.go\n order_handler.go\n grpc/ # gRPC server adapters (if applicable)\n pkg/ # exported, importable from outside (if you publish a library)\n migrations/ # SQL migrations\n config/\n go.mod\n```\n\n> Read [references/domain.md](references/domain.md), [references/usecase.md](references/usecase.md), [references/repository.md](references/repository.md), and [references/delivery.md](references/delivery.md) for the per-layer responsibilities.\n\n## The Four Layers\n\n| Layer | Package | Can import | Must not import |\n|---|---|---|---|\n| Domain | `internal/domain` | stdlib only | usecase, repository, delivery, frameworks |\n| Usecase | `internal/usecase` | domain | repository (concrete), delivery, frameworks |\n| Repository | `internal/repository` | domain, DB driver | delivery, frameworks |\n| Delivery | `internal/delivery/...` | domain, usecase (via interface), framework | repository (concrete) |\n\nA `golangci-lint` config with `depguard` enforces these rules at CI time.\n\n## Layer Sketches\n\n```go\n// Domain — pure interfaces and entities, no I/O.\npackage domain\ntype User struct { ID, Email, Name string; CreatedAt time.Time }\ntype UserRepository interface {\n Get(ctx context.Context, id string) (*User, error)\n Create(ctx context.Context, u *User) error\n}\ntype UserService interface {\n Create(ctx context.Context, in CreateUserInput) (*User, error)\n}\n```\n\n```go\n// Usecase — business logic, depends only on domain interfaces.\ntype userUsecase struct{ repo domain.UserRepository }\n\nfunc NewUserUsecase(repo domain.UserRepository) domain.UserService {\n return &userUsecase{repo: repo}\n}\n```\n\n```go\n// Repository — concrete adapter, translates driver errors to domain errors.\ntype postgresUserRepo struct{ db *sql.DB }\nfunc NewUserRepository(db *sql.DB) domain.UserRepository { return &postgresUserRepo{db: db} }\n```\n\n```go\n// Delivery — HTTP framework lives only here; swap freely.\ntype UserHandler struct{ svc domain.UserService }\nfunc NewUserHandler(svc domain.UserService) *UserHandler { return &UserHandler{svc: svc} }\n```\n\n> Read [references/domain.md](references/domain.md), [references/usecase.md](references/usecase.md), [references/repository.md](references/repository.md), and [references/delivery.md](references/delivery.md) for full code examples per layer.\n\n## Wiring in `main.go`\n\n```go\n// cmd/api/main.go — the only place that knows the whole system.\ndb, _ := sql.Open(\"postgres\", cfg.DBURL)\nuserRepo := repository.NewUserRepository(db)\nuserSvc := usecase.NewUserUsecase(userRepo)\nuserH := delivery.NewUserHandler(userSvc)\nr := gin.New()\nr.POST(\"/api/v1/users\", userH.Create)\n_ = r.Run(cfg.Addr)\n```\n\nThis is the only file that imports every internal package. Adding a feature touches each layer plus one DI line here — predictable.\n\n> Read [references/anti-patterns.md](references/anti-patterns.md) for the failure modes — leaking `*gin.Context` into usecases, importing repository from delivery, returning concrete types instead of interfaces.\n\n## Error Flow\n\n```\nRepository Usecase Delivery\nsql.ErrNoRows → domain.ErrNotFound → 404\nunique violation → domain.ErrConflict → 409\nvalidation rule → domain.ErrValidation → 422\nunknown → wrapped error → 500 (logged)\n```\n\nMap domain errors to HTTP status codes in the delivery layer — never in the domain. The mapping changes per transport (HTTP 404 ↔ gRPC NotFound).\n\n## Anti-Patterns\n\n| Anti-pattern | Why it hurts | Do this instead |\n|---|---|---|\n| `*gin.Context` parameter in a usecase | Locks the system into Gin forever | Pass `context.Context` and plain inputs |\n| Repository returns `*sql.Rows` | Usecase has to know about `database/sql` | Return domain entities only |\n| Concrete `*userUsecase` exported | Direct instantiation bypasses constructor (and the dependency rule) | Return `domain.UserService` from `New...` |\n| Delivery imports repository directly | Skips the usecase; logic moves to handlers | Inject `domain.UserService`, not `*postgresUserRepo` |\n| Same struct for DTO and Domain entity | Adding HTTP-only fields pollutes the domain | Separate request/response structs in delivery |\n| Domain importing `errors.Is(err, gorm.ErrRecordNotFound)` | Couples domain to GORM | Translate driver errors in repository to `domain.ErrXxx` |\n| Wiring scattered across init() funcs | Implicit order, hard to debug | All DI in `main.go`, top-to-bottom |\n\n## Verification Checklist\n\nEach item maps to a command you can run; the expected outcome is in parentheses.\n\n- [ ] `go list -deps ./internal/domain | grep -v '^\\(internal/\\|<modpath>\\)' | grep -v '^[a-z]*$'` shows only stdlib paths (domain has no third-party deps)\n- [ ] `go list -f '{{.Imports}}' ./internal/usecase/... | tr ' ' '\\n' | grep -E '(gin|echo|fiber|chi|database/sql|gorm|pgx)'` is empty (usecase touches no framework/driver)\n- [ ] `go list -f '{{.Imports}}' ./internal/delivery/... | tr ' ' '\\n' | grep 'internal/repository'` is empty (delivery never imports repository)\n- [ ] `grep -rn 'func New[A-Z]' internal/usecase | grep -v 'domain\\.\\|interface'` is empty (every `NewX` returns a domain interface, not a concrete type)\n- [ ] `grep -rln 'internal/repository' cmd/ internal/` lists only `cmd/*/main.go` (main is the only wiring site)\n- [ ] `go test ./internal/usecase/... -count=1` passes with no DB available (usecase mocks the repository interface)\n- [ ] Swapping HTTP framework: `git mv internal/delivery/http internal/delivery/http_old && go build ./internal/usecase/... ./internal/repository/... ./internal/domain/...` succeeds — only delivery is dirty\n\n## References\n\n- [references/domain.md](references/domain.md) — entities, value objects, interface ownership, sentinel errors\n- [references/usecase.md](references/usecase.md) — orchestration patterns, input DTOs, testing\n- [references/repository.md](references/repository.md) — concrete adapters, error translation, transactions\n- [references/delivery.md](references/delivery.md) — HTTP handlers, framework swap, error → status mapping\n- [references/anti-patterns.md](references/anti-patterns.md) — leaks across layer boundaries\n"
}SHA-256 of public snapshot: 63bdf7000de9a230f69d9d4d7a1cc9c52c03e7ac15dcbdaa880fabb419c15b4a