Go Architecture Basics
10 examples to get you started with Architecture - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Architecture - 7 basic and 3 intermediate.
go version).mkdir archdemo && cd archdemo && go mod init example.com/archdemo.Separate binaries, private implementation, and public library code.
archdemo/
cmd/api/main.go
internal/service/
internal/store/
pkg/api/ # optional shared API types
cmd/ holds main packages - one folder per binary.internal/ hides implementation from external importers.pkg/ is optional for libraries you want other modules to import.Related: Architecture in Go: Small Interfaces, Explicit Dependencies - why boundaries matter
Pass dependencies explicitly instead of globals.
package service
type Greeter struct {
prefix string
}
func New(prefix string) *Greeter {
return &Greeter{prefix: prefix}
}
func (g *Greeter) Hello(name string) string {
return g.prefix + ", " + name
}New validates inputs and returns a ready struct.Related: Dependency Injection: wire, dig & Manual Wiring - scaling wiring past hand-rolled
main
Define the contract where it is used.
package handler
import "context"
type Lister interface {
List(ctx context.Context) ([]string, error)
}
type HTTP struct {
svc Lister
}
func New(svc Lister) *HTTP {
return &HTTP{svc: svc}
}Lister has one method - easy to mock in handler tests.internal/store without importing HTTP.Translate HTTP to domain calls; no SQL in handlers.
package handler
import (
"encoding/json"
"net/http"
)
func (h *HTTP) List(w http.ResponseWriter, r *http.Request) {
items, err := h.svc.List(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(items)
}r.Context() for cancellation and deadlines.Related: Handler-Service-Repository Layering - full three-layer flow
Business rules live between HTTP and storage.
package service
import (
"context"
"fmt"
)
type Repo interface {
All(ctx context.Context) ([]string, error)
}
type Service struct {
repo Repo
}
func New(repo Repo) *Service {
return &Service{repo: repo}
}
func (s *Service) List(ctx context.Context) ([]string, error) {
items, err := s.repo.All(ctx)
if err != nil {
return nil, fmt.Errorf("list: %w", err)
}
return items, nil
}%w) for observability.List satisfies the handler's Lister interface from example 3.Isolate persistence behind a small API.
package store
import "context"
type Memory struct {
data []string
}
func (m *Memory) All(ctx context.Context) ([]string, error) {
return append([]string(nil), m.data...), nil
}Wire concrete types in cmd/api/main.go only.
package main
import (
"log"
"net/http"
"example.com/archdemo/internal/handler"
"example.com/archdemo/internal/service"
"example.com/archdemo/internal/store"
)
func main() {
repo := &store.Memory{data: []string{"alpha", "beta"}}
svc := service.New(repo)
h := handler.New(svc)
mux := http.NewServeMux()
mux.HandleFunc("GET /items", h.List)
log.Fatal(http.ListenAndServe(":8080", mux))
}main is the only place that knows all concrete types.ServeMux.Packages under internal/ cannot be imported outside the parent tree.
// internal/store/pg.go
package store
// Only example.com/archdemo/... may import this package.
type PG struct{}internal paths.Related: Clean & Hexagonal Architecture in Go - ports at the domain edge
Organize by capability when domains are clear.
internal/
billing/
service.go
handler.go
postgres.go
shipping/
...
internal/platform/) hold logging and auth helpers.Related: Monolith vs Microservices in Go - when to split binaries
Record why you chose a layout before debate repeats.
# ADR 0003: Handler-service-repository layering
## Status
Accepted
## Context
New HTTP API; team wants testable business logic.
## Decision
Use handler / service / repository packages with consumer-side interfaces.
## Consequences
More packages; explicit wiring in main; easier unit tests.docs/adr/ or .adr/ at repo root.Related: Architecture Decision Records for Go Teams - ranked decisions for Go services
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026