Concept · Architecture & domain modeling
Package by feature
Organise code by business capability rather than technical layer, so a change to one feature stays in one place and boundaries can be enforced.
2 minpackage by layerfeature foldersvertical slicesapi and implementation modules
Package by layer groups code by technical role: all handlers together, all services together, all repositories together. Package by feature groups it by what it does for the business: everything about orders together, everything about payments together.
By layer By feature
internal/ internal/
handlers/ order.go user.go ordering/ handler.go service.go store.go
services/ order.go user.go accounts/ handler.go service.go store.go
repos/ order.go user.go payments/ handler.go service.go store.go
models/ order.go user.go platform/ database/ httpserver/ config/Why feature packages scale better
- Changes stay local. Adding a field to orders touches one directory instead of four. That's high cohesion: code that changes together lives together.
- Encapsulation becomes possible. In a layered layout, everything in
modelsmust be exported forhandlersandservicesto use it, so every feature can reach every other feature's internals. In a feature package, most identifiers can stay unexported, and Go'sinternal/directories can hide a feature's implementation from the rest of the codebase entirely. - The structure tells you what the system does. A newcomer reading
ordering/,payments/,delivery/learns the business; readinghandlers/,services/learns nothing. - Ownership is clear. A team can own a directory, and boundaries map naturally onto bounded contexts or future services.
Layers don't disappear — they move inside each feature, where they still separate HTTP from logic from storage.
In practice
- Keep shared infrastructure (database connection, HTTP server setup, config) in a
platformpackage that features depend on, never the reverse. - Avoid catch-all
utilsorcommonpackages; put helpers in the feature that uses them, or name a package for what it does (money,slug). - When features need each other, go through a small public API rather than reaching into implementation files.
- Large apps go further and split each feature into api and implementation modules, so features depend only on each other's APIs — the pattern used by large Android and Gradle codebases.
Very small services can stay flat in one package; feature packages pay off once there are several distinct capabilities. See the modular design lesson.