Lesson 6 · Methods, interfaces & generics
Interfaces
Implicit, small, and declared where they're used — Go's superpower.
Your win: explain implicit satisfaction, argue why Go interfaces are small, and read our repo's "define the interface at the consumer" pattern — the thing that makes the code so mockable.
Satisfaction is implicit
There is no implements keyword. A type satisfies an interface simply by
having its methods.1 The implementer doesn't
name — or even import — the interface. That one design choice is what decouples Go
packages so cleanly.
type Stringer interface { String() string }
// Email satisfies Stringer just by having String() — no declaration needed.
func (e *Email) String() string { return e.EmailID.String }
Keep interfaces small
io.Reader,
io.Writer, fmt.Stringer. Small interfaces are easy to
satisfy, easy to mock, and compose freely.
Define the interface where you consume it
The idiom that surprises newcomers: in Go you usually declare an interface next to the caller that needs it, listing only the methods that caller uses — not next to the implementation. "Accept interfaces, return structs."
Our controllers do exactly this — an inline, anonymous interface holding just the one method the handler calls:
internal/spike/modules/email/controller/grpc/email_modifier_service.go:25-30type EmailModifierService struct {
EmailCommandHandler interface {
CreateEmail(ctx context.Context, p *commands.CreateEmailPayload) (*dto.Email, error)
}
}
At the hexagonal seams we also keep segregated port files — a named interface per repository the domain needs:
internal/spike/modules/email/infrastructure/repo_port.go:11type EmailRepo interface {
UpsertEmail(ctx context.Context, db database.QueryExecer, e *model.Email) error
...
}
MockEmailRepo{ mock.Mock }). The concrete
&repositories.EmailRepo{} satisfies the port implicitly — it never
imports the interface. Swap real for mock at the constructor; nothing else changes.
(Mocking is Part 5.)
Accept interfaces, return structs
Functions take interface parameters (so callers can pass anything satisfying them)
but constructors return concrete *T (so callers get the full type). You saw
this in Lesson 3: NewEmailModifierService(...) *EmailModifierService. This
keeps flexibility at the boundary without hiding the real type from its owner.
Effective Go — Interfaces
The canonical treatment of implicit satisfaction and interface design. Pair with the Tour's interface section and the Go Proverbs.
→ go.dev/doc/effective_go#interfaces
→ A Tour of Go — interfaces
Check yourself (from memory)
Q1. A Go type satisfies an interface when it…
Q2. "The bigger the interface, the weaker the abstraction" argues for…
Q3. Our controllers declare their repo interfaces…
1. Effective Go — interfaces; Go spec — interface types.
2. Go Proverbs.