1. 도메인 모델링

hyuckhoon.ko·2021년 10월 10일
0

1️⃣ 데이터 모델링에서 벗어나기

대부분 개발자는 도메인 모델을 본 적이 없으며 데이터 모델만 봤을 것이다
-Cyrille Martraire, DDD EU 2017

새로운 시스템을 도입할 때, DB모델링에 바로 착수해왔다.
하지만 여기서부터 모든 것이 잘못되는 지점이다.
유저나 비즈니스 관계자들은 DB 스키마에 관심이 없다.
그들은 어떤 행동(액션)과 그 결과에 관심을 갖는다.
따라서 도메인을 분석할 때는 "행동"에 집중해야 한다.


2️⃣ 도메인 모델링이란

⓵ 도메인(domain)

사용자가 프로그램을 사용하는 대상 분야 (⌜객체지향의 사실과 오해⌟)

위 정의보다 "당신의 소프트웨어가 존재하는 이유"라는 간접적인 표현이 더 와닿는다.

⓶ 모델링: 프로세스

⓷ 도메인 모델링

당신의 소프트웨어가 존재하는 이유를 설명한 프로세스



3️⃣ 도메인 언어 탐구

유관부서(비즈니스 전문가들)과 대화를 통해 도메인 모델을 이해해야 한다.

도메인 모델에 사용할 용어와 규칙을 정해야 한다.

얼리버드 서비스의 콘텍스트 다이어그램

얼리버트에 대한 노트
매치(Match)는 단일 경기 하나를 의미한다.
ex) 10월 10일 용산 아이파크몰 (2구장/맨유) 오전 10시 매치

매치는 매달 1일 00시에 공개된다.
매치에는 여러 개의 매치 신청(MatchApply)를 할 수가 있다.
ex) 매치에 11명 지원 or 매치에 5명 지원

매치 시작 시간에 따라 매치 신청 가격을 차등 부여한다.
1) 매치 공개 후, 매치일이 7일 이하 남은 평일 매치의 경우(if)

  • 8시간 이전: 3000원 할인
  • 7시간 이전: 2000원 할인
  • 6시간 이전: 1000원 할인

2) 매치 공개 후, 매치일이 7일 초과 남은 평일 매치의 경우(else)

  • 7일 이전: 7000원 할인
  • 6일 이전: 6000원 할인
  • 5일 이전: 5000원 할인
  • 4일 이전: 4000원 할인
  • 3일 이전: 3000원 할인
class EarlyBird:
    """얼리버드 시스템"""

    def __init__(self, stadium: str):
        self.stadium = stadium 
        self._earlybird_table = set()

    def __eq__(self, other):
        if not isinstance(other, EarlyBird):
            return False
        return other.stadium == self.stadium

    def __hash__(self):
        return hash(self.stadium)

    def __gt__(self, other):
        if self.stadium is None:
            return False
        if other.stadium is None:
            return True
        return self.stadium > other.stadium
    
    def is_addable(self, timepriceline):
        if timepriceline not in self._earlybird_table:
            return True
        else:
            return False

    def add_timepriceline(self, timepriceline):
        if self.is_addable(timepriceline):
            self._earlybird_table.add(timepriceline)

    def get_earlybird_price(self, match_date):
        price = 10000 
        try:
            if self._earlybird_table:
                for line in self._earlybird_table:
                    if match_date == line.time:
                            price = line.price
                            break
            return price
        except Exception as e:
            raise ValueError(e)


4️⃣ 값 객체

from dataclasses import dataclass
from datetime import datetime


@dataclass(frozen=True)
class TimePriceLine:
    """할인 가능 시간 및 가격 정보"""
    time: datetime
    price: int

얼리버드 시스템에는 여러 라인이 원소로 있다.
각 라인은 시간과 가격정보를 갖고 있다.
간단히 YAML 파일로 작성해보면 아래와 같다.

Early_brid: 10
Time_price_line:
- time: datetime.datetime(2021, 10, 10, 10, 0, tzinfo=<UTC>)
  price: 8000

- time: datetime.datetime(2021, 10, 10, 12, 0, tzinfo=<UTC>)
  price: 8000

- time: datetime.datetime(2021, 10, 10, 14, 0, tzinfo=<UTC>)
  price: 7000

@dataclass(frozen=True)는
해당 클래스의 모든 속성 정보들로 해시를 생성한다.
따라서, 객체 생성 후 인스턴스 변수를 저장하는 것이 불가능하다.(불변 객체)

e = Name("용산 2구장 맨체스터 유나이티드")
e.add_instance_variable = 1

>> Traceback (most recent call last):
  File "main.py", line 20, in <module>
    e.add_instance_variable = 1
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'add_instance_variable'

얼리버드를 이루는 여러 라인들은 시간과 가격 정보 자체가 식별자가 된다.
값으로 두 객체의 동등성을 판별한다는 이야기다.
예를 들어, 내 지갑에 있는 1000원과 친구 지갑에 있는 1000원은 같다. 색이 좀 더 누렇게 바랬다고 해서 두 지폐가 다르게 사용되지 않는 것처럼 말이다.



5️⃣ 엔티티

1000원 지폐가 50,000원 지폐가 되면 전혀 다른 객체가 되어 버린다.
하지만 지폐라는 정체성은 유지가 된다.

용산 아이파크몰 2 경기장의 명칭은 맨유 구장이다.
맨체스터 유나이티드가 팀명을 변경하면, 마찬가지로 용산 2경기장 이름도 변경될 것이다. 그럼에도 그 경기장이라는 인식(정체성)은 변하지 않는다.

from dataclasses import dataclass


@dataclass(frozen=True)
class Name:
    name: str


class Stadium:
    def __init__(self, name: Name):
        self.name = name


yongsan_2 = Stadium(Name("용산 2구장 맨유"))        
yongsan_1 = yongsan_2 
yongsan_2.name = Name("용산 2구장 맨체스터 유나이티드")

assert yongsan_1 == yongsan_2


6️⃣ 도메인 서비스 함수

실제 서비스에서 필요한 행동은 운영팀에서 매달 1일 모든 매치들에
얼리버드 시스템을 일괄 적용 및 할당하는 것이다.

def allocate(line: TimePriceLine, earlybirds: List[EarlyBird]) -> str:
    try:
        earlybird = next(
            e for e in sorted(earlybirds) if e.is_addable(line)
        )
        earlybird.add_timepriceline(line)
        return earlybird.stadium
    except StopIteration:
        raise OutOfMatch(f"Out of match for {line.time, line.price}")

0개의 댓글