source

My favorite Go Backend architecture pattern (2026)

created 2026-05-05 go · architecture · layered-architecture · patterns · source

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.DB into 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 / Repository interface even if it’s contained to one domain. For larger systems, separate root interfaces per bounded context would scale better.