DRF serializer.py 설계 및 의사결정 과정

2star_·2025년 1월 6일
0

최종 프로젝트

목록 보기
8/32

전체 코드

from rest_framework import serializers
from .models import Review, ReviewComment, ReviewLike

class ReviewCommentSerializer(serializers.ModelSerializer):
    """ReviewComment 모델 직렬화"""
    nickname = serializers.SerializerMethodField()

    class Meta:
        model = ReviewComment
        fields = ['id', 'review', 'user', 'nickname', 'content', 'created_at', 'updated_at']
        read_only_fields = ['id', 'created_at', 'updated_at']

    def get_nickname(self, obj):
        """유저 닉네임 반환 (유저가 없으면 '알수없음')"""
        return obj.user.nickname if obj.user else "알수없음"


class ReviewLikeSerializer(serializers.ModelSerializer):
    """ReviewLike 모델 직렬화"""
    nickname = serializers.SerializerMethodField()

    class Meta:
        model = ReviewLike
        fields = ['id', 'review', 'user', 'nickname', 'is_active', 'created_at', 'updated_at']
        read_only_fields = ['id', 'created_at', 'updated_at']

    def get_nickname(self, obj):
        """유저 닉네임 반환 (유저가 없으면 '알수없음')"""
        return obj.user.nickname if obj.user else "알수없음"


class ReviewSerializer(serializers.ModelSerializer):
    """Review 모델 직렬화"""
    nickname = serializers.SerializerMethodField()
    comments = ReviewCommentSerializer(many=True, read_only=True)  # 연결된 댓글들
    total_likes = serializers.SerializerMethodField()
    total_dislikes = serializers.SerializerMethodField()

    class Meta:
        model = Review
        fields = ['id', 'user', 'nickname', 'content', 'app_id', 'score', 'created_at', 'updated_at', 'comments', 'total_likes', 'total_dislikes']
        read_only_fields = ['id', 'created_at', 'updated_at', 'comments', 'total_likes', 'total_dislikes']

    def get_nickname(self, obj):
        """유저 닉네임 반환 (유저가 없으면 '알수없음')"""
        return obj.user.nickname if obj.user else "알수없음"

    def get_total_likes(self, obj):
        """총 좋아요(추천) 수 반환"""
        return obj.likes.filter(is_active=1).count()

    def get_total_dislikes(self, obj):
        """총 비추천 수 반환"""
        return obj.likes.filter(is_active=-1).count()
    
    def validate_score(self, value):
        """
        평점 검증: 0.5~5.0 사이의 값만 허용하고, 0.5 단위로 작성되어야 함.
        """
        if not (0.5 <= value <= 5.0):
            raise serializers.ValidationError("평점은 0.5에서 5.0 사이의 값이어야 합니다.")
        if value * 10 % 5 != 0:
            raise serializers.ValidationError("평점은 0.5 단위로 작성되어야 합니다.")
        return value

오늘 작업한 내용은 Django Rest Framework(DRF) 기반의 serializer.py를 설계하고, Review, ReviewComment, ReviewLike 모델을 직렬화하는 과정에서의 주요 의사결정을 정리했다.


1. 설계 목표

  1. Review, ReviewComment, ReviewLike의 데이터를 직렬화하여 API로 제공.
  2. 직렬화 과정에서 필요하지만 모델에 저장되지 않는 추가 정보(nickname, total_likes, total_dislikes 등)를 처리.
  3. 유효성 검증 로직 추가를 통해 데이터 무결성을 보장.

2. 주요 의사결정 및 이유

1) nickname 필드

  • 목표: Review나 Comment를 작성한 사용자의 닉네임을 제공.
  • 문제: user.nickname 값은 Review나 ReviewComment 모델의 필드가 아니기 때문에, 기본 직렬화 방식으로는 포함 불가능.
  • 결정: SerializerMethodField를 활용해 nickname 필드를 동적으로 생성.
    nickname = serializers.SerializerMethodField()
    • get_nickname 메서드에서 닉네임 값을 반환.
    • 유저 정보가 없을 경우 기본값 "알수없음"을 반환해 API의 안정성을 보장.

2) comments 필드 (연결된 댓글 목록)

  • 목표: Review API에서 연결된 모든 댓글(Comment)을 포함해 데이터의 완전성을 높임.
  • 결정: ReviewCommentSerializer를 사용하여 Nested Serializer로 구현.
    comments = ReviewCommentSerializer(many=True, read_only=True)
    • many=True를 설정하여 여러 개의 댓글을 포함.
    • 댓글 데이터를 읽기 전용(read_only=True)으로 처리해 댓글 생성/수정을 별도 엔드포인트로 분리.

3) total_likestotal_dislikes 필드

  • 목표: Review가 받은 총 좋아요와 비추천 수를 포함하여 사용자 인터페이스에서 유용한 정보를 제공.

  • 문제: 초기 설계에서는 이 값을 Review 모델 필드로 저장하려고 했으나, 두 가지 문제점이 있었다.

    1. 데이터 무결성 유지: 좋아요/비추천이 변경될 때마다 Review 모델의 필드를 동기화해야 하는 추가 복잡성이 발생.
    2. 성능: 필드 값을 업데이트할 때마다 DB에 쓰기 작업이 발생해 비효율적.
  • 결정: DB에 저장하지 않고 Serializer에서 동적으로 계산.

    total_likes = serializers.SerializerMethodField()
    total_dislikes = serializers.SerializerMethodField()
    • get_total_likesget_total_dislikes 메서드를 사용해 ReviewLike 모델의 is_active 상태를 기반으로 필드 값 계산.
    • 장점:
      • 실시간 데이터 반영 가능.
      • DB 설계와 데이터 저장 방식이 간단해짐.

4) score 필드 유효성 검증

  • 목표: Review의 평점(score)이 적절한 범위와 형식을 유지하도록 검증.
  • 결정: validate_score 메서드에서 커스텀 검증 로직 추가.
    def validate_score(self, value):
        if not (0.5 <= value <= 5.0):
            raise serializers.ValidationError("평점은 0.5에서 5.0 사이의 값이어야 합니다.")
        if value * 10 % 5 != 0:
            raise serializers.ValidationError("평점은 0.5 단위로 작성되어야 합니다.")
        return value
    • 평점 범위와 형식을 명확히 정의하여 잘못된 입력을 방지.

3. 주요 학습 내용

  1. SerializerMethodField 활용:

    • 동적으로 계산되거나 모델에 포함되지 않는 데이터를 처리하는 유용한 방법임을 배움.
  2. DB 필드 vs. Serializer 필드:

    • 동적인 데이터는 DB에 저장하기보다는 Serializer에서 계산하는 방식이 더 효율적일 수 있음.
    • 특히, 데이터 동기화 이슈가 있는 경우 Serializer를 활용하면 유지보수 부담을 줄일 수 있음.
  3. Nested Serializer 설계:

    • 연결된 모델 데이터를 API에 포함하는 방법으로 코드의 가독성과 API의 데이터 전달력을 향상.
  4. 유효성 검증:

    • validate_<field_name> 메서드를 사용하여 특정 필드에 대한 검증 로직을 간단하게 추가할 수 있음.

4. 앞으로의 개선 방향

  • 좋아요/비추천 데이터를 동적으로 계산하는 현재 방식은 API 요청이 많아질 경우 성능 문제가 발생할 가능성이 있음. 캐싱이나 비동기 작업을 활용한 최적화를 추가적으로 고민할 필요가 있음.
  • 대규모 데이터를 처리하는 경우, 쿼리 최적화를 위한 prefetch_related 또는 select_related와 같은 ORM 기능을 활용할 계획.
profile
안녕하세요.

0개의 댓글

관련 채용 정보