Go Architecture: Start With Boundaries, Not a Framework
Go does not give you one official application framework. Start with clear package boundaries, a simple layered design, and only add architecture when it improves delivery.
Go gives you a lot of freedom, perhaps too much when you are building your first service.
There is no official equivalent of Spring Boot or Django that decides where everything belongs. Search for the correct Go architecture and you will find many confident GitHub repositories, but no official answer from the Go team.
Which Go architecture should I choose?
I get this question often. My boring answer is: start with boundaries, not with an architecture diagram.
The first useful boundary
For a small API, three areas are often enough:
- transport, such as HTTP handlers
- business logic
- data access
Call them API, service, and repository if you like. Call it layered architecture or onion architecture. I do not care much about the label. I care whether a new engineer can answer: where does this change belong, and what is it allowed to call?
In a typical executable Go project, I also expect to see:
cmd/for executable entry pointsinternal/for application code that should not become a public dependency- small packages with clear responsibilities
I like internal/ because it is not architecture theatre. The compiler enforces it. A slide cannot stop another package from importing something it should not.
Reuse must be genuinely reusable
A common mistake is moving application-specific behavior into a shared package because it looks reusable today.
A shared logger, tracer, or organization-wide monitoring setup can belong in a reusable package. A business API usually should not.
One real example from my work involved monetary rounding. Different countries had different rules. Hungary, in particular, had enough rounding conditions to make the supposedly simple helper surprisingly unpleasant. Several teams processing money needed exactly those rules, so sharing the implementation made sense.
The test is not “can two callers import this?” The test is “does this represent a stable capability that several applications genuinely share?”
Sophisticated architecture can make delivery worse
I once worked on a new Go service designed around a sophisticated domain-driven architecture. The team was not junior. We had senior and Staff engineers, managers, and plenty of discussion about doing architecture properly.
After six months, we still could not ship anything useful.
The older monolith could deliver a comparable feature in roughly three months.
We replaced much of that design with a more direct controller-and-service style. Time to market fell from roughly six months to around two weeks. Later, we launched one country in a day.
That was a useful correction for me. Experienced engineers can overbuild a service just as effectively as beginners can.
A practical decision rule
Start simple. Add another layer, abstraction, or pattern when you can point to the pain it removes:
- duplicated business rules
- unstable external dependencies
- difficult testing
- unclear ownership
- changes leaking across boundaries
If the explanation is mostly architectural vocabulary, wait. You can always add complexity later. Removing it after six months is much more expensive.
For a broader review process, see architecture review mentoring and system design coaching.
About the author
Aleksandr Perederei is a Principal Engineer, former Staff Software Engineer, Engineering Manager, and CTO. He has mentored 120+ engineers on system design, technical leadership, promotion evidence, career direction, and stronger engineering judgment.
Related articles
Use Go for LLM Orchestration, Not Model Research
Go is a strong choice for production LLM orchestration, typed tool boundaries, and agent services. Python remains the practical choice for model research and evaluation.
GoGo Tests Are Permission to Change the System
The most valuable Go tests do more than raise coverage. They expose business assumptions, protect critical workflows, and make refactoring and AI-assisted changes safer.
GoEverybody Talks About AI -- Let's Talk About Code Generators in Go
Explore Go's powerful code generation ecosystem: sqlc for type-safe SQL, OpenAPI generators for API clients and servers, and go generate as the glue. Practical examples with OpenAI, Stripe, and more.
Get engineering articles in your inbox
Practical advice on system design, technical leadership, and career growth. No spam.