Django 프로젝트 구조 이렇게 짜면 협업이 편해진다 (서비스 분리 전략)

Django 프로젝트 구조 이렇게 짜면 협업이 편해진다 (서비스 분리 전략)

안녕하세요! 오늘은 Django 프로젝트를 효율적으로 구조화하여 팀 협업을 개선하는 방법에 대해 알아보겠습니다. 잘 구조화된 프로젝트는 유지보수성과 확장성을 높여줍니다.

왜 프로젝트 구조가 중요한가요?

좋은 프로젝트 구조의 이점:

  1. 유지보수성: 코드를 쉽게 찾고 수정할 수 있습니다.
  2. 확장성: 새로운 기능을 쉽게 추가할 수 있습니다.
  3. 재사용성: 코드를 다른 프로젝트에서 재사용할 수 있습니다.
  4. 테스트 용이성: 단위 테스트와 통합 테스트가 쉬워집니다.
  5. 협업 효율성: 여러 개발자가 동시에 작업하기 좋습니다.

권장 프로젝트 구조

project/
├── apps/                      # 애플리케이션 디렉토리
│   ├── core/                  # 핵심 기능
│   ├── users/                 # 사용자 관리
│   ├── products/              # 상품 관리
│   └── orders/                # 주문 관리
├── config/                    # 프로젝트 설정
│   ├── settings/              # 설정 파일들
│   │   ├── base.py           # 기본 설정
│   │   ├── development.py    # 개발 환경 설정
│   │   └── production.py     # 프로덕션 환경 설정
│   ├── urls.py               # URL 설정
│   └── wsgi.py               # WSGI 설정
├── docs/                      # 문서
├── scripts/                   # 유틸리티 스크립트
├── tests/                     # 테스트
├── requirements/              # 의존성 관리
│   ├── base.txt              # 기본 의존성
│   ├── development.txt       # 개발용 의존성
│   └── production.txt        # 프로덕션용 의존성
└── manage.py                  # Django 관리 명령어

애플리케이션 구조

1. Core 앱

# apps/core/models.py
from django.db import models

class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

# apps/core/managers.py
from django.db import models

class ActiveManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_active=True)

2. Users 앱

# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from core.models import TimeStampedModel

class User(AbstractUser, TimeStampedModel):
    email = models.EmailField(unique=True)
    phone_number = models.CharField(max_length=15, blank=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

# apps/users/serializers.py
from rest_framework import serializers
from .models import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'email', 'username', 'phone_number']

3. Products 앱

# apps/products/models.py
from django.db import models
from core.models import TimeStampedModel

class Product(TimeStampedModel):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    description = models.TextField()
    stock = models.IntegerField(default=0)

    objects = models.Manager()
    active = ActiveManager()

# apps/products/services.py
class ProductService:
    @staticmethod
    def create_product(data):
        # 상품 생성 로직
        pass

    @staticmethod
    def update_stock(product_id, quantity):
        # 재고 업데이트 로직
        pass

설정 파일 구조

1. 기본 설정 (base.py)

# config/settings/base.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent.parent

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third party apps
    'rest_framework',
    'corsheaders',

    # Local apps
    'apps.core',
    'apps.users',
    'apps.products',
    'apps.orders',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

2. 개발 환경 설정 (development.py)

# config/settings/development.py
from .base import *

DEBUG = True

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# 개발용 추가 설정
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']

URL 구조

# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('apps.users.urls')),
    path('api/v1/', include('apps.products.urls')),
    path('api/v1/', include('apps.orders.urls')),
]

# apps/users/urls.py
from django.urls import path
from . import views

app_name = 'users'

urlpatterns = [
    path('users/', views.UserListView.as_view(), name='user-list'),
    path('users/<int:pk>/', views.UserDetailView.as_view(), name='user-detail'),
]

서비스 계층 구조

1. 서비스 클래스

# apps/orders/services.py
from django.db import transaction
from .models import Order, OrderItem

class OrderService:
    @staticmethod
    @transaction.atomic
    def create_order(user, items):
        order = Order.objects.create(user=user)
        for item in items:
            OrderItem.objects.create(
                order=order,
                product=item['product'],
                quantity=item['quantity']
            )
        return order

    @staticmethod
    def cancel_order(order_id):
        order = Order.objects.get(id=order_id)
        order.status = 'cancelled'
        order.save()
        return order

2. 뷰에서 서비스 사용

# apps/orders/views.py
from rest_framework import viewsets
from .models import Order
from .serializers import OrderSerializer
from .services import OrderService

class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

    def perform_create(self, serializer):
        items = self.request.data.get('items', [])
        order = OrderService.create_order(self.request.user, items)
        serializer.instance = order

테스트 구조

# tests/test_orders.py
from django.test import TestCase
from apps.orders.services import OrderService
from apps.products.models import Product
from apps.users.models import User

class OrderServiceTest(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            email='test@example.com',
            password='testpass123'
        )
        self.product = Product.objects.create(
            name='Test Product',
            price=1000
        )

    def test_create_order(self):
        items = [{'product': self.product, 'quantity': 2}]
        order = OrderService.create_order(self.user, items)
        self.assertEqual(order.user, self.user)
        self.assertEqual(order.items.count(), 1)

문서화

1. API 문서

# apps/orders/schemas.py
from drf_yasg import openapi

order_schema = {
    'operation_description': '주문 생성 API',
    'manual_parameters': [
        openapi.Parameter(
            'items',
            openapi.IN_BODY,
            description='주문 상품 목록',
            type=openapi.TYPE_ARRAY,
            items=openapi.Items(
                type=openapi.TYPE_OBJECT,
                properties={
                    'product_id': openapi.Schema(type=openapi.TYPE_INTEGER),
                    'quantity': openapi.Schema(type=openapi.TYPE_INTEGER),
                }
            )
        ),
    ],
    'responses': {
        201: openapi.Response(
            description='주문 생성 성공',
            schema=OrderSerializer
        ),
    }
}

협업을 위한 추가 팁

  1. 코드 스타일 가이드: black, isort, flake8 등의 도구 사용
  2. Git 전략: Git Flow 또는 Trunk Based Development 채택
  3. 리뷰 프로세스: Pull Request 템플릿과 리뷰 체크리스트 사용
  4. CI/CD: GitHub Actions 또는 GitLab CI 설정
  5. 문서화: README.md와 API 문서 꾸준히 업데이트

결론

잘 구조화된 Django 프로젝트는 팀의 생산성을 크게 향상시킵니다. 이 글에서 소개한 구조와 패턴들을 참고하여 자신의 팀에 맞는 프로젝트 구조를 설계해보세요. 추가적인 질문이나 궁금한 점이 있으시면 댓글로 남겨주세요!

댓글