agentsclimarketplace

Go clean architecture

Skill muratmirgun/gophers/skills/go-clean-architecture

26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.

Install
npx -y skills add muratmirgun/gophers --skill go-clean-architecture

Assembled 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 author says it does

Copied from the file, not written here

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.

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.7 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

  1. Dependency Rule. Source depends inward: Delivery → Usecase → Domain. Repository implements interfaces declared in Domain. Domain depends on nothing.
  2. Framework is a detail. Gin/Fiber/Echo/Chi/net-http types live only in internal/delivery/. Usecases see plain Go values.
  3. Database is a detail. SQL, sqlx, sqlc, pgx, GORM live only in internal/repository/. Usecases see repository interfaces.
  4. Domain owns the interfaces; layers below provide implementations. UserRepository is an interface in internal/domain; the Postgres struct is in internal/repository and unexported.
  5. 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.
  6. cmd/<binary>/main.go is the only place that knows the whole system. Wiring (DI) is explicit, framework-free Go code.

When This Pays Off

SymptomWhat clean architecture buys you
HTTP handlers contain SQLMove SQL into a repository; handlers shrink to 5 lines
Tests need a running DBMock the repository interface; usecase tests run in milliseconds
Swapping web frameworks is a rewriteReplace internal/delivery/http; nothing else touched
Business rules duplicated across handlersSingle usecase function, called by HTTP, gRPC, and a CLI
ORM hooks fire in surprising placesRepository 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

LayerPackageCan importMust not import
Domaininternal/domainstdlib onlyusecase, repository, delivery, frameworks
Usecaseinternal/usecasedomainrepository (concrete), delivery, frameworks
Repositoryinternal/repositorydomain, DB driverdelivery, frameworks
Deliveryinternal/delivery/...domain, usecase (via interface), frameworkrepository (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.Context into 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-patternWhy it hurtsDo this instead
*gin.Context parameter in a usecaseLocks the system into Gin foreverPass context.Context and plain inputs
Repository returns *sql.RowsUsecase has to know about database/sqlReturn domain entities only
Concrete *userUsecase exportedDirect instantiation bypasses constructor (and the dependency rule)Return domain.UserService from New...
Delivery imports repository directlySkips the usecase; logic moves to handlersInject domain.UserService, not *postgresUserRepo
Same struct for DTO and Domain entityAdding HTTP-only fields pollutes the domainSeparate request/response structs in delivery
Domain importing errors.Is(err, gorm.ErrRecordNotFound)Couples domain to GORMTranslate driver errors in repository to domain.ErrXxx
Wiring scattered across init() funcsImplicit order, hard to debugAll 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 (every NewX returns a domain interface, not a concrete type)
  • grep -rln 'internal/repository' cmd/ internal/ lists only cmd/*/main.go (main is the only wiring site)
  • go test ./internal/usecase/... -count=1 passes 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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.