Cursor 사례 연구: 솔로 개발자가 5만 줄 Django 모놀리스를 4주 만에 마이크로서비스로 전환한 방법
프로젝트 배경: 3개월 예상 리라이트를 4주로 단축
서울의 한 핀테크 스타트업에서 백엔드를 혼자 담당하던 김태호 개발자(가명)는 5만 줄 규모의 Django 모놀리스를 마이크로서비스로 전환해야 하는 과제를 받았다. 기존 아키텍처는 결제, 사용자 관리, 알림, 리포트 생성이 하나의 코드베이스에 강하게 결합되어 있었고, 배포 한 번에 전체 서비스가 영향을 받는 구조였다. 외부 컨설팅 업체는 최소 3개월, 개발자 3명이 필요하다고 견적을 냈다. 김태호 개발자는 Cursor AI 에디터를 활용하여 이 작업을 혼자서 4주 만에 완료했다.
Cursor 설치 및 프로젝트 설정
1단계: Cursor 설치
# macOS
brew install —cask cursor
Windows - 공식 사이트에서 다운로드
https://cursor.sh 에서 설치 파일 다운로드 후 실행
Linux (AppImage)
chmod +x cursor-.AppImage
./cursor-.AppImage
2단계: 프로젝트 인덱싱 설정
Cursor는 프로젝트를 열면 자동으로 코드베이스를 인덱싱한다. 5만 줄 규모의 프로젝트는 초기 인덱싱에 2~5분 소요된다. .cursorignore 파일로 불필요한 디렉토리를 제외하여 성능을 최적화한다.
# .cursorignore
node_modules/
.venv/
__pycache__/
*.pyc
migrations/
static/vendor/
media/
### 3단계: Cursor 규칙 파일 설정
# .cursorrules
You are an expert Python/Django developer migrating a monolith to microservices.
Always follow these conventions:
- Use Django REST Framework for all API endpoints
- Each microservice uses its own database schema
- Communication between services uses REST API calls
- Keep backward compatibility during migration
- Write type hints for all function signatures
- Generate tests for every extracted module
## 마이그레이션 워크플로우: 4주간의 실전 과정
1주차: 코드베이스 분석 및 의존성 매핑
Cursor의 Ctrl+L (Chat) 기능으로 전체 코드베이스를 분석했다. @codebase 태그를 사용하면 Cursor가 프로젝트 전체 맥락을 이해한 상태에서 응답한다.
# Cursor Chat에서 입력 (Ctrl+L)
@codebase 현재 프로젝트의 모든 Django 앱 간 의존성을 분석해줘.
각 앱이 import하는 다른 앱의 모델과 함수를 테이블 형태로 정리해줘.
Cursor는 payments, users, notifications, reports 네 개의 앱 간 순환 의존성 3건을 포함한 전체 의존성 맵을 생성했다.
2주차: 멀티파일 편집으로 서비스 분리
Cursor의 Composer 기능(Ctrl+I)은 여러 파일을 동시에 수정할 수 있다. 이것이 마이그레이션의 핵심이었다.
# Cursor Composer에서 입력 (Ctrl+I)
payments 앱을 독립 마이크로서비스로 분리해줘.
- payments/models.py의 User 외래키를 user_id 정수 필드로 변경
- payments/views.py에서 users 앱 직접 import를 REST API 호출로 교체
- payments/serializers.py 업데이트
- payments/urls.py를 독립 URL 구성으로 변경
새로운 payments/services/user_client.py 생성Composer는 5개 파일의 변경사항을 인라인 diff로 동시에 보여주었다. 실제 생성된 코드 예시:
# payments/services/user_client.py import httpx from django.conf import settings from typing import Optional
class UserServiceClient:
def init(self):
self.base_url = settings.USER_SERVICE_URL
self.timeout = httpx.Timeout(10.0)
async def get_user(self, user_id: int) -> Optional[dict]:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.base_url}/api/users/{user_id}/",
headers={"Authorization": f"Bearer {settings.INTERNAL_API_KEY}"}
)
if response.status_code == 200:
return response.json()
return None
async def validate_user_exists(self, user_id: int) -> bool:
user = await self.get_user(user_id)
return user is not None</code></pre><pre><code># settings.py 추가 설정
USER_SERVICE_URL = “http://user-service:8001”
INTERNAL_API_KEY = “YOUR_API_KEY” # 실제 운영에서는 환경변수 사용
PAYMENT_SERVICE_URL = “http://payment-service:8002”
NOTIFICATION_SERVICE_URL = “http://notification-service:8003”
3주차: 인라인 Diff 리뷰로 안전한 리팩토링
Cursor의 인라인 diff 리뷰 기능은 각 변경사항을 초록색(추가)/빨간색(삭제)으로 표시하며, Tab으로 수락, Esc로 거부할 수 있다. 이 방식으로 AI가 생성한 코드를 한 줄씩 검증했다.
# Cursor에서 Ctrl+K (인라인 편집) 사용 예시
# 기존 코드를 선택한 후:
이 뷰에서 동기 ORM 쿼리를 비동기 패턴으로 변환하고
Circuit Breaker 패턴을 적용해줘
변환된 코드:
# payments/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
import circuitbreaker
@circuitbreaker.circuit(failure_threshold=5, recovery_timeout=30)
def get_user_payment_history(user_id: int) -> list:
client = UserServiceClient()
user = client.get_user(user_id) # 동기 버전
if not user:
return []
return Payment.objects.filter(user_id=user_id).select_related().values()
@api_view([“GET”])
def payment_history_view(request, user_id):
try:
history = get_user_payment_history(user_id)
return Response({“payments”: list(history)})
except circuitbreaker.CircuitBreakerError:
return Response(
{“error”: “User service temporarily unavailable”},
status=503
)
4주차: 테스트 생성 및 Docker 구성
# Cursor Chat에서 테스트 자동 생성
@codebase payments 서비스의 모든 API 엔드포인트에 대한
pytest 테스트를 생성해줘. UserServiceClient는 mock 처리해줘.# docker-compose.yml (Cursor Composer로 생성)version: “3.8” services: user-service: build: ./user_service ports: - “8001:8000” environment: - DATABASE_URL=postgresql://postgres:password@user-db:5432/users - INTERNAL_API_KEY=YOUR_API_KEY
payment-service: build: ./payment_service ports: - “8002:8000” environment: - DATABASE_URL=postgresql://postgres:password@payment-db:5432/payments - USER_SERVICE_URL=http://user-service:8000 - INTERNAL_API_KEY=YOUR_API_KEY depends_on: - user-db - user-service
user-db: image: postgres:16 environment: - POSTGRES_DB=users - POSTGRES_PASSWORD=password
payment-db: image: postgres:16 environment: - POSTGRES_DB=payments - POSTGRES_PASSWORD=password
정량적 결과
| 지표 | 기존 예상 | Cursor 활용 실제 | 개선율 |
|---|---|---|---|
| 소요 기간 | 12주 | 4주 | 67% 단축 |
| 투입 인원 | 3명 | 1명 | 67% 절감 |
| 수정 파일 수 | 수동 추적 불가 | 187개 파일 | 전수 추적 |
| 테스트 커버리지 | 기존 23% | 마이그레이션 후 78% | 3.4배 향상 |
| 배포 시간 | 45분 (전체) | 서비스당 8분 | 82% 단축 |
git checkout으로 즉시 복원할 수 있다. 각 서비스 분리 작업마다 커밋하라.- **Tab 자동완성 극대화**: Cursor Tab은 현재 편집 맥락에서 다음 코드를 예측한다. 패턴이 반복되는 마이그레이션 작업에서 생산성이 극대화된다.
## Troubleshooting: 자주 발생하는 문제와 해결법
인덱싱이 완료되지 않아 @codebase가 부정확한 경우
# Cursor 하단 상태바에서 인덱싱 진행률 확인
# 인덱싱 재시작: Ctrl+Shift+P → "Cursor: Reindex Project"
# .cursorignore로 불필요한 파일 제외하여 속도 향상
Composer가 너무 많은 파일을 한번에 수정하려는 경우
한 번에 5~7개 파일까지가 최적이다. 프롬프트에 "payments/models.py와 payments/views.py만 수정해줘"처럼 범위를 명시한다.
생성된 코드에서 import 오류 발생
# 기존 모놀리스의 상대 경로 import가 남아있는 경우
Cursor Chat에서:
@codebase payment_service 내에서 아직 users 앱을 직접 import하는
코드가 있는지 찾아서 모두 API 호출로 교체해줘
API 키 인증 오류 (서비스 간 통신)
# 환경변수 확인
echo $INTERNAL_API_KEY
# docker-compose에서 환경변수 전달 확인
docker-compose config | grep INTERNAL_API_KEY
# settings.py에서 환경변수 로드
import os
INTERNAL_API_KEY = os.environ.get("INTERNAL_API_KEY", "YOUR_API_KEY")
자주 묻는 질문 (FAQ)
Q1: Cursor 무료 버전으로도 대규모 마이그레이션이 가능한가요?
Cursor 무료 버전은 느린 모델(GPT-3.5 수준) 사용 횟수에 제한이 있고 프리미엄 모델 사용이 월 50회로 제한됩니다. 5만 줄 규모의 프로젝트에서는 하루에도 수십 번의 Composer/Chat 호출이 필요하므로, Pro 플랜(월 $20)을 권장합니다. Pro 플랜에서는 GPT-4, Claude 등 고성능 모델을 500회/월 사용할 수 있어 충분합니다.
Q2: Cursor가 생성한 코드의 품질을 어떻게 보장하나요?
세 가지 방법을 병행합니다. 첫째, 인라인 diff 리뷰로 모든 변경사항을 한 줄씩 확인합니다. 둘째, Cursor에게 테스트 코드를 함께 생성하도록 요청합니다. 셋째, 기존 테스트 스위트를 매 변경 후 실행하여 회귀 버그를 감지합니다. 이 사례에서는 마이그레이션 중 Cursor가 생성한 코드의 약 15%를 수동으로 수정했으며, 대부분 비즈니스 로직의 미세한 차이에서 발생한 문제였습니다.
Q3: Django 외에 다른 프레임워크의 마이그레이션에도 적용 가능한가요?
Cursor는 언어나 프레임워크에 종속되지 않습니다. Spring Boot 모놀리스를 Spring Cloud 마이크로서비스로, Express.js 단일 앱을 NestJS 마이크로서비스로 전환하는 작업에도 동일한 워크플로우를 적용할 수 있습니다. 핵심은 .cursorrules에 대상 프레임워크의 규칙을 정확히 명시하는 것입니다.