django rest framework 시작하기 - 1

django rest framework 시작하기, 길어질 연재의 첫번째 글입니다.

이 연재에서는 django rest framework 를 단순히 사용하는 것이 아니라 어떤 상황에서 어떤 기능들을 활용해 생산성을 더 끌어올릴 수 있는지, 그 외의 여러 기술적인 고민들에 대해 다룰 예정입니다.

실제로 python/django를 주 서버 스택으로 사용하는 Sendbird에서 배운 노하우와 2020년 제가 맡아서 진행했던 신규 프로젝트에 추가한 노하우까지 꿀팁 대방출 예정이니 좋아요 누르시고 따라오세요! :)

django 소개

django란?

django 는 python 진영의 양대 웹 프레임워크 중 하나로, 일반적인 웹 프레임워크에서 필요한 모든 것들을 갖추고 있으며 커뮤니티 또한 상당해서 언제 선택해도 실패하지 않을 좋은 프레임워크입니다.
다만 django 는 저 옛날(2005) 자체적으로 웹 페이지를 서비스하기 위한 목적으로 만들어져 부분부분 올드한 느낌이 있고, 따로 rest api 를 만들기 위한 편의기능을 갖고 있지는 않아 요즘 트렌드에서 django를 단독으로 사용할 일은 없을 것 같습니다.
저는 django 의 가장 큰 장점으로 강력한 ORM, 그로부터 나오는 생산성을 꼽습니다. ORM 없는 django는 팥 없는 붕어빵이죠.

django-rest-framework란?

django 위에 추가적으로 올라가는 framework 로, spring 과 spring boot의 관계와 같습니다.
라우터, 인증/권한, 데이터 규격화 (시리얼라이저), 필터/페이지네이션, 캐시, 쓰로틀, 렌더러, 테스트 등의 기능을 제공하며, 대체로 django 에서 제공하는 기능을 감싼 wrapper 형태로 되어있습니다.
규약(convention)이 많지만, 웬만한 것들은 설정(configuration)을 통해 원하는 형태로 구현할 수 있습니다. 규약과 규약에서 제공하는 설정 가능성을 존중하는 범주 안에서는 엄청난 생산성 레버리지 효과를 누릴 수 있으며, 필요하다면 그런 이점을 포기하고 프로젝트에서 요구하는 형태로 기능을 구현할 수도 있습니다.

시작하기

python / virtual env 설정

실전에서는 pyenv 를 통해 시스템 python 은 건드리지 않고 추가적으로 원하는 버전의 python 을 설치하는 것을 추천합니다. 특별히 원하는 버전이 없다면 기본 설치되어있는 python 을 사용하셔도 무방합니다.
python package 는 기본적으로 해당 python 에 귀속되는 형태로 저장되기에, virtual environment 를 사용하지 않으면 여러 프로젝트의 package가 뒤섞여버립니다. python 내장 venv 모듈을 통해 프로젝트마다 전용 가상공간을 생성해야 합니다. node의 node_modules처럼 프로젝트 폴더 내에 두어도 깔끔하지만, 요번 연재에서 저는 상위 폴더에 따로 두고 진행하겠습니다.

$ python -m venv {venv_name}
$ source {venv_name}/bin/activate

django 프로젝트 시작하기

django docs 를 띄워두고 같이 보시면 더 좋습니다.

pip install django(=={target_version})
django-admin startproject {project_name} ({location})

위와 같은 느낌으로 설치해주세요. startproject 에서 위치를 지정하지 않으면 project_name 폴더를 생성하고 그 하위에 프로젝트 파일을 생성하기에 스크린샷에서는 . 으로 현재 위치를 지정했습니다. 좋은 framework답게 종속성이 거의 없습니다. django 본체, 시간 관련된 부분을 위한 pytz, sql parsing을 위한 sqlparse, 그리고 ASGI 서포트를 위한 asgiref.
뒤에 한번 이쪽도 자세히 다루겠지만, ASGI (Asynchronous Server Gateway Interface) 는 말 그대로 비동기 서버를 위한 인터페이스입니다. django는 옛스럽게 별도의 WAS에서 request를 받아 python application 으로 넘겨주는 WSGI라는 인터페이스 위에 완전히 동기적으로 구현되어 있는데, node.js 진영의 영향을 받아 비동기로 넘어가는 과정에 있고 ASGI는 이를 위한 새 인터페이스입니다.

django-practice/
	django_practice/
		__init__.py
		settings.py
		urls.py
		asgi.py
		wsgi.py
	manage.py

settings.py에는 설정과 관련된 모든 것들이 들어가고, urls.py는 라우팅 파일인데 settings.py 에서 변경 가능합니다.
manage.py는 django-admin 에 django 를 사용할 때 가장 많이 사용할 환경변수인 DJANGO_SETTINGS_MODULE 을 기본값인 django_practice.settings 로 지정해주는 역할만 합니다. 읽어보기

# export DJANGO_SETTINGS_MODULE=django_practice.settings
$ python manage.py do_something
$ python -m django do_something
$ django-admin do_something

동일한 venv 안에서 환경변수가 설정되어 있다면 위 셋은 완전히 동일합니다.
django_practice 는 프로젝트 파일 폴더로, asgi/wsgi 서버를 띄울때 사용하는 파일과 프로젝트 설정 파일, url을 정의하는 파일로 미니멀하게 구성되어 있습니다.

이 상태에서 서버를 바로 띄워볼 수도 있습니다. 그러면 아래와 같이 migration 경고와 함께 서버가 뜨는 모습을 확인할 수 있습니다.

$ python manage.py runserver

아무것도 하지 않았는데 DB에 대한 migration이 필요하다고 합니다. 그 이유는, settings.py 파일을 열어보면 알 수 있습니다. django 기본 설정에서 admin, auth, contenttypes, sessions라는 django 내장 앱을 참조하고 있기 때문에, 이 앱들에 정의된 모델을 이용하려면 migration이 필요한 상태입니다. 하지만 스크립트 언어 특성상 실제로 해당 모델을 사용하기 전까지 직접적으로 에러가 발생하지는 않기 때문에, 127.0.0.1:8000에 접속하면 장고 기본화면을 볼 수 있습니다.

django app 만들기

이제 프로젝트에 구체적인 django app을 생성할 차례입니다. django 에서는 여러 app을 생성하고 해당 app을 다른 django 프로젝트에서 재사용 할 수 있습니다. 가이드문서

그런데 잘 생각해보면 특정 목적을 가진 프로젝트를 개발하는데, 타 프로젝트에서 재사용이 가능한 완벽히 독립적인 기능셋을 설계해서 개발하기는 쉽지 않습니다. 도메인별로 분리를 시도했다가 결과적으로 서로 종속성이 물리게 된다면 오히려 불필요한 구분을 만드는게 될 수 있기에, 복수의 앱을 사용하는건 충분한 고민과 설계 후에 결정하시는게 좋습니다. 구체적인 그림이 없다면 일단 단일 앱으로 시작하는걸 추천드리고, 이후에 정말 필요하다면 그때 리팩토링 해서 분리해내는게 좋겠습니다. 하나의 앱 내에서 커지는 프로젝트를 관리하기 위한 팁은 곧 설명 드리겠습니다.

프로젝트 폴더 하위에 앱을 생성할 수도 있지만, 불필요한 depth를 늘리지 않기 위해 튜토리얼과 동일하게 루트 폴더 아래에 앱을 생성하겠습니다.

$ python manage.py startapp {app_name}

django-practice/
	app/
		__init__.py
		apps.py
		migrations/
		models.py
		tests.py
		views.py
	django_practice/
	manage.py

앱을 생성하면 이처럼 기본 프로젝트 파일들이 생성됩니다.
apps.py는 앱 로드시, 혹은 앱이 준비된 후 Appconfig.ready() 를 오버라이드 하는 등의 형태로 원하는 로직을 추가할 수 있습니다. 문서
migrations 폴더에는 migration 파일이 저장됩니다. 마이그레이션은 뒤에서 한번 자세하게 다뤄보겠습니다.
models.py 파일 혹은 models 모듈 에는 모델을 정의합니다. 보통 프로젝트가 커지다 보면 모델 역시 같이 커지게 되고, 인스턴스 메소드가 들어가기 시작하면 몇 천줄도 금방입니다. 꼭 모듈을 사용합시다.
views.py 와 tests.py 는 강제되지 않습니다. 뷰나 테스트가 한두개일 수가 없으니, 하나의 파일에서 관리하기는 쉽지 않겠죠? 마찬가지로 모듈을 사용합시다. 이 둘은 이름이 강제되지 않지만 굳이 바꿀 이유도 없으니 그대로 가도 좋습니다.

이렇게 앱을 생성했다면 settings.py의 INSTALLED_APPS 에 'app' (또는 다른곳에 생성했다면 적절한 모듈 경로)을 추가해주면 됩니다.

간단하게 앱이 잘 준비됐나 확인하기 위해 app/views.py 및 django_practice/urls.py 에 다음과 같이 추가해주고, runserver 로 서버를 띄워 127.0.0.1:8000/welcome 에서 Welcome! 이라는 response가 돌아오는지 확인해보셔도 좋습니다.

# app/views.py

from django.http import HttpResponse

def welcome(request):
    return HttpResponse('Welcome!')


# django_practice/urls.py

from app.views import welcome

urlpatterns = [
    ...
    path('welcome', welcome),
]

dependency 관리하기

python 에 포함된 pip (python package installer) 은 단순한 인스톨러라 npm 이나 다른 dependency manager 에 비해 기능이 많이 부족합니다. dev 전용 패키지 구분도 안되고, 패키지 설치와 그 리스트에 대한 관리가 별개로 이루어지고, 패키지들이 같은 공간에 하나의 버전으로만 설치되기 때문에 패키지 C에 대한 dependency를 가진 패키지 A와 B가 원하는 C의 버전이 충돌난다면 직접 해결해야 합니다. 심지어 기존에 설치되어 있던 패키지 여하에 따라 같은 requirements.txt (node에 빗대자면 package.json) 를 설치하더라도 결과가 다를 수 있습니다. 프로젝트 초창기에야 모든 패키지를 최신 버전으로 설치하면 웬만해서는 이슈가 없으니 불편함을 느끼기 어렵지만, 시간이 흘러 패키지 버전 업데이트를 시도했을 때 해결할 수 없는 conflict가 발생해서 일이 많이 늘어날 수 있습니다. 패키지 버전만 변경되는게 아니라 프로젝트 파일도 같이 변경되니까요.

대안으로 pipenv 혹은 poetry가 있습니다. 요번 연재에서는 주제에 집중하기 위해 pip을 사용하겠습니다.

# 현재 설치된 모든 패키지 목록 저장
$ pip freeze > requirements.txt

# requirements.txt
asgiref==3.3.1
Django==3.1.4
pytz==2020.4
sqlparse==0.4.1

# 파일로부터 목록 읽어 패키지 설치
$ pip install -r requirements.txt

django-rest-framework 추가하기

$ pip install djangorestframework
$ pip freeze > requirements.txt

DRF (django rest framework, 이하 DRF) 패키지를 설치한 후 settings.py 에 rest_framework 앱을 추가합니다.

# django_practice/settings.py

INSTALLED_APPS = [
	...
	'rest_framework',
	'app',
]

순서는 앞서 잠깐 언급한 AppConfig.ready() 의 순서 등에 영향을 미치기 때문에 선후관계를 따져서 적절한 위치 - django 뒤, 내 앱 앞 - 에 넣어줍니다.

잘 설치 되었는지 확인하기 위해 DRF에서 편의를 위해 제공하는 기본 로그인/로그아웃 뷰를 활용해봅시다. rest_framework.urls 에 들어가보시면 아래와 같이 login, logout에 뷰가 지정되어 있는 것을 볼 수 있습니다. 아래와 같이 추가 후, 127.0.0.1/api-auth/login/ 에 접속하시면 로그인 창이 반겨줍니다.

# django_practice/urls.py

from django.urls import path, include
from rest_framework import urls

urlpatterns = [
	...
	path('api-auth/', include(urls)),
]


# rest_framework/urls.py

# path('login/', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),
# path('logout/', views.LogoutView.as_view(), name='logout'),

물론 지금은 유저가 없는 상태라 로그인을 할 수는 없습니다.

DB migration

지금까지는 db/model 과 전혀 무관했지만, 유저를 생성하기 위해서는 유저를 저장할 db가 필요합니다. django는 settings.py에서 보셨듯 맨 처음 프로젝트 생성시 sqlite를 기본 db로 사용하고, 프로젝트 루트 폴더에 db.sqlite3 파일을 생성합니다. 뒤에서 RDB로 교체하기로 하고, 지금은 그대로 migration을 돌려봅시다. migration 전후로 showmigrations로 migration 현황을 확인해보세요.

$ python manage.py showmigrations
$ python manage.py migrate
$ python manage.py showmigrations


아직 아무것도 시작한게 없는데 뭔가 많아 보입니다.
결과를 확인하기 위해 직접 sqlite db를 열어볼까요?

왼쪽에서 보시는 것 처럼 테이블이 여럿 생긴걸 확인할 수 있습니다. 이 중 django_migrations 테이블은 다른 테이블과 달리 서비스 데이터가 아니라 django migration 현황을 저장하기 위한 테이블입니다.
migration이 성공하면 저렇게 테이블에 기록을 남기기 때문에, migration 파일의 이름을 함부로 바꾸거나 삭제하면 django에서 migration을 인식하지 못합니다.
여차저차 해서 migration이 꼬이면 migration 파일과 이 migrations 테이블을 잘 만져서 해결할 수 있지만, migration 기능 상의 충돌을 해결하는 것과 실제 나머지 실제 데이터가 있는 테이블이 의도한대로 변경되는건 별개이기 때문에 그런 상황을 만들지 않는게 가장 좋습니다. 강력하고 편한 기능인만큼 주의가 필요합니다 :)

유저 생성

이렇게 기본 테이블이 다 생성됐으니 유저를 생성해봅시다. 아래 커맨드를 실행하면 cli tool이 수퍼유저 혹은 유저 생성을 도와줍니다.

$ python manage.py (createsuperuser|createuser)

이렇게 생성한 유저는 auth_user 테이블에 저장되며 곧바로 위에 추가했던 로그인 페이지에서 이용해볼 수 있습니다. 로그인은 성공하지만, redirect 페이지가 준비되어있지 않기 때문에 404 화면이 뜹니다. debug모드에서의 4xx/5xx 화면은 아래와 같이 디버깅 화면이 나와 개발을 더 편하게 할 수 있습니다. 단, 외부로 노출되는 환경에서는 debug모드를 꼭 꺼주세요.

1회차 마무리

  • django / drf 소개
  • 프로젝트 시작하기 (venv, django, django project, django app, DRF, db migration, 유저 생성)

아직 제대로 시작도 안 한 것 같은데 글이 길어지네요. 2회차에서 이어가겠습니다.

좋은 웹페이지 즐겨찾기