Go Service Architecture
While a service fits in one file, you need not think about architecture — the handler goes to the DB itself, builds the JSON itself, computes the business rules itself. Architecture begins where that simplicity is gone: the service grows, a second transport appears, the external API changes, you want to swap storage in a test. Here what matters is not the volume of code but the boundaries between its parts and the direction of dependencies across those boundaries.
The central trap of this topic is pointing a dependency outward: letting the domain layer import the DB driver, loading JSON tags, db tags, and business methods onto one struct, closing the DB client before the HTTP server. Each such decision couples what should be decoupled, and the cost arrives later — when storage cannot be swapped, renaming a JSON field breaks business logic, and shutting the service down crashes requests on an already-dead connection. This topic breaks architecture into layers — from the dependency rule down to a correct process shutdown.
Topic Map
- Layered architecture — splitting into transport, domain, and storage, and the rule that dependencies point inward.
- Clean architecture — concentric rings and the dependency rule pointing only inward, via dependency inversion.
- The adapter pattern — a stable internal interface hiding a volatile external API or format.
- DTO versus domain entity — a transport-shaped data carrier versus a holder of business state, invariants, and behaviour.
- Graceful shutdown — closing components in reverse dependency order while draining in-flight requests.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
| Pointing dependencies outward — the domain imports the DB driver | Business rules are coupled to a concrete DBMS, no edge can be swapped |
| Confusing layers with separately deployed services | Wrong model — layers live inside one process, the boundary is logical |
| Scattering business logic across transport handlers | The domain layer goes anemic, layering is in name only |
| Thinking the dependency in clean architecture follows the call | The call flows outward, the source dependency points inward; an interface reverses the arrow |
Thinking that copying an external struct into your package isolates it | The copy mirrors the foreign shape — on a format change you edit it and every caller |
| Placing the adapter in the domain layer | The external dependency creeps inside and infects business rules with a volatile API |
Reusing one struct as DTO and domain entity | JSON tags, db tags, and the API shape leak into business logic — three reasons to change one type |
| Hanging domain invariants on a DTO | A DTO is recreated per request and is not the source of truth — an invariant on it guards nothing |
| Closing the DB client before the HTTP server on shutdown | Draining requests hit a dead connection — the user sees a 500 |
| Shutting down without a drain timeout | Shutdown can hang forever if requests never finish |
Interview Relevance
Architecture is a mandatory topic at the senior level of a Go interview, and the question is not a whiteboard diagram but an understanding of boundaries and the direction of dependencies. The interviewer checks whether you separate business rules from details — transport, storage, external APIs — or pile everything into one layer.
What interviewers usually check:
- The rule of layered architecture — dependencies point inward, the domain imports neither transport nor DB.
- How a layer differs from a service — a layer is a boundary in code, a service a boundary of deployment; all layers ship in one binary.
- How dependency inversion reverses the arrow — the consumer declares the interface, the implementation is supplied from outside.
- Why an adapter is needed — to absorb a change in an external API in one place behind a stable interface, and why it does not belong in the domain.
- Why a DTO and a domain entity are separate types, and what leaks if you merge them into one.
- Why shutdown runs in reverse dependency order and why in-flight requests are drained under a
contextwith a timeout.
A typical wrong answer: "the domain can go to the database itself, it's faster." This triggers a discussion that importing the DB driver directly into the domain nails business rules to a concrete DBMS, breaks testability, and makes storage unswappable — while the right boundary rests on an interface the domain declares and an adapter implements from outside.