[Two Scoops of Django] 4장. 장고 앱 디자인의 기본

guava·2021년 9월 4일
0

Two Scoops of Django

목록 보기
4/12
post-thumbnail

Two Scoops of Django 3.x를 읽고 정리한 글입니다.

장고 앱이란? 프로젝트를 구성하는 라이브러리를 지칭한다. 다음은 장고앱이 어떻게 사용하는지 이해하기 위한 용어 정리이다.

  • 장고 프로젝트: 장고 웹 프레임워크를 기반으로 한 웹 앱
  • 장고 앱: 프로젝트의 한 기능을 표현하기 위해 디자인된 작은 라이브러리. 장고 프로젝트는 여러개의 장고 앱으로 구성되어 있으며, 외부 장고 패키지를 지칭하기도 한다.
  • INSTALLED_APPS: 프로젝트에서 사용되고 있는 장고 앱들
  • 서드 파티 장고 패키지: 재사용 가능한 플러그인 형태로 이용 가능한 장고 앱

4.1. 장고 앱 디자인의 황금률


"좋은 장고 앱을 정의하고 개발하는 것은 더글라스 맥얼로이(Douglas Mcllroy)의 유닉스 철학을 따르는 것이다. '한번에 한 가지 일을 하고 그 한 가지 일을 매우 충실히 하는 프로그램을 짜는 것이다'"
- James Bennett

⇒ 각 앱이 그 앱의 주어진 임무에만 집중할 수 있어야 한다.

앱을 설명하기위해 '그리고/또한'이란 단어를 한 번 이상 사용해야 한다면, 너무 커져서 나누어야 할 때가 되었다는 뜻.

4.1.1. 실제 예를 통해 프로젝트 앱들을 정의해보자.

  • 프로젝트 이름: twoscoops_project
    • flavors 앱 : 상점의 모든 아이스크림 종류가 기록되고 그 목록을 웹 사이트에 보여주는 앱
    • blog 앱 : 상점 Two Scoops의 공식 블로그
    • events 앱 : 상점의 행사 내용을 웹 사이트에 보여주는 앱

⇒ 각 앱은 하나의 역할만 수행한다. 물론 각 앱은 연관되어있지만 세분화된 별도의 앱으로 구성되었다.

프로젝트 구성이 잘 되어있다면 다음 앱들을 추가함으로써 사이트 확장이 가능하다.

  • shop 앱: 온라인 주문을 통해 아이스크림을 판매하는 앱
  • tickets 앱: 무제한 아이스크림 행사에 이용될 티켓 판매를 관리하는 앱

주목할 점은, events앱을 확장하는 것이 아니라 tickets앱을 생성했다. 이는 모든 이벤트가 티켓을 필요로 하는것이 아니며 사이트가 성장함에 따라 이벤트 캘린더와 티켓 판매 사이에 복잡한 로직이 생길 수 있기 때문이다.

4.2. 장고 앱 이름 정하기

한 단어로 된 이름을 이용하면 명료함으로 인해 관리가 쉬워진다. (flavors, animals, blog, polls ...)

일반적인 예

  1. 앱의 중심이 되는 모델 이름의 복수 형태 (blog와 같은 예외도 있다.→ 옮긴이: 이 프로젝트에서는 하나의 블로그만 운영하므로 복수를 사용하면 의미가 맞지 않다.)

  2. 사이트의 URL을 고려

    ex) URL이 https://www.example.com/weblog 일 때 메인이 되는 모델이 post라고 할지라도 posts로 짓기 보다는 weblog가 적합할 수 있다.

PEP-8 규약

  • 소문자로 구성된 숫자, 대시(-), 마침표(.), 스페이스, 특수문자를 포함하지 않는 짧은 단어
  • 밑줄(_)을 이름에 이용하는것이 권장되지는 않으나, 가독성을 높이려 한다면 단어들 사이의 공간을 나타내기위해 밑줄 사용 가능

4.3. 확신 없이는 앱을 확장하지 않는다.

대충 작게 유지하라는 내용.

4.4. 앱 안에는 어떤 모듈이 위치하는가?

4.4.1. 공통 앱 모듈

# Common modules
scoops/
├── __init__.py
├── admin.py
├── forms.py
├── management/
├── migrations/
├── models.py
├── templatetags/
├── tests/
├── urls.py
├── views.py
  • 시간이 지남에 따라 형성된 필수 모듈들의 이름이다.
  • 모두가 이 규칙을 지켜야 하는것은 아니지만 지키지 않으면 종국에는 여러 문제점에 봉착할 수 있다. (재사용성, 확장성, 다른 모듈에 대한 도입 등등??)

4.4.2. 비공통 앱 모듈

아래에 나열된 비공통 모듈은 전역환경이 아닌 앱 레벨에서 적용되는 것들이다. (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절)

4.5. 대안: Ruby on Rails 스타일 접근 방식


  • Ruby on Rails는 django와 동시대에 성공한 프레임워크이다. (GitHub, GitLab, Coinbase, Stripe, Square ...)
  • Django-Rails, Python-Ruby사이는 유사한 면이 많다.

4.5.1. 서비스 레이어

초보자의 흔한 고민?? → Django에서 비즈니스 로직은 어디로 가야하는가?

고전적인 예로는 다음과 같이 여러 model과 app에서 유저객체와 관련된 데이터를 생성하는 방식이 있다.

다음과 같은 작업이 있다고 하자.

  1. 시스템은 manager메소드에서 create_user()을 호출해서 유저를 생성한다.
  2. create_ticket()은 호스팅 시스템 파일로 유저의 사진을 업로드한다.
  3. create_ticket()은 또한 manager메소드에 있는 create_ticket()의 호출에 의해 ticket객체를 생성한다.
  4. create_ticket()은 타사 서비스인 incecreamlandia check-in app에게 API를 호출한다.

이런 액션들의 일반적인 배치는 다음과 같이 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

이 접근방식의 장점

  1. 느슨한 결합으로 인해 Ticket 코드를 쉽게 수정할 수 있다.
  2. 관심사 분리 시 개발 구성 요소의 기능 테스트를 쉽게 할 수 있다.
  3. 일반 Django프로젝트보다 return된 객체의 type annotations추가가 쉽다.

단점은??

  1. 소규모 프로젝트일 때 불필요한 복잡성이 추가된다.
  2. 복잡판 프로젝트일때 서비스 계층은 수천줄의 코드가 증가하며, 이를 지원하기 위해 새로운 아키텍처의 개발을 강요하게 된다.
  3. selectors.py를 활용하면 간단한 ORM쿼리조차도 추가 단계가 필요하다.
  4. Django 영속성(persistence)은 모델 자체에 배치된 비즈니스 로직을 기반으로 한다. 서비스 계층은 이를 제거하고 일부 고급 기술을 제거하기도 한다. → 장고가 장고답지 않아진다??

Two Scoops of Django의 저자는 프로젝트가 서비스레이어를 사용함으로써 실패하는 경우를 더 자주 보았다고 하며, 추가적인 추상화를 위해 노력할 필요는 없을수도 있다고..

4.5.2. 대형 단일 앱 프로젝트

  • 프로젝트의 모든 코드를 하나의 앱으로 넣는 방식
  • 작은 Django 프로젝트에서는 그럴 수 있으나 커지면 이 패턴을 따르지는 않는다.
  • 마이그레이션이 쉽고 테이블 이름이 단순해지는 장점이 있다.
  • 레일즈 등 다른 프레임워크는 이 기술을 수용하고 있으나, django는 이 설계에 적합하지 않다.

4.6. 요약

  • 장고 앱은 그 앱 자체가 지닌 한가지 역할에 초점이 맞추어져있고 이름이 단순해야 한다.
  • 앱의 기능이 복잡하다면 여러개의 작은 앱으로 나누어야 한다.

0개의 댓글