[SWE-007][중급] 업무 규칙을 코드의 도메인 모델로 표현하기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[SWE-007][중급] 업무 규칙을 코드의 도메인 모델로 표현하기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 136회 작성일 26-09-02 23:38

본문

[이번 수업]
도메인 모델링은 업무 개념과 규칙을 코드로 표현하는 일입니다. DDD(Domain-Driven Design, 도메인 주도 설계)의 기본 용어를 익히고 주문 모델이 잘못된 상태를 막게 만듭니다.

[선수지식]
SWE-002의 모듈, SWE-003의 테스트, SWE-006의 계층을 알고 Python 클래스와 예외를 읽을 수 있어야 합니다.

[학습목표]
1. 보편 언어와 경계가 있는 컨텍스트를 설명한다.
2. 엔티티·값 객체·애그리거트를 구분한다.
3. 업무 불변식을 모델 안에서 지킨다.

[핵심개념]
도메인은 프로그램이 해결하는 업무 영역입니다. 현업 담당자와 개발자가 같은 뜻으로 쓰는 보편 언어(Ubiquitous Language)를 정하고 문서·코드·테스트에서 `주문`, `상품 추가`, `주문 확정`처럼 같은 말을 사용합니다.

경계가 있는 컨텍스트(Bounded Context)는 용어와 규칙이 일관되게 통하는 범위입니다. 판매의 `고객`과 배송의 `수령인`처럼 필요한 규칙이 다르면 공통 클래스 하나에 모두 넣지 않고 경계별 모델을 만듭니다.

엔티티(Entity)는 속성이 바뀌어도 같은 대상을 추적할 식별자가 있습니다. 값 객체(Value Object)는 별도 정체성보다 값 전체가 중요합니다. 같은 금액의 `Money`는 같은 값이며 생성 후 바꾸지 않고 새 값으로 교체합니다.

애그리거트(Aggregate)는 한 작업에서 함께 일관되어야 하는 객체의 경계이며, 애그리거트 루트가 외부 변경의 입구입니다. `수량은 1 이상`, `빈 주문은 확정할 수 없음`처럼 항상 참이어야 하는 규칙이 불변식(Invariant)입니다. 규칙을 화면마다 복사하지 말고 주문 메서드가 지키게 합니다. 단순 CRUD에는 복잡한 DDD 패턴이 필요하지 않을 수 있습니다.

[따라하기]
`Money`는 값 객체, `Order`는 식별자와 규칙을 가진 애그리거트 루트입니다. 아래 코드를 `order_domain.py`로 저장합니다.

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class Money:
    amount: int

    def __post_init__(self):
        if self.amount < 0:
            raise ValueError("금액은 0 이상이어야 합니다")

class Order:
    def __init__(self, order_id: str):
        self.order_id = order_id
        self._items = []
        self.status = "draft"

    def add_item(self, price: Money, quantity: int):
        if self.status != "draft" or quantity < 1:
            raise ValueError("초안에 1개 이상만 추가할 수 있습니다")
        self._items.append((price, quantity))

    def submit(self):
        if not self._items:
            raise ValueError("빈 주문은 확정할 수 없습니다")
        self.status = "submitted"

    def total(self) -> Money:
        return Money(sum(p.amount * q for p, q in self._items))

order = Order("ORDER-001")
order.add_item(Money(5000), 2)
order.add_item(Money(7000), 1)
order.submit()
print(order.order_id, order.total(), order.status)
print(Money(17000) == order.total())
```

Windows PowerShell에서는 `py order_domain.py`, macOS·Linux에서는 `python3 order_domain.py`를 실행합니다. 예상 결과입니다.

```text
ORDER-001 Money(amount=17000) submitted
True
```

[흔한 실수]
- 데이터베이스 표를 복사해 행동 없는 클래스만 만든다.
- 같은 용어를 모듈마다 다른 뜻으로 사용한다.
- 모든 업무를 하나의 거대한 모델로 합친다.
- 내부 목록을 노출해 루트의 규칙을 우회한다.

[보안 주의]
도메인 불변식은 인증과 권한 검사를 대신하지 않습니다. 외부 입력은 API 경계에서 형식과 크기를 검사하고 모델에서도 업무 규칙을 확인합니다. 주문 ID를 안다고 다른 사용자의 주문을 바꿀 수 없도록 소유권을 검사하며 개인정보·비밀값을 예외와 로그에 남기지 않습니다. 실습은 본인 소유의 로컬 가상 데이터로만 진행합니다.

[직접 해볼 과제]
`cancel()`을 추가하세요. `draft`나 `submitted`만 취소하고 이미 `cancelled`면 예외를 내게 합니다. 정상 취소와 두 번째 취소 실패를 실행하세요.

[확인문제]
1. 엔티티와 값 객체를 구분하는 핵심 기준은 무엇인가요?
2. 애그리거트 루트로 내부를 변경하는 이유는 무엇인가요?
3. 단순 CRUD에 복잡한 DDD가 항상 필요하지 않은 이유는 무엇인가요?

[다음 학습]
다음 MOBILE-007에서는 앱 화면 전환과 내비게이션을 다룹니다.

[공식 참고 자료]
- Eric Evans의 DDD 공식 참고서: https://www.domainlanguage.com/ddd/reference/
- Microsoft 전술적 DDD 지침: https://learn.microsoft.com/en-ca/azure/architecture/microservices/model/tactical-ddd
- Python dataclasses 공식 문서: https://docs.python.org/3/library/dataclasses.html

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
4,636
어제
6,862
최대
16,772
전체
770,587
Copyright © 소유하신 도메인. All rights reserved.