DTO Pattern — Data Transfer Object for Network Communication
What You'll Learn
You will learn how the Data Transfer Object (DTO) pattern packages aggregated data for efficient network transfer, and how to distinguish DTOs from domain entities. You will also see how Assembler classes convert between domain models and DTOs.
Why It Matters
In Distributed Systems, every remote call incurs latency and bandwidth cost. Fetching a customer's profile, order history, and loyalty points in three separate calls is inefficient. DTO aggregates all needed data into a single object that can be serialised and transmitted in one round trip. This reduces network overhead from three round trips to one, a 3x improvement in latency alone.
Real-World Use
REST APIs return JSON payloads that are DTOs — they contain exactly the fields the client needs, no more. DodaTech's microservice gateway assembles DTOs from multiple backend services, returning a unified response to the frontend without exposing internal data models. The frontend never needs to make three separate API calls; the gateway handles aggregation transparently.
The Pattern
DTO is a simple container with no business logic. Assembler creates DTOs from domain objects. The client receives the DTO and uses it for presentation or further processing.
from dataclasses import dataclass, asdict
from typing import Optional
import json
@dataclass
class OrderItemDTO:
product_sku: str
product_name: str
quantity: int
unit_price: float
@property
def total(self) -> float:
return self.quantity * self.unit_price
@dataclass
class OrderDTO:
order_id: str
customer_name: str
items: list[OrderItemDTO]
shipping_address: str
@property
def grand_total(self) -> float:
return sum(item.total for item in self.items)
class OrderDTOAssembler:
@staticmethod
def to_dto(order: "Order", customer: "Customer") -> OrderDTO:
items = [
OrderItemDTO(
product_sku=item["sku"],
product_name=item["name"],
quantity=item["qty"],
unit_price=item["price"]
)
for item in order["items"]
]
return OrderDTO(
order_id=order["id"],
customer_name=customer["name"],
items=items,
shipping_address=customer["address"]
)
class OrderService:
def __init__(self):
self._orders = {}
self._customers = {}
def place_order(self, order_data: dict, customer_data: dict):
self._orders[order_data["id"]] = order_data
self._customers[customer_data["id"]] = customer_data
def get_order_dto(self, order_id: str, customer_id: str) -> OrderDTO:
order = self._orders[order_id]
customer = self._customers[customer_id]
return OrderDTOAssembler.to_dto(order, customer)
service = OrderService()
service.place_order(
{"id": "ORD-001", "items": [
{"sku": "A1", "name": "Widget", "qty": 2, "price": 10.0},
{"sku": "B2", "name": "Gadget", "qty": 1, "price": 25.0}
]},
{"id": "C1", "name": "Alice", "address": "123 Main St"}
)
dto = service.get_order_dto("ORD-001", "C1")
print(json.dumps(asdict(dto), indent=2))
{
"order_id": "ORD-001",
"customer_name": "Alice",
"items": [
{
"product_sku": "A1",
"product_name": "Widget",
"quantity": 2,
"unit_price": 10.0
},
{
"product_sku": "B2",
"product_name": "Gadget",
"quantity": 1,
"unit_price": 25.0
}
],
"shipping_address": "123 Main St"
}
Structure
classDiagram
class Client {
+requestData()
}
class DTO {
-fields
+getters()
}
class Assembler {
+toDTO(entity): DTO
+toEntity(dto): Entity
}
class Entity {
-businessData
+businessMethod()
}
class RemoteService {
+fetchData(): DTO
}
Client --> RemoteService : requests
RemoteService --> Assembler : uses
Assembler --> Entity : reads
Assembler --> DTO : creates
RemoteService --> DTO : returns
Real-World Usage
- RESTful APIs — JSON/XML responses are DTOs; request bodies are also DTOs deserialised from payloads.
- Java Spring
@RestController— methods return DTOs that Jackson serialises to JSON. - gRPC protocol buffers —
.protomessage definitions are DTO schemas compiled to language-specific classes. - GraphQL responses — the resolved data shape matches the query's DTO structure.
- Java EE remote EJBs — DTOs transfer data between JVM instances over RMI.
Related Patterns
- DAO returns DTOs from database queries.
- Repository may return domain objects; an Assembler converts them to DTOs.
- Adapter can convert between different DTO versions for backward compatibility.
- Facade often assembles DTOs from multiple subsystem responses.
- Serialization is the mechanism that transports DTOs over the wire.
Pros and Cons
| Pros | Cons |
|---|---|
| Reduces number of remote calls | Adds boilerplate assembling code |
| Decouples internal domain from external API | DTOs can become stale when domain changes |
| Allows shaping data specifically for client needs | Serialisation overhead for large DTOs |
| Versioning DTOs independently of domain model | Proliferation of DTO classes in complex systems |
| Improves security — no internal fields leaked | Assembler must be maintained alongside entity changes |
The code shows OrderDTO and OrderItemDTO classes with computed properties (total, grand_total). The OrderDTOAssembler converts raw order and customer data into a DTO structure. The DTO is then serialised to JSON using json.dumps(asdict(dto)). The assembler pattern keeps conversion logic separated from both the domain and the DTO.
Practice Questions
- When would you use a DTO versus returning the domain entity directly in a monolithic application?
- Implement a DTO versioning Strategy where v1 and v2 DTOs coexist for API backward compatibility.
- How would you handle nested object graphs in DTOs — should they contain nested DTOs or flat IDs?
- Compare DTO with projections or ViewModels in MVC — when is each appropriate?
Challenge
Implement a DTO assembler that supports partial responses — the client specifies which fields they want (like GraphQL), and the assembler only includes those fields. This reduces payload size for clients that only need a subset of data.
Real-World Task
Use DodaTech's API diff tool to compare your API response structure across two deployments. Identify fields that have changed and implement a versioned DTO Strategy to maintain backward compatibility.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro