go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Use when defining or implementing Go interfaces, composing types through embedding, designing dependency-injection seams, or deciding between pointer and value receivers. Apply proactively whenever a new abstraction is introduced or a constructor returns an abstract type, even
Claude Code
$ npx -y skills add muratmirgun/gophers --skill go-interfaces --agent claude-code
Codex
$ npx skills add muratmirgun/gophers --skill go-interfaces -a codex -y
Cursor
$ npx skills add muratmirgun/gophers --skill go-interfaces -a cursor -y
Gemini CLI
$ npx skills add muratmirgun/gophers --skill go-interfaces -a gemini-cli -y
GitHub Copilot
$ npx skills add muratmirgun/gophers --skill go-interfaces -a github-copilot -y
OpenCode
$ npx skills add muratmirgun/gophers --skill go-interfaces -a opencode -y
Windsurf
$ npx skills add muratmirgun/gophers --skill go-interfaces -a windsurf -y
name: go-interfaces description: "Use when defining or implementing Go interfaces, composing types through embedding, designing dependency-injection seams, or deciding between pointer and value receivers. Apply proactively whenever a new abstraction is introduced or a constructor returns an abstract type, even if the user has not asked about interfaces. Does not cover generics (see go-generics)." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Targets Go 1.21+. Generics guidance is delegated to go-generics." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Interfaces in Go are *consumer contracts*, not implementation hierarchies. They should be **small**, **discovered late**, and **owned by the package that uses them** — not the package that satisfies them.
1. **Accept interfaces, return concrete types.** Consumers state what they need; producers expose what they have. 2. **Interfaces belong in the consumer package.** Defining an interface next to its sole implementation is almost always wrong. 3. **Don't design with interfaces — discover them.** Wait for a second implementation or a test mock to demand one. 4. **The bigger the interface, the weaker the abstraction.** Aim for 1–3 methods; compose larger contracts from smaller ones. 5. **Receiver consistency:** if any method needs a pointer receiver, give *every* method a pointer receiver. 6. **Verify satisfaction at compile time** with `var _ I = (*T)(nil)` when the relationship must not break silently. 7. **Use the comma-ok idiom for every type assertion.** A bare assertion panics on mismatch.
| Situation | Verdict | |---|---| | Single implementation, no tests need to swap it | No interface. Use the concrete type. | | Second implementation appears (or is imminent) | Extract an interface in the consumer package. | | Test needs to fake an external dependency | Define a small interface in the consumer; pass a fake. | | You want to expose optional behaviour (`Flusher`, `ReaderFrom`) | Define a tiny interface; check with `_, ok := v.(Iface)`. | | You want a stable plugin/SPI boundary | Yes, but keep it minimal and version it explicitly. |
> Read [references/consumer-owned-interfaces.md](references/consumer-owned-interfaces.md) when migrating a producer-defined interface back to the consumer, or when designing a new package boundary.
// Good — consumer defines what it needs
package notify
type Sender interface { Send(to, body string) error }
type Service struct{ s Sender }
func NewService(s Sender) *Service { return &Service{s: s} }
// Good — producer returns a concrete type
package email
type Client struct{ /* ... */ }
func New(cfg Config) *Client { /* ... */ }
func (c *Client) Send(to, body string) error { /* ... */ }// Bad — producer defines and returns its own interface,
// forcing every consumer to depend on email.Sender.
func New(cfg Config) Sender { return &client{...} }The exception is "expose an interface, hide the implementation": when a type has no exported methods beyond what the interface promises, returning the interface (`func NewHash() hash.Hash32`) is fine.
Standard library interfaces are the model: `io.Reader`, `io.Writer`, `io.Closer`, `fmt.Stringer`, `error` — one or two methods each. Compose larger contracts:
type ReadWriteCloser interface { io.Reader; io.Writer; io.Closer }If you find yourself writing a five-method interface, split it until each piece has a single reason to exist.
var _ io.ReadWriter = (*MyBuffer)(nil)
Use when the type must satisfy an interface for correctness (custom JSON marshalling, `http.Handler`) and no other static use already enforces it. Don't add one for every interface.
Always use the comma-ok form. Type switches re-declare the variable; cases with multiple types fall back to the interface type. Use optional-behaviour assertions to *enhance* a path without requiring the capability:
s, ok := v.(string) // comma-ok
switch x := v.(type) { case string: /* ... */ } // type switch
if f, ok := w.(http.Flusher); ok { f.Flush() } // optional behaviourStruct embedding promotes the inner type's methods and fields to the outer type. Use it deliberately — every promoted method becomes part of your public API.
type Server struct {
*slog.Logger // exposes Info/Warn/Error on Server
addr string
}| Use embedding when | Use a named field when | |---|---| | You want the outer type to *be* an enhanced version of the inner | You only need the inner type internally | | The full inner API should be promoted | You want to delegate explicitly to a subset |
Avoid embedding in exported types unless the promotion is the whole point. The inner type's method set is locked in once published.
> Read [references/embedding-and-receivers.md](references/embedding-and-receivers.md) when designing struct embedding, overriding promoted methods, resolving name conflicts, or choosing between pointer and value receivers.
Constructors take interfaces; tests pass fakes. No DI container required.
type UserStore interface {
FindByID(ctx context.Context, id string) (*User, error)
}
type UserService struct{ store UserStore }
func NewUserService(s UserStore) *UserService { return &UserService{store: s} }The `UserStore` interface lives in the package that defines `UserService`. The concrete `*pgUserStore` lives in a database package and doesn't know `UserService` exists.
Structs that must not be copied (those holding a mutex, internal pointers, or a `sync.WaitGroup`) should e
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.