Skip to content

Layered vs Flat Architecture: Which Go Architecture Should You Use?

Both Layered and Flat architecture are supported by verikt. Layered is the classic Go web service structure — handler, service, repository — with enforced dependency direction. Flat is a single package with no imposed organization. The question is whether your project warrants any formal layering at all.

LayeredFlat
ComplexityMediumLow
Best forStandard CRUD APIsCLI tools, scripts, prototypes
Dependency rulesStrict (top-down)None
Team sizeAnyAny
Directory structureModerateSimple

Layered organizes a Go service into four packages with a strict top-down dependency direction. Handlers at the top handle HTTP concerns. Services contain business logic. Repositories handle data access. Model is the shared foundation.

cmd/<name>/ → Entry point
internal/handler/ → HTTP handlers (request/response)
internal/service/ → Business logic
internal/repository/ → Data access
internal/model/ → Shared types and domain models
ComponentMay Depend On
modelnothing
repositorymodel
servicerepository, model
handlerservice, model

Dependencies only flow downward. Handlers can’t import from each other. Repositories can’t import from services. verikt check catches violations in CI. The structure maps closely to how most Go developers already think about web services, which makes it easy to navigate and easy to explain.

Flat is main.go and go.mod. No layers, no packages, no prescribed conventions.

main.go
go.mod

You can still add capabilities — http-api works, postgres works — but there’s no layered structure to receive them. Everything coexists in a single package. For small programs, that’s fine. For anything that grows, it becomes the reason you wish you’d used a different architecture.

  • Structure: Layered has four packages with defined responsibilities. Flat has one.
  • Dependency enforcement: Layered prevents handlers from importing repositories, services from importing handlers, etc. Flat has no rules.
  • Testability: Layered’s service layer can be tested without HTTP setup. Layered’s repository layer can be swapped for a fake in service tests. Flat mixes everything.
  • Navigability: Layered’s directory structure tells you where to look for any given piece of code. Flat requires reading all of main.go.
  • Growth: Layered scales reasonably well to 10,000+ lines across multiple handlers and services. Flat does not.
  • You’re building an HTTP API backed by a database
  • The service has multiple handlers and some business logic worth separating from data access
  • You want to be able to test service logic independently from HTTP and database
  • Multiple developers will work on the codebase and you need consistent conventions
  • You expect the service to live in production for more than a few months
  • You’re building a CLI tool, internal script, or one-off utility
  • The entire program is conceptually one thing — no meaningful separation between “handling” and “logic”
  • It’s a prototype that may be thrown away
  • You want to move fast without structural decisions
  • The codebase will stay small enough that a single main.go is readable
Terminal window
# Layered
verikt new my-service --language go --arch layered
# Flat
verikt new my-tool --language go --arch flat