2025/12/06~07 MainProject - Guide

김기훈·2025년 12월 6일

TIL

목록 보기
76/194

팀 별 업무 진행 방식

  • 주제

    • 오즈코딩스쿨 LMS에 필요한 부가 기능을 구현 → 커뮤니티, 쪽지시험, 질의응답 등의 기능
    • 익스턴십 진행 간 조교 또는 코치진을 통해 문서를 전달받고 문서에 따라 기능을 구현

API 명세서, 테이블 명세서 문서 작성 순서

  • Spec API

    • 실제 API처럼 동작하지만, 진짜 백엔드나 서버 없이 가짜 응답을 제공하는 테스트용 API
    1. API 설계 시 사용자 요구사항 정의서와 디자인 피그마들을 참고하여 기능에 대해서 고민해보고 설계
    1. API 설계가 완료되면 API 명세서 노션 템플릿을 활용하여 API 명세서를 작성
    1. API 명세서가 작성이 완료 → 사전에 제공된 ERD를 참고하여 ORM을 사용하여 데이터베이스 모델링 구현
    1. 모델링까지 완료되면 테이블 명세서 스프레드 시트에 해당 내용을 작성

기능 개발 순서

  • 1. Spec API 작성

    • API 명세서에 작성된 Request, Response, API Endpoint 에 따라 Spec API를 작성
  • 2. Spec API PR 및 Merge

    • Spec API 작성이 완료되었다면 원격 브랜치에 푸시한 후 Develop 을 Base Branch 로
    • PR을 작성 PR 작성시 템플릿 양식에 맞게 작성 ( 미준수 시 재작성 요청 )
    • PR 작성 완료 전 Reviewer 에 반드시 백엔드 코치, 담당 조교를 추가
    • PR 작성이 완료되면 조교 또는 코치의 PR Review 이후에 피드백이 반영되면 코치 또는 조교가 머지 진행
  • 3. Spec API에 기능 추가

    • Spec API 작성이 완료되면 해당 Spec API 기능 로직을 추가하여 개발
    • 본 기능 개발 시 Request Data에 대한 Validation을 추가하고 비즈니스 로직에 따라
      • 적절한 로직을 수행한 후 응답을 반환
    • 기능 개발은 반드시 적절한 Test Code와 함께 작성되어야 하며,
      • Test Code의 수율은 Coverage 80%를 충족해야 함
  • 4. 기능 추가 PR 및 Merge

    • 기능 추가가 완료되면 2번에서 진행한대로 PR 을 작성한 후 피드백 또는 머지를 기다림

기능 개발 주의 ⚠️

    1. PR 작성 시 반드시 Base Branch 는 Develop으로 설정
    1. 기능 작성 중 기존 API 명세서와 달라진 점이 있다면 명세서에 즉시 반영
    1. @extend_schema 데코레이터를 활용하여 해당 API 에 대한
    • 구체적인 설명, Request, Response 데이터에 대해서 명확히 작성
    1. PR이 머지되기까지 손놓고 대기하지 않고 최신 develop 브랜치로부터 분기하여
    • 새로은 Feature 브랜치를 생성한 후 다른 API에 대한 작업을 수행
    1. API 작성 전, 후에 해결되지 않은 사항이나 헷갈리는 부분에 대해서는
    • 담당 코치, 조교에게 즉시 질문하여 해결
    1. 기능 개발 시 발생하는 오류들에 대해서는 해결방법을 찾아보고 해결이 완료되면
    • Trouble Shooting 문서 작성 습관화

백엔드 PR 작성 유의사항

  • 1개의 PR은 하나의 기능에 대해서 작성합니다.
    • 여러개의 기능을 하나의 PR에 담을 경우 리뷰어가 PR에 대해서 이해하기 힘들뿐 아니라
    • 코드 양이 많기 때문에 가독성이 떨어집니다.
      • PR은 반드시 PR 템플릿을 준수하여 작성합니다.
  • 커밋은 최대한 자주 하되 Commit Template 을 적용하여 작성합니다.
    • ex 1) ✨Feat: 유저 모델 작성
    • ex 2) ✨Feat: 유저 회원가입 시리얼라이저 작성
    • ex 3) ✨Feat: 유저 회원가입 APIView 작성
    • ex 4) ✨Feat: 유저 회원가입 APIView URL 엔드포인트 매핑
  • 커밋 템플릿 적용방법: 아래의 커맨드를 코드 에디터의 터미널에 입력합니다.
    • git config --local commit.template ./.github/commit_template.txt

프로젝트 구조 설명

  • Django App Directory 생성 규칙

oz_externship_be/
├── .github/                # 깃허브 설정 파일 ( 커밋템플릿, 이슈템플릿, pr 템플릿, CI / CD 등 )
│   ├── COMMIT_TEMPLATE/    # 하위에 커밋 템플릿을 정의
│   ├── ISSUE_TEMPLATE/     # 하위에 이슈템플릿을 정의
│   └── workflows/          # 하위에 CI / CD 스크립트를 정의
│       ├── checks.yml/     # develop 또는 main 브랜치에 Push 또는 PR Merge 시 데이터 베이스 연결 확인, 코드 포매팅 체크, 테스트 통과 여부를 검사하는 스크립트
│       ├── dev_deploy.yml/ # develop 브랜치에 Push시 개발 서버에 배포 자동화를 구현한 스크립트
│       └── prod_deploy.yml/ # develop 브랜치에 Push시 개발 서버에 배포 자동화를 구현한 스크립트
├── config/                 
│   ├── __init__.py
│   ├── settings/
│   │   ├── base.py         # 프로젝트 전역 공통 설정 파일
│   │   ├── dev.py          # 개발 서버 프로젝트 전역 설정 파일
│   │   ├── local.py        # 로컬 환경 프로젝트 전역 설정 파일
│   │   └── prod.py         # 프로덕션 환경 프로젝트 전역 설정 파일
│   ├── asgi.py
│   ├── urls.py
│   └── wsgi.py
├── apps/                   # 앱 디렉토리 (앱별로 디렉토리를 나눔)
│   ├── core/               # 공통 앱 (공통으로 사용되는 utils, base 모델, commands 정의)
│   │   ├── commands/       # 장고 커맨드 등록 폴더
│   │   ├── utils/          # 프로젝트 전역에서 공통으로 사용되는 유틸 함수를 정의하는 폴더
│   │   ├── tests/          # core 내에 정의된 util 메서드 혹은 클래스에 대한 테스트들을 구현하는 폴더
│   │   └── models.py       # 모든 앱에서 공통으로 사용되는 base 모델 정의 (ex. TimeStampModel)
│   ├── app_name1/
│   │   ├── migrations/     # 마이그레이션 파일
│   │   ├── services/       # 앱에서 사용되는 서비스 로직을 구현하는 폴더 / 서비스 로직
│   │   ├── tests/          # 앱에서 사용되는 테스트들을 구현하는 폴더
│   │   ├── models/         # 앱에서 사용되는 모델들을 정의하는 폴더
│   │   ├── urls/           # 앱 전용 URL 라우팅을 정의하는 폴더
│   │   ├── serializers/    # 시리얼 라이저 모음 폴더
│   │   ├── views/          # CBV, FBV 를 구현하는 폴더 / HTTP 호출 관련
│   │   └── apps.py         # 앱 설정
│   ├── app_name2/          # 다른 앱
│   │   ├── migrations/     # 마이그레이션 파일
│   │   ├── services/       # 앱에서 사용되는 서비스 로직을 구현하는 폴더 / 서비스 로직
│   │   ├── tests/          # 앱에서 사용되는 테스트들을 구현하는 폴더
│   │   ├── models/         # 앱에서 사용되는 모델들을 정의하는 폴더
│   │   ├── urls/           # 앱 전용 URL 라우팅을 정의하는 폴더
│   │   ├── serializers/    # 시리얼 라이저 모음 폴더
│   │   ├── views/          # CBV, FBV 를 구현하는 폴더
│   │   └── apps.py         # 앱 설정
│   └── ...
├── envs/                   # 환경변수 파일들
│   ├── .local.env          # 로컬 환경에서 서버 구동 및 테스트 시 필요한 환경변수
│   ├── .dev.env            # 개발 서버 환경에서 서버 구동 및 테스트 시 필요한 환경변수
│   └── .prod.env           # 배포 환경에서 서버 구동 및 테스트 시 필요한 환경변수
├── resources/              # 초기 설정 파일 및 스크립트, nginx, docker, kubernetes 의 yaml 파일
│   ├── nginx/
│   │   ├── Dockerfile       # nginx 이미지 빌드 도커 파일
│   │   └── nginx.local.conf # 로컬 환경에서 테스트 용 nginx 설정 파일
│   │   └── nginx.dev.conf   # 개발 서버 환경에서 테스트 용 nginx 설정 파일
│   │   └── nginx.prod.conf  # 프로덕션 서버 환경에서 테스트 용 nginx 설정 파일
│   └── scripts/             # 필요한 shell scripts를 모아두는 디렉터리 (test, formatter, create_dummy 등)
│       ├── code_formatting.sh   # black, isort 코드 포매팅 실행 스크립트
│       └── test.sh              # mypy 타입 검사 수행 및 전체 테스트코드 실행 시 사용되는 스크립트
├── manage.py                  # Django 실행 파일
├── poetry.lock                # poetry 의존성 패키지 설치 정보
├── pyproject.toml             # poetry 의존성 패키지 목록 및 설정
├── dockerfile                 # 도커 이미지 빌드 파일
├── docker-compose.local.yml   # 로컬 환경 테스트 용 도커 컨테이너 정의 파일
└── README.md                  # 프로젝트 소개서
  • 앱은 각 도메인 별로 구분하여 생성하며, app name은 snake case 를 적용

  • 기능 별 구분 도메인 → App name

    • 사용자 및 권한 관리 → users | 인증 및 로그인 → auth | 강의 → lectures
    • 스터디 그룹 → studies | 그룹 스케줄 → schedules | 학습 기록 → study_notes
    • 구인 공고 → recruitments | 공고 지원 → applicaitons | 채팅 → chat
    • 알림 → notificaitons

Code Formatting

  • 현재 세팅된 프로젝트에서는 black, isort, mypy를 사용하여 코드 포매팅과 타입 어노테이션 준수를 확인

    • 이를 통해 코드의 정적 품질 관리 체계를 구축 가능
  • mypy

    • mypy는 정적 타입 검사기

    • Python 코드에서 타입 힌트를 이용해 오류를 사전에 잡는 도구

    • 사용하는 이유

      • 런타임 전에 타입 관련 오류를 잡을 수 있음
      • 협업 시 함수 인자나 리턴 타입의 명시로 코드 가독성 향상
      • 코드 리팩토링 시 타입 안정성 확보
      def add(x: int, y: int) -> int:
          return x + y
      
      add("1", "2")  # mypy는 여기서 오류 발생 (str 대신 int가 필요)
      • stubs를 사용하여 동적 타입도 처리 가능
  • isort

    • import 정렬 도구

    • Python 코드에서 import 구문을 자동으로 정렬해주는 도구

    • 사용하는 이유

      • import 순서를 일관되게 유지
      • 중복 import나 잘못된 순서 방지
      • 팀 협업 시 merge conflict 줄일 수 있음
      - 1. 자동 정렬 전
      import sys
      import os
      from django.conf import settings
      import datetime
      
      - 2. 자동 정렬 후 
      import datetime
      import os
      import sys
      
      from django.conf import settings
  • black

    • 자동 코드 포매터

    • Python 코드를 PEP8 스타일 가이드에 맞게 자동으로 정리해주는 도구

    • 사용하는 이유

      • 코드 스타일 논쟁 제거 (포맷팅을 자동화)
      • 일관된 코드 스타일 유지
      • PR 리뷰에서 스타일 관련 지적 줄이기
      - 1. 정리 전
      def hello(name): print("Hello, " + name)
      
      - 2. 정리 후(black 적용)
      def hello(name):
          print("Hello, " + name)
  • CI / CD 파이프라인

    • PR시 자동으로 black, isort, mypy를 이용하여 코드 포맷과 타입 어노테이션을 검사
    • 기능 개발이 완료되고 Push 하기 전
      • 반드시 프로젝트 루트디렉터리/resources/scripts 디렉터리에 위치한
      • code_formatting.sh 파일을 실행하여 코드 포맷을 일관되게 유지하고,
      • mypy 정적 타입 검사를 통해 올바른 타입을 명시했는지 체크 필요

Test Code

  • 각 기능 개발시 통합 테스트 코드를 반드시 작성 필요
    • 내가 작성한 기능이 올바르게 동작하는 지
      • 로컬에서는 swagger, postman 등의 툴로 수작업으로 테스트 가능 하지만
        • CI 환경에서는 수작업으로 테스트 불가
          • 작성된 코드가 올바르게 동작하는 지를 검증하는 통합 테스트 코드가 필요
  • 통합 테스트

    • 통합 테스트(Integration Test)는 서로 다른 모듈들 간의 상호작용을 테스트하는 과정
      • 예를 들어, 신규로 개발한 API 서버 내의 DB 호출 함수가
      • 데이터베이스의 데이터를 잘 호출하고 있는지, 올바른 응답을 반환하는 지 등을 테스트하는 과정
  • Test Code 의 중요성

    • 테스트 코드를 작성하면 개발자는 소프트웨어가 어떻게 작동하는지를 이해하고,
      • 소프트웨어를 수정할 때 예상치 못한 부작용을 방지 가능
    • 또한 테스트 코드는 개발자 간의 협업을 원활하게 하고,
      • 소프트웨어를 유지 보수하는 데 필요한 문서화 작업을 줄일 수 있음
  • 테스트 코드 작성 시 pytest 등의 별도의 테스트 라이브러리를 사용하는 대신
    • Django 내부에 포함된 TestClient를 활용하여 테스트코드를 작성
      • CI 가 Django Test Module을 기준으로 설정되어 있어 Pytest로 Testcode 작성 시
      • Github Actions 의 CI 스크립트를 이용한 테스트 통과여부 확인이 불가능
  • 예시

from rest_framework.test import APITestCase
from django.urls import reverse
from rest_framework import status
from django.contrib.auth import get_user_model

User = get_user_model()

class UserSignupTest(APITestCase):
    def setUp(self):
		    # ex: path('signup/', SignupView.as_view(), name='user-signup')
        self.signup_url = reverse('user-signup')  
        self.valid_payload = {
            "username": "testuser",
            "email": "test@example.com",
            "password": "securepassword123"
        }`

    def test_signup_success(self):
        response = self.client.post(self.signup_url, data=self.valid_payload)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue(User.objects.filter(username="testuser").exists())
        self.assertEqual(response.data["username"], "testuser")
        self.assertNotIn("password", response.data)  # 보안상 비밀번호는 응답에 없어야 함

    def test_signup_missing_fields(self):
        response = self.client.post(self.signup_url, data={})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("username", response.data)
        self.assertIn("email", response.data)
        self.assertIn("password", response.data)

    def test_signup_duplicate_username(self):
        # 이미 같은 username의 사용자 생성
        User.objects.create_user(username="testuser", email="test1@example.com", password="password123")
        response = self.client.post(self.signup_url, data=self.valid_payload)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("username", response.data)

    def test_signup_weak_password(self):
        weak_payload = self.valid_payload.copy()
        weak_payload["password"] = "123"
        response = self.client.post(self.signup_url, data=weak_payload)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("password", response.data)

Swagger 문서 자동화 양식

  • 익스턴십에서는 Swagger 문서 자동화를 위한 라이브러리로 drf-spectacular 를 사용
    • 이 라이브러리는 기본적으로 djangorestframework 의 APIView 클래스의
      • 인스턴스 변수인 serializer_class 에 할당된 시리얼라이저를 읽어와서 스키마를 구성
    • 따라서 DRF 의 APIView, GenericAPIView를 적극적으로 활용하되
      • serializer_class 인스턴스 변수에 해당 뷰 클래스에서 사용되는 시리얼라이저를 필수적으로 명시
    • 단, 예외적으로 각 APIView 클래스 또는 메소드 함수(get, post, put, patch, delete 등) 마다
      • extend_schema 데코레이터를 사용하여 스키마를 구성하고
      • 각 API 별 태깅, API 요약, 구체적인 설명, 파라미터 등을 지정 가능
  • 사용법

@extend_schema(
  tags=["User"],
  summary="회원 정보 업데이트 API",
  description="""
  오즈 코딩 스쿨 이용자들 중 로그인 한 회원이 자기 자신의 정보를 수정할 때 사용하는 API 입니다.
  요청 본문으로 업데이트할 필드를 선택적으로 포함하여 요청을 보내면, 서버에서 해당 유저의 유저정보 필드들 중 요청 본문에 포함된 필드를 업데이트 합니다.
  """,
  request=UserUpdateRequestSerializer,
  responses=UserUpdateResponseSerializer
)
class UserUpdateAPIView(APIView):
	def patch(self, request: Request) -> Response:
		serailizer = UserUpdateRequestSerializer(data=request.data, partial=True)
		serializer.is_valid(raise_exception=True)
		instance = serailizer.save()
		return Response(data=UserUpdateResponseSerializer(instance).data, status=status.HTTP_200_OK)

extend_schema

  • 익스턴십에서는 extend_schema를 사용하여 swagger 문서 자동화 양식을 지정
    • 규칙을 따라 extend_schema 데코레이터를 적절하게 활용하여 swagger 문서를 구성해야 함
  • 규칙

      1. Tag는 해당 API가 해당되는 요구사항 정의서의 카테고리 명을 사용
      • ex. 쪽지시험 관리, 회원 관리 등
      1. Summary에 해당 API의 요약 설명을 기재
      • ex. 일반 회원가입 API, 쪽지시험 목록 API 등
      1. Description에 해당 API의 구체적인 동작 설명을 기재
오즈 코딩 스쿨 이용자들 중 로그인 한 회원이 자기 자신의 정보를 수정할 때 사용하는 API 
요청 본문으로 업데이트할 필드를 선택적으로 포함하여 요청을 보내면, 
서버에서 해당 유저의 유저정보 필드들 중 요청 본문에 포함된 필드를 업데이트 함

URL 매핑 규칙

    1. Trailing Slash는 추가 X ( url 맨뒤에 / 는 사용 X )
    1. 모든 API는 Rest API 원칙에 따라 url을 구성
    1. url 매핑 시 어드민 페이지에 해당되는 API의 경우 prefix에 admin을 추가합니다.
      1. /admin/users | 2. /admin/exams | 3. /admin/qna-questions

Spec API 📝

  • 백엔드 서버가 어떤 기능을 제공하는지 URL, Method, Request/Response 등을 표준 형식으로 정의한 문서

    • 서버와 클라이언트(프론트/앱/협업 개발자)들이 약속하는 규칙서

      기능URLMethodRequestResponse
      로그인/api/users/login/POST{id, pw}{access, refresh}

API 스펙 포맷

  • 가장 많이 사용하는 API 스펙 포맷 = OpenAPI(Swagger)

# OpenAPI 3.0 YAML

paths:
  /users/login:
    post:
      summary: User Login
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                username:
                  type: string
                password:
                  type: string
      responses:
        '200':
          description: Login success

SpecAPI로 할 수 있는 것

  • 1) API 문서 자동 생성

    • 백엔드 코드를 기반으로 자동으로 API 문서 생성 가능
    • (Django → drf-yasg / Spectacular, FastAPI → 자동 생성)
  • 2) 클라이언트 코드 자동 생성

    • OpenAPI 스펙 파일만 있으면 아래를 자동 생성 가능:
      • React/Next.js API 호출 코드
      • Android/iOS API 코드
      • Python/Typescript SDK
  • 3) 테스트 자동화

    • 스펙 기반으로 API 테스트도 자동 가능

SpecAPI의 기본 구성 요소

  • Info : API 전체 설명
  • Paths : URL들
  • Methods : GET / POST / PUT / DELETE
  • Parameters : URL, Query, Header
  • Request Body : 입력 데이터
  • Responses : 응답 형식
  • Schema : 데이터 구조 정의 (DTO 같은 구조)

예시

POST /api/users/register/
Request:
{
  "username": "kihoon",
  "password": "1234",
  "email": "test@example.com"
}

Response:
201 Created
{
  "id": 1,
  "username": "kihoon",
  "email": "test@example.com"
}

Service Layer 😖

  • 비즈니스 로직을 Django의 View, Model, Serializer 등으로부터 분리하여,
    • 독립적인 모듈 또는 클래스로 정의하는 계층 구조(Layered Architecture)
  • 비즈니스 로직 예시

    • 인증(Auth)과 관련된 API를 호출할 때, 사용자를 회원가입 시키거나,
    • 로그인 요청을 처리하고, 비밀번호를 검증하거나, 토큰을 발급하는 등의 모든 작업
  • 서비스 레이어 아키텍처

    • 비즈니스 로직을 View나 Model이 아닌 Service라는 별도의 계층에 위치시키는 것
  • 장점

    • 인증과 관련된 다양한 처리 로직을 명확히 분리하고 관리할 수 있음 / 유지보수성과 재사용성이 높아짐
  • 필요한 이유?

    • 비즈니스 로직 분리 → View는 요청 처리와 응답 반환에 집중하고, 서비스는 로직을 담당
    • 유지보수성 증가 → 하나의 서비스 로직만 수정하면 전체 로직에 반영됨
    • 재사용성 향상 → 여러 View나 Task 등에서 동일한 서비스 함수를 재사용 가능
    • 단위 테스트 용이 → View와 분리된 서비스 함수는 독립적으로 테스트 가능
  • 구조 ex.

oz_externship/
├── apps/
│   └── users/
│       ├── models.py
│       ├── services/
│       │   └── auth_service.py
│       ├── serializers.py
│       ├── urls.py
│       └── views.py

예제

# models.py
- 1. 데이터베이스 테이블과 매핑되는 구조 정의 / DB 저장,조회 기능 담당

from django.contrib.auth.models import AbstractBaseUser
from django.db import models

class User(AbstractBaseUser):
    email = models.EmailField(unique=True, max_length=50)
		name = models.CharField(max_length=20)
		nickname = models.CharField(max_length=20, unique=True)
		phone = models.CharField(max_length=13)
		birthday = models.DatetimeField()
		created_at = models.DatetimeField(auto_now_add=True)
		updated_at = models.DatetimeField(auto_now=True)
		
    USERNAME_FIELD = "email

    class Meta:
        db_table = "users"
# services/auth_service.py
- 1. 핵심 비즈니스 로직 담당/트랜젝션 처리/복잡한 흐름 제어/여러 모델 간 연동 등

from django.contrib.auth import authenticate
from rest_framework_simplwjwt.tokens import RefreshToken

class AuthService:
    @staticmethod
    def jwt_login(email: str, password: str) -> dict:
        user = authenticate(email=email, password=password)
        if user is None:
            raise ValueError("이메일 또는 비밀번호가 올바르지 않습니다.")

        refresh_token = RefreshToken.for_user(user)

        return {
            "access_token": str(refresh_token.access_token),
            "refresh_token": str(refresh_token),
            "user": user
        }
# serializers.py
- 1. 요청 데이터의 유효성 검사
- 2. 모델 인스턴스를 JSON 등으로 직렬화하여 응답 데이터 생성

from rest_framework import serializers
from apps.users.models import User

class UserLoginRequestSerializer(serailizers.Serializer)
		email = serializers.EmailField(max_length=50)
		password = serializers.CharField()
		
		
class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "nickname", "email")
        read_only_fields = fields

class UserLoginResponseSerializer(serializers.Serializer):
    access_token = serializers.CharField()
    refresh_token = serializers.CharField()
    user = UserInfoSerializer()
# views.py
- 1. 요청을 받고, 서비스 호출 및 응답을 반환
- 2. 비즈니스 로직은 최소화

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from apps.users.services.auth_service import AuthService
from apps.users.serializers import UserLoginRequestSerializer, UserLoginResponseSerializer

class UserLoginAPIView(APIView):
    def post(self, request):
        serailizer = UserLoginRequestSerializer(request.data)
        serailizer.is_valid(raise_exception=True)
				
				# AuthService를 호출하여 jwt_login 메서드를 통해 비즈니스 로직 구현
        try:
            response_data = AuthService.jwt_login(serailizer.validated_data)
        except ValueError as e:
            return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
				rsp_serilizer = UserLoginResponseSerializer(response_data)
        return Response({"data": rsp_serilizer.data}, status=status.HTTP_200_OK)

실무 팁

  • 서비스 메서드는 @staticmethod 또는 @classmethod를 활용해 독립적으로 호출 가능하게 구성
  • 복잡한 로직은 서비스 내부에서 메서드 분리로 가독성을 높이고, 작은 단위의 유닛 테스트가 가능하도록 구성
  • ORM 을 이용하여 데이터를 수정, 삽입, 삭제 시 transaction.atomic으로 데이터 정합성을 관리 가능
  • 예외(Exception)를 명시적으로 raise하고, View에서 핸들링하는 패턴 추천

DRF 작성 가이드

  • models.py

class Product(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField()
    image = models.ImageField(upload_to='products')
    price = models.IntegerField()
    stock = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return self.name
  • serializers.py

class ProductSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(write_only=True)
    image_url = serializers.CharField(read_only=True, source='image.url')
    
    class Meta:
        model = Product
        exclude = ('created_at', 'updated_at')
        extra_kwargs = {
            'stock': {'write_only': True},
            'description': {'write_only': True},
        }
  • ProductSerializerProduct 등록, 리스트 조회 시 사용할 시리얼라이저
    • 리스트 조회 시 상품 id, 이름, 상품이미지, 가격만 표시하도록 하기 위해
    • exclude와 extra_kwargs를 적절히 사용하여 이외의 필드가 노출되지 않도록 구성
class ProductDetailSerializer(serializers.ModelSerializer):
    image = serializers.ImageField(write_only=True)
    image_url = serializers.CharField(read_only=True, source='image.url')
    
    class Meta:
        model = Product
        fields = '__all__'
        read_only_fields = ('created_at', 'updated_at')
  • ProductDetailSerializerProduct 단일 상세 조회, 정보 수정 시 사용할 시리얼라이저
    • 시리얼라이저를 작성할 때 필요시
      • read_only_fields, extra_kwargs, validators 등을 통해 유효성 검증을 추가 가능

profile
안녕하세요.

0개의 댓글