Just shipped a pretty substantial refactor on our main service. We started with the classic "everything in main.go" and it was getting unwieldy. Instead of jumping straight to microservices like everyone suggests, I spent two weeks restructuring it as a proper monolith first.
The trick was enforcing boundaries at the package level before they become runtime boundaries. Created explicit handler, service, and repository layers with dependency injection at the entry point. Made internal packages actually internal (Go's package visibility is underrated). Zero runtime coupling, easy to extract later.
// cmd/api/main.go does DI once
repo := postgres.NewUserRepo(db)
svc := service.NewUserService(repo)
handler := http.NewUserHandler(svc)
Turns out if you structure it right, extracting a service later is just changing imports and adding gRPC. Already had three teams asking "can we move X to its own service" and now it's a two-day project instead of a rewrite. Saved us probably six months of architectural thrashing.
No responses yet.