Go clean architecture
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.From its SKILL.md
npx -y skills add muratmirgun/gophers --skill go-clean-architectureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.5 KB, as published. Nobody here has run it
Go Clean Architecture
A 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.
This skill is framework-agnostic. Swap Gin for Fiber, Echo, Chi, or net/http by replacing the delivery layer — zero changes elsewhere.
Core Rules
- Dependency Rule. Source depends inward: Delivery → Usecase → Domain. Repository implements interfaces declared in Domain. Domain depends on nothing.
- Framework is a detail. Gin/Fiber/Echo/Chi/net-http types live only in
internal/delivery/. Usecases see plain Go values. - Database is a detail. SQL, sqlx, sqlc, pgx, GORM live only in
internal/repository/. Usecases see repository interfaces. - Domain owns the interfaces; layers below provide implementations.
UserRepositoryis an interface ininternal/domain; the Postgres struct is ininternal/repositoryand unexported. - 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. cmd/<binary>/main.gois the only place that knows the whole system. Wiring (DI) is explicit, framework-free Go code.
When This Pays Off
| Symptom | What clean architecture buys you |
|---|---|
| HTTP handlers contain SQL | Move SQL into a repository; handlers shrink to 5 lines |
| Tests need a running DB | Mock the repository interface; usecase tests run in milliseconds |
| Swapping web frameworks is a rewrite | Replace internal/delivery/http; nothing else touched |
| Business rules duplicated across handlers | Single usecase function, called by HTTP, gRPC, and a CLI |
| ORM hooks fire in surprising places | Repository methods are explicit; no hidden behavior |
If 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.
Project Structure
myapp/
cmd/
api/main.go # entry point: config → DI → start server
worker/main.go # different entry, same Domain & Usecase
internal/
domain/ # entities, value objects, repository INTERFACES, domain errors
user.go
order.go
errors.go
usecase/ # business logic; depends only on domain
user_usecase.go
order_usecase.go
repository/ # implementations of domain interfaces (Postgres, in-memory, ...)
user_postgres.go
order_postgres.go
delivery/ # framework-specific adapters
http/ # Gin/Echo/Chi/net-http handlers and routes
user_handler.go
order_handler.go
grpc/ # gRPC server adapters (if applicable)
pkg/ # exported, importable from outside (if you publish a library)
migrations/ # SQL migrations
config/
go.mod
Read references/domain.md, references/usecase.md, references/repository.md, and references/delivery.md for the per-layer responsibilities.
The Four Layers
| Layer | Package | Can import | Must not import |
|---|---|---|---|
| Domain | internal/domain | stdlib only | usecase, repository, delivery, frameworks |
| Usecase | internal/usecase | domain | repository (concrete), delivery, frameworks |
| Repository | internal/repository | domain, DB driver | delivery, frameworks |
| Delivery | internal/delivery/... | domain, usecase (via interface), framework | repository (concrete) |
A golangci-lint config with depguard enforces these rules at CI time.
Layer Sketches
// Domain — pure interfaces and entities, no I/O.
package domain
type User struct { ID, Email, Name string; CreatedAt time.Time }
type UserRepository interface {
Get(ctx context.Context, id string) (*User, error)
Create(ctx context.Context, u *User) error
}
type UserService interface {
Create(ctx context.Context, in CreateUserInput) (*User, error)
}
// Usecase — business logic, depends only on domain interfaces.
type userUsecase struct{ repo domain.UserRepository }
func NewUserUsecase(repo domain.UserRepository) domain.UserService {
return &userUsecase{repo: repo}
}
// Repository — concrete adapter, translates driver errors to domain errors.
type postgresUserRepo struct{ db *sql.DB }
func NewUserRepository(db *sql.DB) domain.UserRepository { return &postgresUserRepo{db: db} }
// Delivery — HTTP framework lives only here; swap freely.
type UserHandler struct{ svc domain.UserService }
func NewUserHandler(svc domain.UserService) *UserHandler { return &UserHandler{svc: svc} }
Read references/domain.md, references/usecase.md, references/repository.md, and references/delivery.md for full code examples per layer.
Wiring in main.go
// cmd/api/main.go — the only place that knows the whole system.
db, _ := sql.Open("postgres", cfg.DBURL)
userRepo := repository.NewUserRepository(db)
userSvc := usecase.NewUserUsecase(userRepo)
userH := delivery.NewUserHandler(userSvc)
r := gin.New()
r.POST("/api/v1/users", userH.Create)
_ = r.Run(cfg.Addr)
This is the only file that imports every internal package. Adding a feature touches each layer plus one DI line here — predictable.
Read references/anti-patterns.md for the failure modes — leaking
*gin.Contextinto usecases, importing repository from delivery, returning concrete types instead of interfaces.
Error Flow
Repository Usecase Delivery
sql.ErrNoRows → domain.ErrNotFound → 404
unique violation → domain.ErrConflict → 409
validation rule → domain.ErrValidation → 422
unknown → wrapped error → 500 (logged)
Map domain errors to HTTP status codes in the delivery layer — never in the domain. The mapping changes per transport (HTTP 404 ↔ gRPC NotFound).
Anti-Patterns
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
*gin.Context parameter in a usecase | Locks the system into Gin forever | Pass context.Context and plain inputs |
Repository returns *sql.Rows | Usecase has to know about database/sql | Return domain entities only |
Concrete *userUsecase exported | Direct instantiation bypasses constructor (and the dependency rule) | Return domain.UserService from New... |
| Delivery imports repository directly | Skips the usecase; logic moves to handlers | Inject domain.UserService, not *postgresUserRepo |
| Same struct for DTO and Domain entity | Adding HTTP-only fields pollutes the domain | Separate request/response structs in delivery |
Domain importing errors.Is(err, gorm.ErrRecordNotFound) | Couples domain to GORM | Translate driver errors in repository to domain.ErrXxx |
| Wiring scattered across init() funcs | Implicit order, hard to debug | All DI in main.go, top-to-bottom |
Verification Checklist
Each item maps to a command you can run; the expected outcome is in parentheses.
-
go list -deps ./internal/domain | grep -v '^\(internal/\|<modpath>\)' | grep -v '^[a-z]*$'shows only stdlib paths (domain has no third-party deps) -
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) -
go list -f '{{.Imports}}' ./internal/delivery/... | tr ' ' '\n' | grep 'internal/repository'is empty (delivery never imports repository) -
grep -rn 'func New[A-Z]' internal/usecase | grep -v 'domain\.\|interface'is empty (everyNewXreturns a domain interface, not a concrete type) -
grep -rln 'internal/repository' cmd/ internal/lists onlycmd/*/main.go(main is the only wiring site) -
go test ./internal/usecase/... -count=1passes with no DB available (usecase mocks the repository interface) - 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
References
- references/domain.md — entities, value objects, interface ownership, sentinel errors
- references/usecase.md — orchestration patterns, input DTOs, testing
- references/repository.md — concrete adapters, error translation, transactions
- references/delivery.md — HTTP handlers, framework swap, error → status mapping
- references/anti-patterns.md — leaks across layer boundaries
What ships with it: 5 files
29.9 KB alongside SKILL.md
references/
- anti-patterns.md5.8 KB
- delivery.md6.9 KB
- domain.md5.0 KB
- repository.md6.3 KB
- usecase.md5.8 KB
Gives 0 of the 12 instructions most architecture codebase skills give
Counted across 858 of the 1,304 authors here whose files we hold, read 2026-09-06
- Apply the deletion test to identify shallow modulesin 32 of 858, across 31 files
- Read domain glossary and ADRs before exploringin 22 of 858, across 19 files
- Use Tailwind and Mermaid via CDN for reportsin 21 of 858, across 18 files
- Document architecture decision recordsin 20 of 858, across 12 files
- Offer to record ADRs for rejected candidatesin 17 of 858, across 14 files
- Limit primary navigation to four to seven itemsin 17 of 858, across 7 files
- Write HTML report to the system temp directoryin 17 of 858, across 14 files
- Read product marketing context before asking questionsin 16 of 858, across 6 files
- Use Mermaid graph TD for visual sitemapsin 15 of 858, across 5 files
- Ensure every page has at least one internal linkin 15 of 858, across 5 files
- Use ASCII tree format for page hierarchy draftsin 15 of 858, across 5 files
- Enforce lowercase URLs with hyphensin 15 of 858, across 5 files
Said here and by no other author read
- organize code into domain, usecase, repository, and delivery layers
- keep framework types inside the delivery layer
- keep database implementation details inside the repository layer
- map request bodies to domain inputs in the delivery layer
- perform all dependency injection in the main package
- return domain interfaces from constructor functions
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.