Last active 1749231863

Data Transfer Objects (DTOs) Explained for Beginners (Java, then Python)

kristofer's Avatar kristofer revised this gist 1749231863. Go to revision

No changes

kristofer's Avatar kristofer revised this gist 1749231827. Go to revision

2 files changed, 283 insertions, 1 deletion

dtointro.md

@@ -1,4 +1,4 @@
1 - Data Transfer Objects (DTOs) Explained for Beginners
1 + # Data Transfer Objects (DTOs) via Java; Explained for Beginners
2 2
3 3 ## What Are DTOs?
4 4

pythonDTO.md(file created)

@@ -0,0 +1,282 @@
1 + # DTOs in Python: A Pragmatic Approach
2 +
3 + Yes, Python developers absolutely use DTOs, but they often look different from Java or C# implementations. The concept remains the same - transferring data between layers - but Python's dynamic nature allows for more flexible approaches.
4 +
5 + ## Common Python DTO Patterns
6 +
7 + ### 1. Dictionaries
8 +
9 + The simplest form of DTOs in Python - plain dictionaries:
10 +
11 + ```/dev/null/dict_dto.py#L1-14
12 + def get_user_profile(user_id):
13 + # Fetch user from database
14 + user = User.query.get(user_id)
15 +
16 + # Create a dictionary that serves as a DTO
17 + user_dto = {
18 + 'id': user.id,
19 + 'username': user.username,
20 + 'email': user.email
21 + # Note: password_hash is intentionally excluded
22 + }
23 +
24 + return user_dto
25 + ```
26 +
27 + ### 2. Dataclasses (Python 3.7+)
28 +
29 + A more structured approach using Python's dataclasses:
30 +
31 + ```/dev/null/dataclass_dto.py#L1-17
32 + from dataclasses import dataclass, asdict
33 +
34 + @dataclass
35 + class UserDTO:
36 + id: int
37 + username: str
38 + email: str
39 +
40 + def get_user_profile(user_id):
41 + user = User.query.get(user_id)
42 +
43 + # Create a DTO from the entity
44 + user_dto = UserDTO(
45 + id=user.id,
46 + username=user.username,
47 + email=user.email
48 + )
49 +
50 + return user_dto # or asdict(user_dto) for dictionary conversion
51 + ```
52 +
53 + ### 3. Pydantic Models (Popular in FastAPI)
54 +
55 + Pydantic provides data validation and is widely used for DTOs in FastAPI:
56 +
57 + ```/dev/null/pydantic_dto.py#L1-23
58 + from pydantic import BaseModel
59 + from typing import List
60 +
61 + class OrderItemDTO(BaseModel):
62 + id: int
63 + product_name: str
64 + quantity: int
65 + price: float
66 +
67 + class OrderDetailDTO(BaseModel):
68 + id: int
69 + customer_name: str
70 + total: float
71 + items: List[OrderItemDTO]
72 +
73 + @app.get("/orders/{order_id}")
74 + def get_order(order_id: int):
75 + order = db.get_order(order_id)
76 +
77 + # Pydantic automatically handles the conversion
78 + return OrderDetailDTO(
79 + id=order.id,
80 + customer_name=order.customer.name,
81 + total=order.total,
82 + items=[OrderItemDTO(**item.__dict__) for item in order.items]
83 + )
84 + ```
85 +
86 + ### 4. Named Tuples
87 +
88 + Lightweight immutable DTOs:
89 +
90 + ```/dev/null/namedtuple_dto.py#L1-12
91 + from collections import namedtuple
92 +
93 + # Define DTO structure
94 + UserDTO = namedtuple('UserDTO', ['id', 'username', 'email'])
95 +
96 + def get_user_profile(user_id):
97 + user = User.query.get(user_id)
98 +
99 + # Create a named tuple as DTO
100 + user_dto = UserDTO(id=user.id, username=user.username, email=user.email)
101 +
102 + return user_dto
103 + ```
104 +
105 + ## Framework-Specific Approaches
106 +
107 + ### Django REST Framework Serializers
108 +
109 + Django's approach to DTOs is through serializers:
110 +
111 + ```/dev/null/django_serializers.py#L1-19
112 + from rest_framework import serializers
113 +
114 + class OrderItemSerializer(serializers.Serializer):
115 + id = serializers.IntegerField()
116 + product_name = serializers.CharField()
117 + quantity = serializers.IntegerField()
118 + price = serializers.DecimalField(max_digits=10, decimal_places=2)
119 +
120 + class OrderSerializer(serializers.Serializer):
121 + id = serializers.IntegerField()
122 + customer_name = serializers.CharField()
123 + total = serializers.DecimalField(max_digits=10, decimal_places=2)
124 + items = OrderItemSerializer(many=True)
125 +
126 + # Usage in view
127 + @api_view(['GET'])
128 + def get_order(request, order_id):
129 + order = Order.objects.get(id=order_id)
130 + serializer = OrderSerializer(order)
131 + return Response(serializer.data)
132 + ```
133 +
134 + ### SQLAlchemy ORM with Marshmallow
135 +
136 + Combining SQLAlchemy with Marshmallow for serialization:
137 +
138 + ```/dev/null/marshmallow_example.py#L1-28
139 + from marshmallow import Schema, fields
140 +
141 + # Database entity
142 + class User(db.Model):
143 + id = db.Column(db.Integer, primary_key=True)
144 + username = db.Column(db.String)
145 + email = db.Column(db.String)
146 + password_hash = db.Column(db.String)
147 + created_at = db.Column(db.DateTime)
148 +
149 + # DTO schema
150 + class UserSchema(Schema):
151 + id = fields.Int()
152 + username = fields.Str()
153 + email = fields.Str()
154 + # Note: password_hash is excluded
155 +
156 + user_schema = UserSchema()
157 +
158 + @app.route('/users/<int:user_id>')
159 + def get_user(user_id):
160 + user = User.query.get_or_404(user_id)
161 +
162 + # Convert to DTO
163 + user_dto = user_schema.dump(user)
164 +
165 + return jsonify(user_dto)
166 + ```
167 +
168 + ## When to Use DTOs in Python
169 +
170 + Even though Python is more flexible, you should still consider using DTOs when:
171 +
172 + 1. **API Boundaries**: Exposing data through REST APIs
173 + 2. **Security Concerns**: Need to filter sensitive data
174 + 3. **Serialization Requirements**: Converting between formats (JSON, XML, etc.)
175 + 4. **Domain Separation**: Keeping domain models separate from presentation
176 + 5. **Documentation**: Making API contracts explicit (especially with Pydantic and OpenAPI)
177 +
178 + ## Comparing Python and Java DTO Approaches
179 +
180 + ```/dev/null/comparison.txt#L1-12
181 + ┌───────────────────┬────────────────────────┬────────────────────────┐
182 + │ Aspect │ Java │ Python │
183 + ├───────────────────┼────────────────────────┼────────────────────────┤
184 + │ Formality │ Highly structured │ More pragmatic │
185 + │ Type Safety │ Compile-time checking │ Runtime verification │
186 + │ Implementation │ Explicit classes │ Various options │
187 + │ Mapping │ MapStruct, ModelMapper │ Direct assignment, │
188 + │ │ │ marshmallow, Pydantic │
189 + │ Verbosity │ More boilerplate │ Less boilerplate │
190 + │ Integration │ Separate from │ Often integrated with │
191 + │ │ serialization │ serialization │
192 + └───────────────────┴────────────────────────┴────────────────────────┘
193 + ```
194 +
195 + ## Practical Python DTO Example for a RESTful API
196 +
197 + Here's a complete example using Flask, SQLAlchemy, and dataclasses:
198 +
199 + ```/dev/null/flask_example.py#L1-56
200 + from flask import Flask, jsonify
201 + from flask_sqlalchemy import SQLAlchemy
202 + from dataclasses import dataclass
203 + from typing import List
204 +
205 + app = Flask(__name__)
206 + db = SQLAlchemy(app)
207 +
208 + # Database models
209 + class Order(db.Model):
210 + id = db.Column(db.Integer, primary_key=True)
211 + customer_name = db.Column(db.String)
212 + customer_email = db.Column(db.String)
213 + total = db.Column(db.Float)
214 +
215 + class OrderItem(db.Model):
216 + id = db.Column(db.Integer, primary_key=True)
217 + order_id = db.Column(db.Integer, db.ForeignKey('order.id'))
218 + product_name = db.Column(db.String)
219 + product_sku = db.Column(db.String)
220 + quantity = db.Column(db.Integer)
221 + price = db.Column(db.Float)
222 + order = db.relationship('Order', backref='items')
223 +
224 + # DTOs using dataclasses
225 + @dataclass
226 + class OrderItemListDTO:
227 + id: int
228 + product_name: str
229 + quantity: int
230 + price: float
231 +
232 + @dataclass
233 + class OrderListDTO:
234 + id: int
235 + customer_name: str
236 + total: float
237 +
238 + @dataclass
239 + class OrderDetailDTO:
240 + id: int
241 + customer_name: str
242 + total: float
243 + items: List[OrderItemListDTO]
244 +
245 + # API endpoints
246 + @app.route('/api/orders')
247 + def get_orders():
248 + orders = Order.query.all()
249 + # Convert to ListOfMasters DTOs
250 + orders_dto = [OrderListDTO(id=o.id, customer_name=o.customer_name, total=o.total)
251 + for o in orders]
252 + return jsonify(orders_dto)
253 +
254 + @app.route('/api/orders/<int:order_id>')
255 + def get_order(order_id):
256 + order = Order.query.get_or_404(order_id)
257 + # Convert to DetailOfMaster DTO with embedded ListOfDetails
258 + items_dto = [OrderItemListDTO(id=i.id, product_name=i.product_name,
259 + quantity=i.quantity, price=i.price)
260 + for i in order.items]
261 +
262 + order_dto = OrderDetailDTO(
263 + id=order.id,
264 + customer_name=order.customer_name,
265 + total=order.total,
266 + items=items_dto
267 + )
268 + return jsonify(order_dto)
269 + ```
270 +
271 + ## Conclusion
272 +
273 + Python definitely uses the DTO pattern, but with a more pragmatic and flexible approach than
274 + strictly typed languages. The key principles remain the same:
275 +
276 + 1. **Separate concerns** between database models and API responses
277 + 2. **Control data exposure** to prevent leaking sensitive information
278 + 3. **Optimize data transfer** by including only what's needed
279 + 4. **Make API contracts clear** for consumers
280 +
281 + Whether you call them DTOs, serializers, schemas, or just data containers, the pattern serves the same essential purpose across all languages - creating a
282 + clear boundary between your internal data models and what you expose to the outside world.

kristofer's Avatar kristofer revised this gist 1749231587. Go to revision

1 file changed, 195 insertions

dtointro.md(file created)

@@ -0,0 +1,195 @@
1 + Data Transfer Objects (DTOs) Explained for Beginners
2 +
3 + ## What Are DTOs?
4 +
5 + A Data Transfer Object (DTO) is a simple container for data that travels between different parts of your application. Think of a DTO like an envelope specifically designed to carry exactly what's needed—no more, no less.
6 +
7 + ```/dev/null/OrderDTO.java#L1-7
8 + public class OrderDTO {
9 + private Long id;
10 + private String customerName;
11 + private BigDecimal total;
12 + // Getters and setters
13 + }
14 + ```
15 +
16 + ## Why Do We Use DTOs?
17 +
18 + Imagine you're ordering food at a restaurant:
19 +
20 + 1. You don't need to see everything happening in the kitchen (the database and business logic)
21 + 2. The waiter doesn't bring the entire kitchen to your table
22 + 3. The menu (DTO) shows you just what you need to know
23 +
24 + DTOs serve a similar purpose in programming. They:
25 +
26 + 1. **Separate concerns**: Keep your database entities separate from what you show to users
27 + 2. **Control data exposure**: Share only the information that's needed
28 + 3. **Optimize data transfer**: Reduce the amount of data sent over the network
29 +
30 + ## Real-World Example
31 +
32 + Let's say you have a User entity in your database:
33 +
34 + ```/dev/null/User.java#L1-10
35 + @Entity
36 + public class User {
37 + @Id
38 + private Long id;
39 + private String username;
40 + private String email;
41 + private String passwordHash; // Sensitive!
42 + private Date createdAt;
43 + // More fields and methods...
44 + }
45 + ```
46 +
47 + When showing user information in your app, you probably don't want to expose the password hash or other sensitive information. So you create a DTO:
48 +
49 + ```/dev/null/UserDTO.java#L1-6
50 + public class UserDTO {
51 + private Long id;
52 + private String username;
53 + private String email;
54 + // Getters and setters
55 + }
56 + ```
57 +
58 + ## How DTOs Fit in Your Application
59 +
60 + ```/dev/null/flow.txt#L1-5
61 + Database Entity → Service Layer → DTO → API Response → Client
62 + User → UserService → UserDTO → JSON → Browser/App
63 + ```
64 +
65 + ## Benefits of Using DTOs
66 +
67 + ### 1. Security
68 +
69 + DTOs prevent accidentally exposing sensitive data:
70 +
71 + ```/dev/null/UserService.java#L1-11
72 + public UserDTO getUserProfile(Long userId) {
73 + User user = userRepository.findById(userId);
74 +
75 + // Convert User entity to UserDTO
76 + UserDTO dto = new UserDTO();
77 + dto.setId(user.getId());
78 + dto.setUsername(user.getUsername());
79 + dto.setEmail(user.getEmail());
80 + // Notice: passwordHash is not copied to the DTO!
81 + return dto;
82 + }
83 + ```
84 +
85 + ### 2. Flexibility
86 +
87 + Your database entities can change without affecting your API:
88 +
89 + ```/dev/null/flexibility.txt#L1-7
90 + If you add a new field to User:
91 + private String internalNotes;
92 +
93 + Your UserDTO remains unchanged, so:
94 + 1. API clients won't see unexpected data
95 + 2. You won't break existing client applications
96 + 3. You can evolve your database schema independently
97 + ```
98 +
99 + ### 3. Performance
100 +
101 + DTOs can combine or simplify data to reduce network traffic:
102 +
103 + ```/dev/null/OrderSummaryDTO.java#L1-10
104 + // Instead of sending separate Order and Customer objects
105 + public class OrderSummaryDTO {
106 + private Long orderId;
107 + private String customerName;
108 + private int itemCount;
109 + private BigDecimal total;
110 +
111 + // This combines data from multiple database entities
112 + // into one compact object
113 + }
114 + ```
115 +
116 + ### 4. Versioning
117 +
118 + DTOs make it easier to support multiple API versions:
119 +
120 + ```/dev/null/versioning.txt#L1-10
121 + API v1 → UserDTOv1 (basic fields)
122 + API v2 → UserDTOv2 (basic fields + new fields)
123 +
124 + Both can be created from the same User entity, allowing
125 + you to support old and new clients simultaneously.
126 + ```
127 +
128 + ## Different Types of DTOs
129 +
130 + For our example of an Order with OrderItems, we can create different DTOs for different purposes:
131 +
132 + ```/dev/null/OrderListDTO.java#L1-6
133 + // For displaying orders in a list (ListOfMasters)
134 + public class OrderListDTO {
135 + private Long id;
136 + private String customerName;
137 + private BigDecimal total;
138 + }
139 + ```
140 +
141 + ```/dev/null/OrderDetailDTO.java#L1-8
142 + // For displaying a single order (DetailOfMaster)
143 + public class OrderDetailDTO {
144 + private Long id;
145 + private String customerName;
146 + private String shippingAddress;
147 + private List<OrderItemDTO> items;
148 + // More detailed fields...
149 + }
150 + ```
151 +
152 + ## When Should You Use DTOs?
153 +
154 + As a beginner, consider using DTOs when:
155 +
156 + 1. Your application has an API that others consume
157 + 2. You're working with sensitive data that shouldn't be exposed
158 + 3. Your database entities contain more data than what's needed for specific views
159 + 4. You need to combine data from multiple sources into a single response
160 + 5. You want to protect your application from database changes
161 +
162 + ## Common Pitfalls to Avoid
163 +
164 + 1. **DTO Explosion**: Creating too many similar DTOs
165 + 2. **Manual Mapping Tedium**: Writing repetitive code to convert between entities and DTOs
166 + 3. **Outdated DTOs**: Forgetting to update DTOs when entities change
167 +
168 + ## Tools to Help with DTOs
169 +
170 + To avoid manual mapping code, you can use libraries like:
171 +
172 + ```/dev/null/mapping.java#L1-11
173 + // Using MapStruct (Java)
174 + @Mapper
175 + public interface UserMapper {
176 + UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
177 +
178 + UserDTO userToUserDTO(User user);
179 +
180 + // The implementation is generated automatically!
181 + }
182 +
183 + // Usage: UserDTO dto = UserMapper.INSTANCE.userToUserDTO(user);
184 + ```
185 +
186 + ## Conclusion
187 +
188 + DTOs are like custom containers designed specifically for the data journey between your application layers. They help you:
189 +
190 + 1. Control what data gets exposed
191 + 2. Keep your database structure private
192 + 3. Make your application more maintainable and secure
193 + 4. Optimize network performance
194 +
195 + As you grow as a programmer, you'll find DTOs are a fundamental pattern that helps keep your code clean, secure, and flexible!
Newer Older