# 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. - Published: 2026-07-12 - Updated: 2026-07-12 - Author: SharpSkill - Tags: django, drf, serializers, api, python - Reading time: 5 min --- 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. > **Quy Tắc Hiệu Suất DRF Serializer** > > 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. ```python # 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 attrs ``` Sự 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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 instance ``` Mẫ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. ```python # 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. ```python # 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. ```python # 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. ```python # 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 data ``` ## Debug 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à đủ. ```python # 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) ``` ## 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/django/django-rest-framework-serializers-deep-dive