생애주기를 갖는 객체, 엔티티(Entity)

박유민·2024년 1월 2일
0

Domain-Driven Design

목록 보기
2/2
post-thumbnail
post-custom-banner

Entities are usually big things like Customer, Ship, Rental Agreement. Values [value objects] are usually little things like Date, Money, Database Query (...)
Martin Fowler

"개체(Entity)"는 일반적으로 고객, 선박, 임대 계약과 같이 큰 것들을 나타냅니다. 반면에 "값(Value)"은 날짜, 금액, 데이터베이스 조회와 같이 작은 것들을 나타낸다.
마틴 파울러

엔티티란?

도메인 주도 개발에서 말하는 엔티티는 도메인 모델을 구현한 도메인 객체를 의미한다. 이전 글에서 설명했던 값 객체도 도메인 모델을 구현한 도메인 객체이다. 이 두 가지 객체의 차이는 동일성을 통해 식별이 가능한지 아닌지에 있다.

엔티티를 설계할때에는 데이터베이스와 연관지어서 생각하지 않아야 한다. 데이터베이스와의 의존성을 줄이고, 엔티티를 행동 중심으로 설계를 하게 되면 엔티티는 비지니스 논리에만 의존하게 되므로 변경에 대한 유연성이 향상된다.

도서관 대여 시스템으로 설명하는 엔티티

도서관 대여 시스템을 디자인해야 한다고 생각해보자. 책 관련 정보들을 관리해야하고, 책을 대여한 이용객들의 정보도 관리해야 한다.

간단하게 구현해 보기 위해서, 요구사항을 아래와 같이 정리했다.
1. 누구나 도서관의 이용객이 될 수 있다.
2. 책을 대여하기 위해서는 연체료가 없어야 한다.
3. 이미 대여중인 책은 다른 유저가 대여 할 수 없다.

위의 요구사항을 만족하기 위해서는 책, 이용객 이라는 2개의 엔티티 모델이 필요하다.

# Base model for entity
class Entity:
    id: int = field(init=False)

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, type(self)):
            return self.id == other.id
        return False

    def __hash__(self):
        return hash(self.id)
@dataclass
class Book(Entity):
	title: str
    author: str
    is_available: bool = True
    
@dataclass
class User(Entity):
	name: str
    has_fine: bool
    rented_books: Set[Book] = field(default_factory=set)
 
    def rent_book(self, book: Book):
        if not self.has_fine and book.is_available:
            self.rented_books.add(book)
            book.is_available = False
        else:
            raise ValueError("Cannot rent the book.")

    def return_book(self, book: Book):
        if book in self.rented_books:
            self.rented_books.remove(book)
            book.is_available = True
        else:
            raise ValueError("This book is not rented by the user.")

위의 엔티티들을 아래와 같이 사용 할 수 있다.

book1 = Book(title="The Great Gatsby", author="F. Scott Fitzgerald")
book2 = Book(title="To Kill a Mockingbird", author="Harper Lee")

user1 = User(name="Alice")
user2 = User(name="Bob", has_fine=True)

user1.rent_book(book1)
user1.rent_book(book2)

user2.rent_book(book2)  # ValueError: Cannot rent the book.

user1.return_book(book1)
user1.return_book(book2)

엔티티의 성질

가변이다.

  • 값 객체는 불변성을 갖는 객체였지만, 엔티티는 가변성을 갖는다. 위의 도서관시스템에서 유저의 이름을 변경하거나, has_fine등 연체료가 있는지 여부를 변경할 수 있다.

속성이 같아도 구분 할 수 있다.

  • 엔티티는 속성이 같아도 식별자가 존재하기 때문에 구분 할 수 있다.
user1 = User(name="Alice")
user2 = User(name="Alice")

이렇게 속성이 같은 두명의 유저가 있더라도, user1과 user2의 id값은 각각 고유하게 가지고 있는 값이므로 다른 유저 2명으로 판단한다.

user1.id != user2.id  # true

동일성

유저의 이름이 변경되어도 식별자인 id값이 변경되는것이 아니기 때문에 동일한 한명의 유저로 판단 할 수 있다.

마무리

이러한 엔터티의 특성들을 살펴보면, 엔터티가 도메인 주도 개발에서 중요한 역할을 하는 이유를 이해할 수 있습니다. 엔터티는 동일성을 통해 식별되며, 가변적이고 속성이 같아도 구분 가능하며, 동일성은 식별자에 의해 유지됩니다.

도서관 대여 시스템을 통한 엔터티 모델링 예제에서 엔터티는 사용자와 책을 표현하며, 이를 통해 연체료 여부, 대여 상태 등의 비즈니스 규칙을 효과적으로 표현할 수 있었습니다.

엔터티와 값 객체는 각각의 역할과 특성을 가지고 있으며, 이를 잘 조합하여 도메인 모델을 설계함으로써 확장가능하고 유연한 소프트웨어 시스템을 개발할 수 있습니다.

profile
백엔드 엔지니어
post-custom-banner

0개의 댓글