9 DTO
Stem question: https://stackoverflow.com/questions/1051182/what-is-a-data-transfer-object-dto
DTO (Data Transfer Object) is a fundamental design pattern, especially important in larger applications. Let me break this down in detail with practical examples.
9.1 What is a DTO?
A Data Transfer Object is essentially a simple container that carries data between different parts of your application or between different applications entirely. Think of it as a “data messenger” - it has no business logic, just holds data and provides access to it.
┌─────────────────┐ DTO ┌─────────────────┐
│ Service A │ ────────→ │ Service B │
│ (Database) │ │ (API/UI) │
└─────────────────┘ └─────────────────┘
9.2 Why Do DTOs Exist?
9.2.1 1. Separation of Concerns
Your internal domain models (database entities) often contain: - Database-specific details - Internal business logic - Relationships that shouldn’t be exposed
DTOs provide a “clean” version of your data for external consumption.
9.2.2 2. Network Efficiency
Instead of sending multiple small requests:
┌─────────┐ Request: Get patient name ┌──────────┐
│ Client │ ──────────────────────────────→ │ Server │
└─────────┘ └──────────┘
┌─────────┐ Request: Get patient age ┌──────────┐
│ Client │ ──────────────────────────────→ │ Server │
└─────────┘ └──────────┘
┌─────────┐ Request: Get patient studies ┌──────────┐
│ Client │ ──────────────────────────────→ │ Server │
└─────────┘ └──────────┘
You send one DTO with all needed data:
┌─────────┐ PatientDTO (name, age, studies) ┌──────────┐
│ Client │ ←────────────────────────────────── │ Server │
└─────────┘ └──────────┘
9.3 Practical Example: Medical Radiology System
Let’s say you’re building a radiology system. Here’s how DTOs work:
9.3.1 Domain Model (Internal - Database Entity)
# This is your internal domain model
class Patient:
def __init__(self):
self.id = None
self.first_name = None
self.last_name = None
self.date_of_birth = None
self.medical_record_number = None
self.insurance_info = None # Sensitive data
self.internal_notes = None # Internal only
self.created_at = None
self.updated_at = None
self.studies = [] # List of Study objects
def calculate_age(self):
# Business logic here
pass
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
9.3.2 DTO (External - Data Transfer)
# This is what you send to the frontend/API
class PatientDTO:
def __init__(self, patient_id, full_name, age, studies_summary):
self.patient_id = patient_id
self.full_name = full_name # Already computed
self.age = age # Already computed
self.studies_summary = studies_summary # Simplified data
def to_dict(self):
return {
'patient_id': self.patient_id,
'full_name': self.full_name,
'age': self.age,
'studies_summary': self.studies_summary
}
9.3.3 DTO Assembler/Mapper
class PatientDTOAssembler:
@staticmethod
def to_dto(patient: Patient) -> PatientDTO:
"""Convert domain model to DTO"""
= [
studies_summary
{'study_id': study.id,
'modality': study.modality,
'study_date': study.date.isoformat(),
'status': study.status
}for study in patient.studies
]
return PatientDTO(
=patient.id,
patient_id=patient.get_full_name(),
full_name=patient.calculate_age(),
age=studies_summary
studies_summary
)
@staticmethod
def from_dto(dto: PatientDTO) -> Patient:
"""Convert DTO back to domain model (if needed)"""
# This is less common, but sometimes necessary
= Patient()
patient id = dto.patient_id
patient.# Note: You'd need additional data to fully reconstruct
return patient
9.4 Architecture Flow
┌─────────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ Database │ │ Domain │ │ DTO │ │ Frontend │
│ (Raw Data) │ │ Models │ │ (Clean Data)│ │ (Display) │
│ │ │ │ │ │ │ │
│ • patient_tbl │───→│ • Patient │───→│ PatientDTO │───→│ • UI Display │
│ • study_tbl │ │ • Study │ │ StudyDTO │ │ • API Resp │
│ • internal_data │ │ • Business │ │ • No Logic │ │ │
│ │ │ Logic │ │ • Clean │ │ │
└─────────────────┘ └──────────────┘ └─────────────┘ └──────────────┘
9.5 When to Use DTOs
9.5.1 ✅ Use DTOs When:
- Building APIs (REST, GraphQL)
- Communicating between microservices
- Need to hide internal implementation details
- Working with distributed systems
- Frontend needs different data structure than backend
9.5.2 ❌ Skip DTOs When:
- Simple CRUD applications
- Single-tier applications
- Internal methods within same service
- Performance is critical and transformation cost is high
9.6 Real-World Example: Radiology API
# API Endpoint using DTO
from flask import Flask, jsonify
= Flask(__name__)
app
@app.route('/api/patients/<int:patient_id>')
def get_patient(patient_id):
# 1. Fetch domain model from database
= patient_service.get_by_id(patient_id)
patient
# 2. Convert to DTO
= PatientDTOAssembler.to_dto(patient)
patient_dto
# 3. Return clean JSON
return jsonify(patient_dto.to_dict())
# Response looks clean:
{"patient_id": 12345,
"full_name": "John Doe",
"age": 45,
"studies_summary": [
{"study_id": 67890,
"modality": "CT",
"study_date": "2024-09-20",
"status": "completed"
}
] }
9.7 Key Benefits for Your Medical Applications
- Privacy Protection: DTOs help ensure you don’t accidentally expose sensitive patient data
- API Versioning: You can change internal models without breaking client applications
- Network Efficiency: Send only necessary data to mobile radiology apps
- Clean Interfaces: Frontend developers get predictable, documented data structures
Think of DTOs as “business cards” for your data - they contain just the essential information needed for a specific interaction, without all the internal complexities.