Summary of Two Scoops of Django
1. 코딩 스타일
읽기 쉬운 코드
- 축약적이거나 함축적인 변수명은 피한다.
- 함수 인자의 이름들은 꼭 써 준다.
- 클래스와 메서드를 문서화한다.
- 코드에 주석은 꼭 달도록 한다.
- 재사용 가능한 함수 또는 메서드 안에서 반복되는 코드들은 리팩토링을 해둔다.
- 함수와 메서드는 가능한 한 작은 크기를 유지한다. 어림잡아 스크롤없이 읽을 수 있는 길이가 적합하다.
PEP8를 따른다.
임포트 (import)
임포트 순서
일반적인 경우
- 표준 라이브러리
- 연관 외부 라이브러리
- 로컬 애플리케이션 또는 라이브러리에 한정된 임포트
Django
- 표준 라이브러리
- 코어 장고 임포트
- 장고와 무관한 외부 앱 임포트
- 프로젝트 앱 임포트
임포트 방식
- 절대 임포트 : from app.module import something
- 명시적 상대 : from .module import something
- 암묵적 상대 : from module import something
- 가급적 명시적 상대 임포트를 사용
Never use
import *
장고 코드 스타일
- URL 패턴 이름엔 dash 대신 underscore
- 템플릿 블록 이름에 dash 대신 underscore
HTML, CSS, JS 스타일은 경우에 따라 적당한걸 사용한다.
- IDE나 Editor에 종속되는 코딩 스타일은 지양한다.
2. 최적화된 장고 환경 꾸미기
- 개발과 운영환경에서 같은 DB를 이용
- 서로 다른 DB는 데이터와 타입 호환이 완벽하지 않음.
- PostgreSQL 추천
- pip와 virtualenv 사용
- pip를 이용해 장고와 의존 패키지 설치
- VCS 활용
- (선택) 개발과 운영의 환경을 동일하게 구성
- VirtualBox, Docker, ...
- 단점
- 필요하지 않는 기능들까지 제공되어 복잡해짐. OS레벨에서 고려해야할 필요가 없는 경우, 안하는 편이 더 편리하다.
3. 장고 프로젝트 구성
<repository_root>/
<django_project_root>/
<configuration_root>/
- repository_root : README.md, docs/, .gitignore, requirements.txt, 배포 관련 파일 등등
- django_project_root : Django project 소스들
- configuration_root : settings, uris.py 등등. (유효한 파이썬 패키지)
- Django cookiecutter를 사용할 수도 있음.
4. 장고 앱 디자인의 기본
- 장고 앱 용어
- 장고 프로젝트 : 장고 웹 프레임워크를 기반으로 한 웹 애플리케이션
- 장고 앱 : 프로젝트의 한 기능을 표현하기 위해 디자인된 작은 라이브러리
- INSTALLED_APPS : 프로젝트에 이용하기 위해 INSTALLED_APPS 세팅에 설정한 장고 앱들
- 서드 파티 장고 패키지 : 파이썬 패키지 도구들에 의해 패키지화된, 재사용 가능한 플러그인 형태의 장고 앱
- 각 앱은 그 앱의 주어진 임무에만 집중할 수 있어야함
- 앱 이름 정하기
- 가능한 한 한 단어
- 일반적으로 앱의 중심이 되는 모델의 복수 형태 (blog등은 예외)
- URL의 주소가 어떻게 되는지도 고려
- PEP8 규약에 맞게
- 되도록이면 앱들을 작게 유지
- 공통 앱 모듈 / 비공통 앱 모듈
- 공통 앱 모듈의 이름 규약은 가급적 따르자.
5. settings과 requirements 파일
- (최선의) 장고 설정 방법
- 버전 컨트롤 시스템으로 모든 설정 파일을 관리 (운영 환경에서 중요)
- 반복되는 설정을 없앰
- 암호나 비밀키 등은 안전하게 보관 (버전 컨트롤 시스템에서 제외)
- 버전 관리되지 않는 로컬 세팅은 피한다.
- 로컬 세팅 방식은 모든 곳에 버전 컨트롤되지 않은 코드가 존재하게 됨
- 각기 다른 로컬 세팅을 가질 경우, 어느 쪽에서 문제가 생기면 재현하기도 어렵고 원인 추적도 어려워짐
- 팀원들이 서로의 로컬 세팅을 복사하고 붙여쓰는데에 반복적인 일이 생기고 비효율적이게 됨
- 여러 개의 settings 파일 이용
settings
아래에base.py
,local.py
,staging.py
,test.py
,production.py
로 각기 다른 세팅 파일을 둔다. (각 세팅 모듈은 독립적인 requirements가 필요)- 규모에따라 추가적인 세팅 파일이 필요할 수도 있음 (ex. CI서버 환경 파일)
- 만약
local.py
를 이용하여 셸을 시작할 경우,python manage.py shell --settings=twoscoops.settings.local
를 쓴다 - --settings를 사용하는 대신 DJANGO_SETTINGS_MODULE 환경 변수를 조건에 맞는 세팅 모듈 패스로 설정할 수도 있음
- 다중 개발 환경 세팅
- 팀원별로 각기 다른 개발 환경 세팅 파일을 사용할 경우,
dev_<name>.py
형식의 파일을 VCS에서 관리하며 사용할 수 있다.
- 팀원별로 각기 다른 개발 환경 세팅 파일을 사용할 경우,
- 코드에서 설정 분리
- 비밀키들은 설정값들이지, 코드가 아니다.
- 환경 변수 패턴
- bash등의 설정 파일에 설정값들을 export해놓고 코드에서 환경 변수값을 불러와 사용한다.
- 만약 비밀키가 존재 하지 않을 경우 Django 코어 익셉션의
ImproperlyConfigured
를 임포트하여 예외를 발생시킨다.
- 환경 변수를 사용할 수 없을 때
- Apache, Nginx 환경에서 환경 변수 사용을 못할 수 있다. 이 땐 비밀 파일 패턴을 사용한다
- 비밀 파일 패턴
- JSON, Config, YAML 또는 XML중 한 가지 포맷을 선택해 비밀 파일을 생성
- 비밀 파일을 관리하기 위한 비밀 파일 로더를 추가
- 비밀 파일의 이름을 .gitignore 또는 .hgignore에 추가
- 여러 개의 requirements 파일 이용
requirements
아래에base.txt
,local.txt
,staging.txt
,production.txt
로 각기 다른 requirements 파일을 둔다.base.txt
는 공통 의존 패키지- 나머지의 경우 상단에
-r base.txt
로 공통 패키지를 포함하고 아래에 추가 패키지를 적는다.
- settings에서 파일 경로 처리
- Fixes path가 아닌 Base path 기준의 Relative path를 사용하자.
6. 장고 모델
- 모델이 너무 많으면 앱을 나눈다.
- 모델 상속
파이썬 표준 라이브러리의 abc에서의 추상화 기초 클래스와 장고에서의 추상화 기초 클래스는 서로 목적이 전혀 다르다.
모델의 상속 스타일 | 장점 | 단점 |
---|---|---|
상속 없음 : 모델들 사이에 공통 필드가 존재할 경우, 두 모델에 전부 해당 필드를 만들어 줌 | 테이블 매핑에 신경을 안써도되며, 간단함 | 모델간의 중복 테이블이 많을 경우 관리 어려움 |
추상화 기초 클래스 : 오직 상속받아 생성된 모델들의 테이블만 생성 | 공통 부분 재사용 가능. 추가 테이블 필요 없음. 조인으로 인한 성능 저하 없음. | 부모 클래스를 독립적으로 이용 못함 |
멀티테이블 상속 : 부모와 자식 모델에 대해서 모두 테이블이 생성됨. OneToOneField는 부모와 자식 간에 적용됨. | 각 모델에 대해 매칭되는 테이블이 생성됨. 따라서 부모 또는 자식 모델 어디로든지 쿼리 가능. 부모 객체로부터 자식 객체 호출 가능. | 자식 테이블에 대한 각 쿼리에 대해 부모 테이블로의 조인이 필요해 부하 발생. 권하지 않음 |
프록시 모델 : 원래 모델에 대해서만 테이블이 생성됨. | 각기 다른 파이썬 작용(behavior)을 하는 모델들의 별칭을 가질 수 있음 | 모델 필드 변경 불가 |
- DB 마이그레이션
- 마이그레이션 생성 팁
- 새로운 앱이나 모델이 생성되면 새 모델에 대해 makemigrations 실행
- 생성된 마이그레이션 코드를 실행하기 전에 생성된 코드를 한 번 봄.
sqlmigrate
를 통해 SQL문이 어떻게 생성되었는지도 확인 가능 - 외부 앱에 대해 마이그레이션을 처리할 때에는 MIGRATION_MODULES 세팅 이용
- 마이그레이션 배포 및 관리
- 배포 전에 마이그레이션이 rollback 가능한지 확인하자.
- 데이터가 많이 존재한다면, 실제 마이그레이션 실행 전에 스테이징 서버에서 비슷한 규모의 데이터에 대해 충분히 테스트하자. (운영 환경에선 많은 시간이 걸림)
- MySQL를 사용한다면
- 스키마 변환전에 DB 백업 필수. MySQL은 스키마 변경에 대한 트랜잭션 미지원. (rollback 불가능)
- DB를 변환하기 이전에 프로젝트를 읽기 전용모드로 변경
- 상당히 큰 테이블의 경우 스키마 변경에 상당한 시간이 소요됨
- 마이그레이션 생성 팁
- 모델 디자인
- DB 정규화
- 캐시의 비정규화 (성능 향상)
- 반드시 필요한 경우에만 비정규화 수행
- null과 공백
필드 타입 | null=True | blank=True |
---|---|---|
CharField, TextField, SlugField, EmailField, CommaSeparatedIntegerField, UUIDField | X | O. 위젯이 빈 값을 허용하기를 원한다면 설정한다. |
FileField, ImageField | X | O |
BooleanField | X. 대신 NullBooleanField 이용 | X |
IntegerField, FloatField, DecimalField, DurationField | 해당 값이 DB에 NULL로 들어가도 상관없다면 이용 | 위젯에서 해당 값이 빈 값을 받아와도 상관없다면 null=True와 같이 이용 |
DateTimeField, DateField, TimeField | DB에서 해당 값들을 NULL로 설정하는게 가능하다면 이용 | 위젯에서 해당 값이 빈 값을 받아와도 문제가 없다거나 auto_now나 auto_now_add를 이용하고 있다면 null=True와 같이 이용 |
ForeignKey, ManyToManyField, OneToOneField | DB에서 해당 값들을 NULL로 설정하는게 가능하다면 이용 | 위젯에서 해당 값이 빈 값을 받아와도 괜찮다면 이용 |
GenericIPAddressField | DB에서 해당 값을 NULL로 설정하는게 가능하다면 이용 | 위젯에서 해당 값이 빈 값을 받아와도 괜찮다면 이용 |
IPAddressField | X | X |
- BinaryField 이용
- 메시지팩 형식의 콘텐츠
- 원본 센서 데이터
- 압축된 데이터. ex. 센트리(Sentry)가 블롭(BLOB)으로 저장했지만 레거시 이슈등으로 base64로 인코딩된 데이터들의 형식
- 이를 이용하여 파일을 직접 서비스하지는 않는다. (성능 이슈)
- 범용 관계(generic relations)는 피하자.
- 모델 간의 인덱싱이 존재하지 않으면 쿼리 속도에 손해를 가져옴
- 다른 테이블에 존재하지 않는 레코드를 참조할 수 있는 데이터 충돌의 위험성이 존재
- PostgreSQL에만 존재하는 필드에 대한 null과 공백의 사용 여부
필드 타입 | null=True | blank=True |
---|---|---|
ArrayField | O | O |
HStoreField | O | O |
IntegerRangeField, BigIntegerField, FloatRangeField | DB에서 해당 값들을 NULL로 설정할 수 있다면 이용 가능 | 위젯에서 해당하는 폼이 빈 값을 허용하기를 원한다면 null=True와 함께 사용 |
DatetimeRangeField, DateRangeField | DB에서 해당 값들을 NULL로 설정할 수 있다면 이용 가능 | 위젯에서 해당 값이 빈 값을 허용하기를 원할 경우 또는 auto_now나 auto_now_add를 이용하고 있다면 null=True와 같이 이용 |
- 모델의 _meta API
- 사용 이유
- 모델 필드의 리스트를 가져올 때
- 모델의 특정 필드의 클래스를 가져올 때 (또는 상속 관계나 상속 등을 통해 생성된 정보를 가져올 때)
- 앞으로의 장고 버전들에서 이러한 정보를 어떻게 가져오게 되었는지 확실하게 상수로 남기기를 원할 때
- 사용 예시
- 장고 모델의 자체 검사 도구
- 라이브러리를 이용해서 특별하게 커스터마이징된 자신만의 장고를 만들 때
- 장고의 모델 데이터를 조정하거나 변경할 수 있는 일종의 관리도구를 만들 때
- 시각화 또는 분석 라이브러리를 제작할 때, 예를 들어, 'foo'라는 단어로 시작하는 필드에 대한 분석 정보
- 사용 이유
모델 매니저
기존 모델 매니저를 교체하는 경우의 주의점
- 추상화 기초 클래스의 자식들은 부모 모델의 모델 매니저를 받게 되고, 접합 기반 클래스들의 자식들은 그렇지 못함
- 모델 클래스에 적용되는 첫번 째 매니저는 장고가 기본값을 취급하는 매니저이다. 따라서 쿼리셋으로부터 결과를 예상할 수 없게 만든다.
모델 매니저의 적용 순서 :
objects = modles.Manager()
는 항상 새로운 이름의 커스텀 모델 매니저 위에 두도록 한다.
- 거대 모델
- 데이터 관련 코드를 뷰와 템플릿이 아닌 모델의 메서드 또는 매니저 메서드에 넣어 캡슐화
- 재사용성 개선 가능
- 그러나, 신의 객체 수준으로 증가시킨다면 모델이 매우 복잡해짐
- 복잡함을 낮추는 기술
- 모델 행동 (믹스인) : 모델 행동은 믹스인을 통한 캡슐화와 구성화의 개념으로 이루어짐. 모델은 추상화 모델로부터 로직들을 상속받음.
- 상태 없는 헬퍼 함수 : 로직을 모델로부터 떼어내 독립적인 유틸리티 함수로 넣음. 유닛 테스트가 편해짐. 다만 stateless하기 때문에 더 많은 인자를 필요로 함
7. 쿼리와 데이터베이스 레이어
- 단일 객체에서
get_object_or_404()
사용- 단, 뷰에서만 이용
- 쿼리 예외 처리
- ObjectDoesNotExist : 어떤 모델 객체에도 사용 가능 (
except ObjectDoesNotExist
) - DoesNotExist : 특정 모델에서만 사용 가능 (
except ModelName.DoesNotExist
) - MultipleObjectsReturned : 여러 개의 객체가 반환될 수 있는 경우 사용 (
except ModelName.MultipleObjectsReturned
)
- ObjectDoesNotExist : 어떤 모델 객체에도 사용 가능 (
- 코드 가독성과 유지보수의 용이성을 위해 지연 연산 활용
- 고급 쿼리 도구
- 데이터 처리를 언어 차원에서 하지않고 DB 내부에서 처리하게 함으로써 성능 향상을 얻을 수 있으며, 경합 상황(race condition)을 피할 수 있음
- DB 함수를 사용함으로써 성능 향상을 얻을 수 있음
- Raw SQL은 지양
- Raw SQL을 직접 이용함으로써 파이썬 코드나 ORM을 통해 생성된 코드가 월등히 간결해지고 단축되는 경우에만 사용
- 인덱스를 사용해야할 경우
- 인덱스가 빈번하게 (10~25%) 이용될 때
- 실제 데이터 또는 실제와 비슷한 데이터가 존재해서 인덱싱 결과에 대한 분석이 가능할 때
- 인덱싱을 통해 성능이 향상되는지 테스트할 수 있을 때
- 트랜잭션
- 각각의 HTTP요청을 트랜잭션으로 처리
'ATOMIC_REQUESTS': True
- 모든 쿼리가 보호되어 안전성 확보 가능
- 그러나 성능 저하를 발생시킬 수 있음
- DB가 아닌 아이템에 대해 데이터를 변형하는 뷰를 만들 때에는 해당 뷰를
transaction.non_atomic_requests()
로 데코레이팅함을 고려
- 명시적인 트랜잭션 선언
- 성능 문제가 심각하지 않는 한
ATOMIC_REQUESTS
를 이용
- 성능 문제가 심각하지 않는 한
- 트랜잭션 가이드라인
- DB에 변경이 생기지 않는 작업은 트랜잭션으로 처리하지 않는다.
- DB에 변경이 생기는 작업은 반드시 트랜잭션으로 처리한다.
- DB 읽기 작업을 수반하는 변경 작업 또는 DB 성능에 관련된 특별한 경우에는 앞의 두 가이드라인을 모두 고려한다.
- 각각의 HTTP요청을 트랜잭션으로 처리
목적 | ORM 메서드 | 트랜잭션 이용? |
---|---|---|
데이터 생성 | .create(), .bulk_create(), .get_or_create() | Yes |
데이터 가져오기 | .get(), .filter(), .count(), .iterate(), .exists(), .exclude(), .in_bulk() 등 | |
데이터 수정하기 | .update() | Yes |
데이터 지우기 | .delete() | Yes |
- 독립적인 ORM 메서드 호출을 트랜잭션으로 처리하지 말자.
django.http.StreamingHttpResponse
와 트랜잭션- 뷰가
django.http.StreamingHttpResponse
를 반환한다면 응답이 시작된 이상 트랜잭션 에러를 중간에 처리하기란 불가능 ATOMIC_REQUESTS
를 False로 설정 후, 명시적인 트랜잭션 선언을 고려
- 뷰가
- 참고자료
8. 함수 기반 뷰(FBV)와 클래스 기반 뷰(CBV)
- 범용적인 CBV로 가능하거나 복잡한 뷰 처리가 필요하지 않을 경우 CBV를 사용하며, CBV를 사용했을 경우 더 복잡해지거나 커스텀 에러가 필요할 경우 FBV
- URLConf로부터 뷰 로직 분리 (느슨한 결합 유지)
- URL 네임스페이스 활용
- 서드파티 라이브러리와의 상호 운용성 증가
- 검색, 업그레이드, 리팩토링이 쉬움
- URLConf에서 뷰를 문자열로 지목하지 말자. (< 1.8)
- 뷰에서 비즈니스 로직 분리 (재사용 가능한 비즈니스 로직 컴포넌트)
locals()
를 뷰 컨텍스트에 이용하지 말자.- 명시적 컨텐츠를 이용
9. 함수 기반 뷰(FBV)의 모범적인 이용
- 모든 함수는
HttpRequest
객체를 받고HttpResponse
객체를 반환한다. - FBV 가이드라인
- 뷰 코드는 작을수록 좋다.
- 뷰에서 절대 코드를 반복해서 사용하지 말자.
- 뷰는 프레젠테이션(presentation) 로직을 처리해야 한다. 비즈니스 로직은 가능한 한 모델 로직에 적용시키고 만약 해야 한다면 폼 안에 내재시켜야 한다.
- 뷰를 가능한 한 단순하게 유지하라.
- 403, 404, 500을 처리하는 커스텀 코드를 쓰는 데 이용하라.
- 복잡하게 중첩된
if
블록 구문은 피하라.
HttpRequest
객체를 활용한 코드 단순화 및 재사용- 데코레이터를 잘 활용하되 남용하지는 말라.
HttpResponse
객체 또한 함수간 전달이 가능하다.