Hexagonal Architecture
The next step up — stronger separation via ports and adapters when layered feels limiting. Hexagonal Architecture
Layered architecture (also called n-tier or three-tier architecture) organizes a service into horizontal layers, each responsible for a distinct concern. The canonical three layers are presentation (HTTP handlers), business logic (services), and data access (repositories), with a shared model package for types used across layers.
It’s the most widely understood architecture pattern in backend development — most Go developers will recognize it immediately. Each layer calls down to the layer below, and dependencies point in one direction: handlers call services, services call repositories. Repositories never call services, services never call handlers.
The pattern’s strength is its familiarity and simplicity. For straightforward CRUD APIs, an order service with a handler, service, and repository package is easier to reason about than a full hexagonal architecture with separate port/ and adapter/ directories. The trade-off is that the model package creates a shared dependency between all layers, which can lead to tight coupling as the codebase grows.
verikt’s layered architecture scaffold produces:
cmd/<name>/ → Entry pointinternal/handler/ → HTTP request handling — parsing, validation, response formattinginternal/service/ → Business logic — rules, orchestration, decisionsinternal/repository/ → Data access — queries, persistence, transactionsinternal/model/ → Shared types — request/response structs, domain modelsDependency direction:
| Layer | May Depend On |
|---|---|
| handler | service, model |
| service | repository, model |
| repository | model |
| model | nothing |
The model package is the shared vocabulary. Every layer imports from it, but model imports from nothing.
In practice, each layer holds a struct with its dependencies injected:
type OrderService struct { repo repository.OrderRepository}
func (s *OrderService) CreateOrder(ctx context.Context, req model.CreateOrderRequest) (*model.Order, error) { // business rules here}The handler receives an OrderService interface (not the concrete struct), making it testable. The repository also hides behind an interface, allowing the service tests to run without a database.
verikt scaffolds the full layered structure and encodes dependency rules in verikt.yaml. Run verikt check to catch violations — a handler importing directly from a repository, or a service calling another service’s handler. See Architectures for the full comparison with hexagonal and clean.
Hexagonal Architecture
The next step up — stronger separation via ports and adapters when layered feels limiting. Hexagonal Architecture
Repository Pattern
The data access layer in detail — interfaces, generics, and the data access contract. Repository Pattern
Architectures
Full comparison of all four architectures verikt supports. Architectures