Tìm Hiểu Sâu Về Serializer Django REST Framework: Validation, Nested và Tối Ưu N+1
Hướng dẫn toàn diện về DRF serializers: pipeline validation, nested serializers, xử lý quan hệ phức tạp và các kỹ thuật tối ưu để tránh vấn đề N+1 queries.

Serializer trong Django REST Framework đảm nhận nhiệm vụ quan trọng là chuyển đổi queryset và model instance thành JSON response—đồng thời validate dữ liệu đầu vào trước khi lưu vào database. Mặc dù việc sử dụng serializer cơ bản khá đơn giản, các ứng dụng production đòi hỏi sự thành thạo về pipeline validation, xử lý quan hệ nested và tối ưu hóa query.
Mỗi SerializerMethodField hoặc nested serializer truy cập các đối tượng liên quan mà không có select_related/prefetch_related sẽ kích hoạt thêm database query. Danh sách 100 đối tượng với 3 quan hệ có nghĩa là 301 queries thay vì 4.
Hiểu Pipeline Validation của DRF Serializer
DRF serializer thực thi validation theo thứ tự cụ thể: deserialization cấp field, validator cấp field, sau đó là validation cấp object thông qua validate(). Pipeline này xác định thời điểm và cách thức can thiệp vào quá trình chuyển đổi dữ liệu.
Quy trình validation bắt đầu với to_internal_value(), thực hiện deserialization các kiểu dữ liệu nguyên thủy và chạy các field validator. Chỉ sau khi tất cả các field vượt qua validation riêng lẻ, validate() mới được thực thi để kiểm tra liên field.
# serializers.py
from rest_framework import serializers
from django.utils import timezone
from .models import Event
class EventSerializer(serializers.ModelSerializer):
start_date = serializers.DateTimeField()
end_date = serializers.DateTimeField()
class Meta:
model = Event
fields = ['id', 'title', 'start_date', 'end_date', 'location']
def validate_start_date(self, value):
# Validation cấp field chạy trước
if value < timezone.now():
raise serializers.ValidationError("Ngày bắt đầu không được ở quá khứ.")
return value
def validate(self, attrs):
# Validation cấp object chạy sau khi tất cả field được validate
start = attrs.get('start_date')
end = attrs.get('end_date')
if start and end and end <= start:
raise serializers.ValidationError({
'end_date': "Ngày kết thúc phải sau ngày bắt đầu."
})
return attrsSự phân tách này cho phép kiểm soát chi tiết: bắt lỗi các giá trị field rõ ràng không hợp lệ sớm, sau đó validate các quy tắc nghiệp vụ liên quan đến nhiều field.
Custom Validator và Logic Validation Tái Sử Dụng
DRF hỗ trợ ba mẫu validator: phương thức cấp field, lớp validator độc lập và tham số validators. Validator độc lập khuyến khích việc tái sử dụng trên nhiều serializer và duy trì nguyên tắc single responsibility.
# validators.py
from rest_framework import serializers
import re
class SlugFormatValidator:
"""Validate định dạng slug an toàn cho URL."""
def __init__(self, allow_unicode=False):
self.allow_unicode = allow_unicode
self.pattern = r'^[\w-]+$' if allow_unicode else r'^[a-z0-9-]+$'
def __call__(self, value):
if not re.match(self.pattern, value):
raise serializers.ValidationError(
"Slug chỉ được chứa chữ thường, số và dấu gạch ngang."
)
class UniqueForUserValidator:
"""Validate giá trị là duy nhất cho user hiện tại."""
requires_context = True
def __init__(self, queryset, field_name):
self.queryset = queryset
self.field_name = field_name
def __call__(self, value, serializer_field):
request = serializer_field.context.get('request')
if not request or not request.user.is_authenticated:
return
filter_kwargs = {
self.field_name: value,
'user': request.user
}
# Loại trừ instance hiện tại khi update
instance = serializer_field.parent.instance
exists_query = self.queryset.filter(**filter_kwargs)
if instance:
exists_query = exists_query.exclude(pk=instance.pk)
if exists_query.exists():
raise serializers.ValidationError(
f"Bạn đã có một item với {self.field_name} này."
)Validator cần context sử dụng cờ requires_context = True và nhận serializer_field làm tham số thứ hai. Điều này cung cấp quyền truy cập vào request, instance và toàn bộ serializer cha.
# serializers.py
from .validators import SlugFormatValidator, UniqueForUserValidator
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
slug = serializers.CharField(
max_length=100,
validators=[
SlugFormatValidator(allow_unicode=False),
UniqueForUserValidator(
queryset=Article.objects.all(),
field_name='slug'
)
]
)
class Meta:
model = Article
fields = ['id', 'title', 'slug', 'content', 'published']Xử Lý Nested Serializer cho Quan Hệ Phức Tạp
Nested serializer mô hình hóa các quan hệ database trong API response. Thách thức thực sự nằm ở việc xử lý các thao tác create và update—đặc biệt khi làm việc với quan hệ many-to-many hoặc reverse foreign key.
# models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField(unique=True)
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
class Book(models.Model):
title = models.CharField(max_length=300)
isbn = models.CharField(max_length=13, unique=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
tags = models.ManyToManyField(Tag, related_name='books')
published_date = models.DateField()
class Chapter(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='chapters')
title = models.CharField(max_length=200)
order = models.PositiveIntegerField()
content = models.TextField()Nested serializer chỉ đọc có thể được đưa vào trực tiếp, nhưng nested có thể ghi yêu cầu override create() và update() rõ ràng.
# serializers.py
from rest_framework import serializers
from django.db import transaction
from .models import Author, Tag, Book, Chapter
class ChapterSerializer(serializers.ModelSerializer):
class Meta:
model = Chapter
fields = ['id', 'title', 'order', 'content']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'email']
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
author_id = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(),
source='author',
write_only=True
)
tags = TagSerializer(many=True, read_only=True)
tag_ids = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
source='tags',
many=True,
write_only=True
)
chapters = ChapterSerializer(many=True)
class Meta:
model = Book
fields = [
'id', 'title', 'isbn', 'author', 'author_id',
'tags', 'tag_ids', 'chapters', 'published_date'
]
@transaction.atomic
def create(self, validated_data):
chapters_data = validated_data.pop('chapters', [])
tags = validated_data.pop('tags', [])
book = Book.objects.create(**validated_data)
book.tags.set(tags)
for chapter_data in chapters_data:
Chapter.objects.create(book=book, **chapter_data)
return book
@transaction.atomic
def update(self, instance, validated_data):
chapters_data = validated_data.pop('chapters', None)
tags = validated_data.pop('tags', None)
# Update các field đơn giản
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Update quan hệ many-to-many nếu được cung cấp
if tags is not None:
instance.tags.set(tags)
# Xử lý update nested chapters
if chapters_data is not None:
# Chiến lược: xóa hiện có và tạo lại
instance.chapters.all().delete()
for chapter_data in chapters_data:
Chapter.objects.create(book=instance, **chapter_data)
return instanceMẫu này tách biệt các thao tác đọc và ghi: các field nested cung cấp output được định dạng đẹp, trong khi các field *_id chấp nhận primary key cho input. Wrapper @transaction.atomic đảm bảo tính nhất quán dữ liệu khi các thao tác multiple thất bại một phần.
Kỹ Thuật Tối Ưu để Tránh Vấn Đề N+1 Query
Vấn đề hiệu suất phổ biến nhất trong DRF xuất phát từ N+1 query được tạo ra bởi nested serializer và method field. Một endpoint trả về 50 cuốn sách với author, tag và chapter sẽ thực thi hàng trăm query nếu không được tối ưu đúng cách.
# views.py
from rest_framework import viewsets
from django.db.models import Prefetch
from .models import Book, Chapter
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
def get_queryset(self):
return Book.objects.select_related(
'author'
).prefetch_related(
'tags',
Prefetch(
'chapters',
queryset=Chapter.objects.order_by('order')
)
)Việc lựa chọn giữa select_related và prefetch_related tuân theo quy tắc đơn giản: select_related cho quan hệ single-object (ForeignKey, OneToOne) sử dụng SQL JOIN, trong khi prefetch_related cho quan hệ multi-object (ManyToMany, reverse ForeignKey) sử dụng query riêng biệt.
Xử Lý SerializerMethodField Hiệu Quả
SerializerMethodField thường gây ra vấn đề N+1 vì query được thực thi cho mỗi instance. Giải pháp liên quan đến annotation queryset hoặc sử dụng prefetch cẩn thận.
# serializers.py
class BookListSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.name', read_only=True)
chapter_count = serializers.IntegerField(read_only=True)
tag_names = serializers.SerializerMethodField()
class Meta:
model = Book
fields = ['id', 'title', 'author_name', 'chapter_count', 'tag_names']
def get_tag_names(self, obj):
# An toàn nếu tags đã được prefetch
return [tag.name for tag in obj.tags.all()]
# views.py
from django.db.models import Count
class BookListViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = BookListSerializer
def get_queryset(self):
return Book.objects.select_related(
'author'
).prefetch_related(
'tags'
).annotate(
chapter_count=Count('chapters')
)Annotation Count đếm chapter trong một query duy nhất thay vì fetch quan hệ và đếm trong Python. Truy cập author.name qua source hoạt động vì select_related('author') đã tải dữ liệu author.
Custom Prefetch cho Quan Hệ Đã Lọc
Đối tượng Prefetch cho phép custom query cho các quan hệ được prefetch, cho phép lọc và sắp xếp mà không cần query bổ sung.
# views.py
from django.db.models import Prefetch
from .models import Book, Chapter, Review
class BookDetailViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = BookDetailSerializer
def get_queryset(self):
return Book.objects.select_related(
'author'
).prefetch_related(
'tags',
Prefetch(
'chapters',
queryset=Chapter.objects.order_by('order').only(
'id', 'title', 'order', 'book_id'
)
),
Prefetch(
'reviews',
queryset=Review.objects.filter(
is_approved=True
).select_related('user').order_by('-created_at')[:5],
to_attr='recent_reviews'
)
)Thuộc tính to_attr lưu kết quả prefetch trong thuộc tính tùy chỉnh (recent_reviews) thay vì thay thế relation manager mặc định. Điều này hữu ích khi cần truy cập cả subset đã lọc và quan hệ đầy đủ.
Kiểm Soát Serialization Nâng Cao với to_representation
Phương thức to_representation() cung cấp kiểm soát hoàn toàn đối với output được serialize. Nó thực thi sau tất cả serialization field, cho phép biến đổi dựa trên context hoặc dữ liệu instance.
# serializers.py
class BookSerializer(serializers.ModelSerializer):
chapters = ChapterSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = ['id', 'title', 'isbn', 'author', 'chapters', 'published_date']
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get('request')
# Loại bỏ field nhạy cảm cho user không phải staff
if request and not request.user.is_staff:
data.pop('isbn', None)
# Thêm field có điều kiện
if instance.published_date:
from django.utils import timezone
data['is_new_release'] = (
timezone.now().date() - instance.published_date
).days < 30
# Tái cấu trúc output nested
if 'chapters' in data:
data['chapter_count'] = len(data['chapters'])
data['chapters'] = data['chapters'][:3] # Chỉ 3 đầu tiên trong response
return dataDebug Query Serializer
Các package django-debug-toolbar và django-silk rất có giá trị để xác định vấn đề query. Để logging có lập trình, một wrapper queryset đơn giản là đủ.
# utils.py
import logging
from django.db import connection, reset_queries
from django.conf import settings
from functools import wraps
logger = logging.getLogger(__name__)
def log_queries(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not settings.DEBUG:
return func(*args, **kwargs)
reset_queries()
result = func(*args, **kwargs)
queries = connection.queries
logger.debug(
f"{func.__name__} executed {len(queries)} queries"
)
for query in queries:
logger.debug(f" [{query['time']}s] {query['sql'][:100]}")
return result
return wrapper
# views.py
class BookViewSet(viewsets.ModelViewSet):
@log_queries
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)Sẵn sàng chinh phục phỏng vấn Django?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Kết Luận
Làm chủ DRF serializer đòi hỏi hiểu biết về pipeline validation, mẫu nested relationship và chiến lược tối ưu query. Các thành phần chính cần ghi nhớ bao gồm: sử dụng validation cấp field cho kiểm tra riêng lẻ và validate() cho quy tắc liên field, tách biệt field read-only và write-only cho nested relationship rõ ràng, luôn áp dụng select_related và prefetch_related trong queryset của view, và tận dụng to_representation() cho output có điều kiện.
Áp dụng các mẫu này đảm bảo API không chỉ hoạt động chính xác mà còn hoạt động tốt dưới tải production thực tế. Serializer hiệu quả giảm đáng kể thời gian phản hồi và tải server.
Thẻ
Chia sẻ
Bài viết liên quan

Câu hỏi phỏng vấn Django: ORM, Middleware và DRF chi tiết
Câu hỏi phỏng vấn Django bao gồm tối ưu hóa ORM với select_related và prefetch_related, kiến trúc middleware, hiệu suất serializer Django REST Framework, permissions và các pattern pagination.

Async View và ASGI trong Django 2026: Hiệu năng và câu hỏi phỏng vấn
Phân tích chuyên sâu về async view và ASGI trong Django 2026: cơ chế hoạt động bên dưới, server nên triển khai, async ORM cùng cái bẫy SynchronousOnlyOperation, kèm theo các câu hỏi phỏng vấn.

Django và PostgreSQL năm 2026: Đánh chỉ mục, tìm kiếm toàn văn và câu hỏi phỏng vấn
Hướng dẫn thực hành tối ưu Django với PostgreSQL: chỉ mục B-tree, một phần và bao phủ, tìm kiếm toàn văn với SearchVector và GIN, cùng các câu hỏi phỏng vấn 2026.