Skip to content

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

Both Clean and Flat architecture are supported by verikt. Clean Architecture imposes Uncle Bob’s ring model — entities, use cases, interface adapters, infrastructure. Flat imposes nothing. The choice is whether your project needs any formal structure at all.

CleanFlat
ComplexityHighLow
Best forComplex domain, production servicesCLI tools, scripts, prototypes
Dependency rulesStrict (ring model)None
Team sizeMedium / LargeAny
Directory structureComplexSimple

Clean Architecture organizes a Go service into concentric rings. Entities at the core have no dependencies. Use cases orchestrate entities. Interface adapters handle translation between use cases and transports. Infrastructure — databases, external APIs, config — sits at the outermost ring and is the only layer allowed to depend on everything.

cmd/<name>/ → Entry point
internal/entity/ → Core business entities (no dependencies)
internal/usecase/ → Application use cases
internal/interface/handler/ → HTTP handlers, adapters
internal/infrastructure/ → Database, external services, config
ComponentMay Depend On
entitynothing
usecaseentity
interfaceusecase, entity
infrastructureinterface, usecase, entity

verikt check enforces these rules in CI. If an entity imports from infrastructure, it’s caught immediately. The structure is designed to keep business logic testable and infrastructure-independent regardless of how the service grows.

Flat is main.go and go.mod. No prescribed layers, no dependency rules, no directory conventions.

main.go
go.mod

Capabilities still work with flat — adding http-api generates HTTP handling code, postgres generates database code — but there’s no ring model to place them in. Everything lives in one place.

The lack of structure is a deliberate feature for the use cases flat is designed for: tools, scripts, and prototypes where layering adds overhead without adding value.

  • Structure overhead: Clean has 4 defined layers with strict rules. Flat has none.
  • Dependency enforcement: Clean enforces import boundaries via verikt check. Flat has no rules to enforce.
  • Testability: Clean Architecture isolates entities and use cases from infrastructure, making them trivially testable. Flat mixes everything, which is fine for small programs and a problem for larger ones.
  • Growth: Clean Architecture holds up as the codebase grows. Flat accumulates coupling quickly and becomes difficult to restructure.
  • Entry point: Clean Architecture has a meaningful distinction between what the system does (entity/usecase) and how it does it (infrastructure). Flat has no such distinction.
  • You’re building a production service with non-trivial business logic
  • Your team reasons about the system in terms of entities and use cases
  • You want your domain logic to be testable without database or HTTP setup
  • The service will grow — more developers, more features, more infrastructure integrations
  • You’re applying Domain-Driven Design and want a structure that reflects that approach
  • You’re building a CLI tool, utility script, or internal tool
  • It’s a proof of concept that may never reach production
  • The code will stay small — a few hundred lines at most
  • There’s no meaningful domain logic to isolate from infrastructure
  • You need to ship something quickly without structural decisions slowing you down
Terminal window
# Clean Architecture
verikt new my-service --language go --arch clean
# Flat
verikt new my-tool --language go --arch flat