最後活躍 1749231863

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

kristofer's Avatar kristofer 已修改 1749231863. 還原成這個修訂版本

沒有任何變更

kristofer's Avatar kristofer 已修改 1749231827. 還原成這個修訂版本

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(檔案已創建)

@@ -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 已修改 1749231587. 還原成這個修訂版本

1 file changed, 195 insertions

dtointro.md(檔案已創建)

@@ -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!
上一頁 下一頁