Go architectures · 3 MIN READ
Layered architecture: transport, application, storage
Separate responsibilities without confusing logical layers with deployed tiers.
Separate responsibilities without confusing logical layers with deployed tiers. A layered service routes requests through transport, application logic and persistence. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Architecture · A MAP OF THE IDEA
- Transport decodes input
- Application validates a use case
- Storage persists data
Useful when
- A service has distinct request and storage concerns
- A team benefits from a predictable call path
Watch out
- Features can require edits across several layers
- Thin pass-through layers add ceremony
Implementation & operation cost: Medium
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
A layered service routes requests through transport, application logic and persistence. Logical layers may live in the same executable. Avoid a handler bypassing validation to write directly to storage, and avoid pass-through types that only rename the next function.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"errors"
"fmt"
)
type Store struct{ names []string }
func (s *Store) Save(name string) { s.names = append(s.names, name) }
type Service struct{ store *Store }
func (s Service) Register(name string) error {
if name == "" {
return errors.New("name required")
}
s.store.Save(name)
return nil
}
func main() {
store := &Store{}
service := Service{store: store}
fmt.Println(service.Register("Ada"))
fmt.Println(store.names)
}
// Output:
// <nil>
// [Ada]
Register enforces a rule before Store changes. main acts as the transport for this small example and prints success followed by the stored name. Concrete dependencies are enough here; a boundary interface can be introduced when there is a caller need.
Tradeoffs and the simpler alternative
A feature may touch every horizontal layer, so keep mapping work proportional to the value it protects. Layering alone does not invert dependencies, create transactions or make storage replaceable. A single function is simpler for a one-off script.
MVC is a presentation organization
Model–View–Controller separates a model of state and behavior, a view that renders output, and a controller that translates user input into operations. A Go web handler can act as a controller and a template as the view. This does not require a database model for every view, nor does it decide whether the application is one process or many services. Keep business invariants outside presentation formatting; use html/template rather than concatenating untrusted HTML.
Trace dependency direction through a real request
An HTTP layer decodes a request and maps a response; an application layer executes a use case; a storage adapter persists its state. The application should receive typed intent, not an http.Request. Likewise, database rows should not automatically become public JSON. Separate request DTOs from domain values when their validation and compatibility rules differ. Avoid a layer whose only purpose is forwarding every call unchanged to another layer.
Put the transaction around the business operation
A transaction belongs to the complete use case, such as moving funds between accounts. Starting and committing inside each repository method makes the two balance changes independent. Pass transaction-scoped capabilities to the use case or offer one atomic domain operation. Keep validation errors distinct from infrastructure failures, and do not convert every failure into a nil result. A layered diagram does not itself establish atomicity, authorization or idempotency.
Reduce horizontal change amplification
If one small feature repeatedly changes controller, manager, service, repository and mapper with no distinct decision in most layers, collapse unnecessary forwarding. Organize files by feature within a layer or introduce a vertical slice. Preserve stable cross-cutting policy such as authentication at a clear boundary. Test each layer only for the behavior it owns: request mapping, business invariant or persistence contract, rather than duplicating the same happy-path assertion five times.
TAKE THIS WITH YOU
Separate responsibilities without requiring network tiers. A feature may touch every horizontal layer, so keep mapping work proportional to the value it protects. Layering alone does not invert dependencies, create transactions or make storage replaceable. A single function is simpler for a one-off script.
Check your understanding
Which statement best describes Layered architecture: transport, application, storage?
Go to the source
Progress stays in this browser. No account needed.