"Write programs that do one thing and do it well."(하나의 기능만 잘 수행하는 프로그램을 작성하라)
Unix philosophy according to Douglas Mcllroy
해당 철학을 따라 각 app은 하나의 task만 처리해야한다. 만약 각 app을 설명할 때 한 문장으로 설명할 수 없다면 app이 나뉘어질 수 있다는 것이다.
아이스크림 가게가 있을 때 각 app은 다음과 같이 나뉘어질 수 있다.
flavors
: 아이스크림 맛과 그를 웹사이트에 보여주는 appblog
: Two Scoops blog 관련 로직events
: 가게의 이벤트들을 띄워주는 appshop
: mail 주문으로 판매할 수 있게 해주는 서비스tickets
: 프리미엄 아이스크림 페스티벌 티켓 판매를 관리하는 앱
하나의 앱에서 이걸 다 하는 것보다 나누는 것이 훨씬 낫다.
App을 위해 쉽고, 기억하기 쉬운 이름을 짓는 것이 좋다.
그리고 PEP 8 가이드에 맞게
- 모두 소문자고,
- 숫자, 대쉬, 마침포, 공백, 특수문자가 없는 이름을 써라
- 필요하다면 언더바(_)를 사용하라
완벽한 App design을 만들기 위해서 너무 최선을 다하지 마라. 그냥 이것만 기억해라.
App을 최대한 작게 만드는 것에 집중해라
99%의 Django app들은 이런 식으로 구성되어 있다.
# Common modules
scoops/
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── management/
├── migrations/
├── models.py
├── templatetags/
├── tests/
├── urls.py
├── views.py
하지만 우리는 이런식으로 구성한다.
# uncommon modules
scoops/
├── api/
├── behaviors.py
├── constants.py
├── context_processors.py
├── decorators.py
├── db/
├── exceptions.py
├── fields.py
├── factories.py
├── helpers.py
├── managers.py
├── middleware.py
├── schema.py
├── signals.py
├── utils.py
├── viewmixins.py
api/
: api를 �구성할 때 필요한 다양한 모듈들을 분리해놓는다.behaviours.py
: Model mixin들에 대한 정보를 포함한다.constants.py
: app-level 설정들을 저장해두는 파일.decorators.py
: Decorator들을 위치시키는 파일db/
: custom model field나 component를 저장하는 파일fields.py
: form field를 위한 파일factories.py
: Test data factory를 위한 파일utils.py
: Helper function들을 ㅈ저장하는 파일.view
와model
을 가볍게 만들기 위해 사용한다.managers.py
:models.py
가 엄청나게 커질 때 custom model manager를 이곳에다 둔다.schema.py
: GraphQL을 위한 파일signals.py
: Custom signal을 위한 파일viewmixins.py
: View mixin 저장 파일
Ruby on Rails style 적용에 대한 예시
예를 들어 user를 만들고, user에게 티켓을 주는 예시를 생각해보자.
전통적인 접근법에서는
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 manager에서 ticket manager를 부르는 식으로 구현한다. 이는 User app과 Ticket app의 dependency를 강화한다.
Service Layer의 접근에서는 services.py
와 selectors.py
를 통해 서비스 로직을 구현할 수 있다.
# 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
이런 접근의 장점은 다음과 같다.
- 17줄이 12줄로 줄었다.
- user와 ticket 코드의 의존성이 줄어든다.
- 책임감의 분리를 통해 유저를 생성하는 로직과 티켓을 부여하는 로직을 분리해줄 수 있다.
- (Optional) type annotation을 통해서 return 되는 객체에 대해서 쉽게 알 수 있다.
단점은 다음과 같다.
- 작은 프로젝트의 경우에는 복잡성만 더한다.
- 복잡한 프로젝트의 서비스 레이어는 1000줄 이상으로 비대해질 것이다.
selectors.py
는 ORM이 할 수 있는 일을 굳이 wrapping할 수 있다.- Django는 model에 business logic을 넣는 식으로 발전하기 때문에 그런 능력을 잃게 된다.
하나의 app으로 전체 프로젝트를 관리한다.
migration에 이점이 있다. Ruby on rails나 다른 프레임워크은 이런 패턴을 따르게 돼있지만, Django는 이런 디자인으로 최적화 되지 않는다.
- App 사이즈는 최대한 줄여라.
- App이 하는 일을 한 문장으로 설명하지 못한다면 줄여야된다는 신호다.