Skip to content

DTO Pattern — Data Transfer Object for Network Communication

DodaTech Updated 2026-06-24 4 min read

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.proto message 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.
  • 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

  1. When would you use a DTO versus returning the domain entity directly in a monolithic application?
  2. Implement a DTO versioning Strategy where v1 and v2 DTOs coexist for API backward compatibility.
  3. How would you handle nested object graphs in DTOs — should they contain nested DTOs or flat IDs?
  4. 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