DTO
A struct whose only job is carrying data across a boundary, keeping domain types independent of their wire or storage format.
A DTO (Data Transfer Object) is a struct whose only job is crossing the boundary between a domain type and its external representation. It moves data in one direction: from your domain to the outside world, or back again. There's no behavior, validation, or business rules.
Why bother with a separate struct? Because your domain model and your transfer format are two different things. The domain type uses encapsulation, a constructor that enforces invariants, and methods that implement business logic. The JSON or database representation can have public fields and a flat data layout that external clients depend on. Trying to make one struct do both jobs leads to wrong abstractions, strong coupling, and accidental complexity.
In Go, a DTO is typically a struct with exported fields and JSON or database tags.
You map the domain object onto the DTO, then let json.Marshal or the database driver take it from there.
On the way back, you unmarshal into the DTO first, then use the domain constructor to create a valid instance.
DTOs are often code-generated when following the api-first design, saving you from writing boilerplate code by hand.
The pattern shows up often at boundaries in a Clean Architecture project. HTTP handlers convert between API DTOs and domain types, repositories convert between database DTOs and aggregates, and event publishers convert between event payloads and internal state. Each DTO is specific to its boundary. An API DTO and a database DTO for the same domain type can look completely different.
Not every type needs a DTO. If your struct is a plain data carrier with exported fields and no invariants, adding a DTO layer is ceremony without benefit. The pattern pays off for types where invariants matter: value objects with unexported fields, entities with complex state, etc.
References
- When you shouldn't use frameworks in Go — Discusses when DTOs are worth the overhead and when a single model is enough. Mentions introducing transport DTOs for domain layer encapsulation in the Wild Workouts project, but notes it's not always needed for simpler models or smaller teams.