Two Scoops of Django 3.x를 읽고 정리한 글입니다.
장고 앱이란? 프로젝트를 구성하는 라이브러리를 지칭한다. 다음은 장고앱이 어떻게 사용하는지 이해하기 위한 용어 정리이다.
"좋은 장고 앱을 정의하고 개발하는 것은 더글라스 맥얼로이(Douglas Mcllroy)의 유닉스 철학을 따르는 것이다. '한번에 한 가지 일을 하고 그 한 가지 일을 매우 충실히 하는 프로그램을 짜는 것이다'"
- James Bennett
⇒ 각 앱이 그 앱의 주어진 임무에만 집중할 수 있어야 한다.
앱을 설명하기위해 '그리고/또한'이란 단어를 한 번 이상 사용해야 한다면, 너무 커져서 나누어야 할 때가 되었다는 뜻.
⇒ 각 앱은 하나의 역할만 수행한다. 물론 각 앱은 연관되어있지만 세분화된 별도의 앱으로 구성되었다.
프로젝트 구성이 잘 되어있다면 다음 앱들을 추가함으로써 사이트 확장이 가능하다.
주목할 점은, events앱을 확장하는 것이 아니라 tickets앱을 생성했다. 이는 모든 이벤트가 티켓을 필요로 하는것이 아니며 사이트가 성장함에 따라 이벤트 캘린더와 티켓 판매 사이에 복잡한 로직이 생길 수 있기 때문이다.
한 단어로 된 이름을 이용하면 명료함으로 인해 관리가 쉬워진다. (flavors, animals, blog, polls ...)
앱의 중심이 되는 모델 이름의 복수 형태 (blog와 같은 예외도 있다.→ 옮긴이: 이 프로젝트에서는 하나의 블로그만 운영하므로 복수를 사용하면 의미가 맞지 않다.)
사이트의 URL을 고려
ex) URL이 https://www.example.com/weblog
일 때 메인이 되는 모델이 post라고 할지라도 posts
로 짓기 보다는 weblog
가 적합할 수 있다.
대충 작게 유지하라는 내용.
# Common modules
scoops/
├── __init__.py
├── admin.py
├── forms.py
├── management/
├── migrations/
├── models.py
├── templatetags/
├── tests/
├── urls.py
├── views.py
아래에 나열된 비공통 모듈은 전역환경이 아닌 앱 레벨에서 적용되는 것들이다. (31.1절. 유틸리티들을 위한 코어 앱 만들기 참고)
# uncommon modules
scoops/
├── api/ # api를 만들 때 우리가 다양한 모듈을 분리하기 위해 만드는 패키지 (17.3.1. 일관된 API 모듈 이름 지정)
├── behaviors.py # 6.7.1절에서 설명할 모델 믹스인 위치에 대한 옵션
├── constants.py # 앱 레벨에서 이용되는 세팅
├── context_processors.py
├── decorators.py # 데코레이터들이 존재 (9.3절)
├── db/ # 여러 프로젝트에서 이용되는 커스텀 모델이나 컴포넌트
├── exceptions.py
├── fields.py
├── factories.py # 테스트 데이터를 위한 팩터피 파일 (24.3.5절)
├── helpers.py # 뷰에서 추출한 코드(8.5절: 비즈니스 로직이 보이지않게 유지하도록 시도)와 모델을 가볍게 하기 위한 장소(6.7절. Fat model 이해)
├── managers.py # models.py가 너무 커질 경우, 커스텀 모델 매니저를 여기로 이동한다.
├── middleware.py
├── schema.py # 일반적으로 GraphQL API코드가 배치되는 위치.
├── signals.py # 커스텀 시그널을 제공하는 것에 대한 대안으로 커스텀 시그널을 넣기에 유용한 공간. (28장)
├── utils.py # helper.py와 동일하다.
├── viewmixins.py # 뷰 믹스인을 이 모듈로 이전해서 뷰 모듈과 패키지를 가볍게 한다. (10.2절)
초보자의 흔한 고민?? → Django에서 비즈니스 로직은 어디로 가야하는가?
고전적인 예로는 다음과 같이 여러 model과 app에서 유저객체와 관련된 데이터를 생성하는 방식이 있다.
다음과 같은 작업이 있다고 하자.
이런 액션들의 일반적인 배치는 다음과 같이 User와 Ticket에 정의된 manager의 여러 메소드들에 퍼져있게 된다.
class UserManager(BaseUserManager):
"""In users/managers.py"""
def create_user(self, email=None, password=None, avatar_url=None):
user = self.model(
email=email,
is_active=True,
last_login=timezone.now(),
registered_at=timezone.now(),
avatar_url=avatar_url
)
resize_avatar(avatar_url)
Ticket.objects.create_ticket(user)
return user
class TicketManager(models.manager):
"""In tasks/managers.py"""
def create_ticket(self, user: User):
ticket = self.model(user=user)
send_ticket_to_guest_checkin(ticket)
return ticket
서비스 계층 접근 방식은 사용자 객체와 티켓 사이에 관심이 분리되어야 한다.
User코드에서 Ticket코드를 호출하기 위해 로직을 포함하면 두개의 domain이 밀접해진다.
따라서 관심사의 분리를 위해 새 계층이 추가되어야 하며, 이를 서비스 레이어라고 한다. (service.py, selectors.py)
# Service layer example
scoops/
├── api/
├── models.py
├── services.py # Service layer location for business logic
├── selectors.py # Service layer location for queries
├── tests/
서비스 계층 설계에서 위의 코드는 아래와 같이 실행된다.
# In users/services.py
from .models import User
from tickets.models import Ticket, send_ticket_to_guest_checkin
def create_user(email: str, password: str, avatar_url: str) -> User:
user = User(
email=email,
password=password,
avatar_url=avatar_url
)
user.full_clean()
user.resize_avatar()
user.save()
ticket = Ticket(user=user)
send_ticket_to_guest_checkin(ticket)
return user
이 접근방식의 장점
단점은??
Two Scoops of Django의 저자는 프로젝트가 서비스레이어를 사용함으로써 실패하는 경우를 더 자주 보았다고 하며, 추가적인 추상화를 위해 노력할 필요는 없을수도 있다고..