source
My favorite Go Backend architecture pattern (2026)
My favorite Go Backend architecture pattern
By codinginspiration (Medium, 2025-08-06).
A short opinion piece advocating a 3-layer Go backend pattern (Handler → Service → Repository) inspired by Spring Boot’s MVC, plus the Go-idiomatic functional-options pattern for shared pkg/ clients. Aimed at devs coming from Java/Spring who want a “reasonable architecture” as Go projects grow.
Key takeaways
- Three layers, unidirectional dependencies: Handler (HTTP) → Service (business logic) → Repository (DB). Repository methods only callable from Service.
- Interfaces over struct pointers in every layer — for testability via mockery. Don’t pass
*gorm.DBinto Service or Handler structs. - Method signature convention: every layer method returns
(responseDTO, error). - Split monolithic interfaces with Go embedding:
type Handlers interface { User; Article }instead of one fat interface — keeps the domains decoupled while sharing a single root type. - Shared
pkg/: cross-cutting code that can be copied between projects (HTTP clients, etc.). Uses functional-options-pattern for constructors. - Tooling: gin (HTTP), gorm (ORM), mockery (mocks), Resty (outbound HTTP),
sync.Once(singletons).
Acknowledged weaknesses
The author admits transaction management is unsolved in this layering: when business logic spans multiple repository calls, you typically have to leak *gorm.DB up to Service, which violates the abstraction. Spring’s @Transactional annotation has no clean Go equivalent. Common workaround in the Go ecosystem (not in this article): pass a tx interface that both Service and Repository can use, or use a Unit-of-Work pattern.
Relevance to kulify
Two Go projects in the ecosystem could lean on this:
- Vault — currently CLI-shaped, no HTTP layer. The Service/Repository split applies if Vault ever grows an HTTP daemon for cross-machine secret sync.
- katastar — already has Go HTTP server + handlers. Worth an audit: does it follow Handler/Service/Repository, or is logic mixed into handlers? If a refactor pass happens (e.g., during the multi-user auth work), this is the target shape.
Both projects pre-date this ingest, so the article is a reference rather than a directive — useful when starting any new Go service inside kulify, or when reviewing an existing one.
Honest critique
- The article is light on transactions across services, cancellation/context propagation, structured logging, and observability — all of which matter as much as layering once a service has real traffic.
- No discussion of gRPC, event-driven flows, or CQRS — pattern is squarely sync-HTTP-over-relational-DB.
- The “split interfaces via embedding” trick is sound but carries a cost: every new feature touches the root
Handlers/Services/Repositoryinterface even if it’s contained to one domain. For larger systems, separate root interfaces per bounded context would scale better.
Related
- go-layered-architecture — the pattern itself, abstracted from this article
- functional-options-pattern — the Go constructor idiom the article uses
- gin, gorm, mockery — the toolkit
- Vault, katastar — kulify’s existing Go projects