When structuring a Go service, most developers start with something like this:
services/qoomlee/handler/
flight.go
booking.go
seat.go
It feels tidy — all handlers live together, all repositories together. But as the project grows, you end up jumping between folders constantly just to work on one feature. Changing a booking flow means touching handler/booking.go, repository/booking.go, service/booking.go — scattered across the tree.
The better approach groups by domain, not layer:
services/qoomlee/
flight/
handler.go
repository.go
booking/
handler.go
repository.go
seat/
handler.go
repository.go
Now everything about bookings lives in booking/. You open one folder, do your work, close it. This is the feature folder (or "vertical slice") pattern — and it scales beautifully.
Why it wins:
- Locality — related code lives together. Less folder-hopping.
- Encapsulation — each domain owns its own types, logic, and data access.
- Deletability — removing a feature means deleting one folder, not hunting across layers.
-
Onboarding — a new dev can own
flight/without understanding the whole codebase. -
Service extraction — when
booking/grows big enough to become its own microservice, it's already self-contained. You move one folder, adjust imports, done. With flat layers, you'd be untangling shared files across the whole tree.
The flat approach optimizes for "things that look alike." The feature approach optimizes for "things that change together" — which is almost always the right trade-off.
Structure your code around the problem, not the solution.
Top comments (0)