DDD에서는 생성자를 정의할 때 자가 위임(self-delegation)과 자가 캡슐화(self-encapsulation)를 사용할 것을 권장한다. 이 두 가지 개념을 통해 어떻게 생성자를 정의해야 하는지 알아보자.
자가 위임, 자가 캡슐화 - DDD에서 생성자를 정의하는 방법
자가 위임, 자가 캡슐화란 무엇일까?
"자가 캡슐화는 심지어 같은 클래스 내에서부터 모든 데이터로의 액세스가 접근자 메소드를 거쳐가도록 설계하는 방법이다." - Martin Fowler
자가 위임(self-delegation) 자가 캡슐화(self-encapsulation)는 비슷한 맥락에서 사용되는 용어다. 자가 위임은 이름 그대로 객체가 자기 자신에게 작업을 위임하는 것을 의미한다. 그리고 자가 캡슐화는 객체가 자신의 데이터를 직접 접근하지 않고 메서드를 통해 접근하는 것을 의미한다.
두 개념을 종합하면, 객체가 자신의 데이터를 읽거나 변경할 때 자기 자신의 메서드를 통해 작업을 위임하라는 뜻이 된다. 다시 말해서 getter와 setter 메서드를 통해 변수에 접근하라는 것이다. 변수에의 직접 접근을 경계하고 행동적 도메인 모델링을 강조하는 DDD에서 선호하는 방법이다.
DDD에서 getter와 setter를 권장한다는 말이 사실일까?
사실 무분별하게 getter와 setter를 정의하여 사용하는 것은 DDD에서 권장하는 방법은 아니다. 그런데, 이들과 관련된 자가 위임과 자가 캡슐화를 DDD에서 권장한다니, 이게 도대체 무슨 말일까? 모순이 아닌가?
Getter와 setter 사용을 지양하라는 것은 getter와 setter 모두를 public하게 노출하지 말라는 말로 해석할 수 있다. 외부에서 객체의 데이터를 직접 변경할 수 있다는 것은 해당 객체가 무기력한 도메인 모델(anemic domain model)임을 암시하기 때문이다.
따라서, 엄밀히 따지면 DDD에서 권장하는 getter와 setter의 활용은 "private setter를 활용한 생성자 정의"에 국한된다. Setter를 외부에서 접근하지 못하도록 제한하여 자기 자신만 사용할 수 있도록 숨기는 것이다.
자가 위임, 자가 캡슐화를 통한 생성자 구현 방법
from typing import Optional
class Person:
def __init__(self, name: str, age: int) -> None:
# self.name = name
# self.age = age
self._name: Optional[str] = None
self._age: Optional[int] = None
self._set_name(name=name)
self._set_age(age=age)
def _set_name(self, name: str) -> None:
if len(name) > 20: # guard
raise ValueError("Name is too long.")
self._name = name
def _set_age(self, age: int) -> None:
if age < 0: # guard
raise ValueError("Age can't be lower than 0.")
self._age = age
위 python 코드 예제를 통해 감을 잡아보자. 생성자에서 private setter를 통해 멤버 변수에 값을 할당하고 있다. 그리고 setter의 장점을 활용하여 다양한 assertion 로직들을 정의한 것을 볼 수 있다. 코드의 특징을 요약하면 다음과 같다.
- 외부에서 setter를 직접 호출하지 않도록 하여 값 변경 책임을 자신만 갖도록 제한하였다.
- Assertion 코드를 setter 쪽으로 분산시켜서 생성자 코드가 간결해졌다.
- Setter의 assertion 코드를 통해 무결성과 일관성을 유지하기 편해졌다.
결론: private setter를 사용해 생성자를 정의하자!
자가 위임과 자가 캡슐화는 DDD에서 객체의 무결성과 일관성을 유지하는 데 중요한 역할을 수행한다. 생성자를 정의할 때 이를 활용하여 데이터 접근을 제어하고, 객체의 내부 상태를 보호할 수 있다. 따라서, 생성자에서 private setter를 통해 변수에 값을 할당하는 습관을 들이자.
More Posts
Validation 코드는 어디에 작성해야 할까? - The 3 types of validation logics
개발자들은 다양한 validation 코드들을 작성하는데 많은 시간을 소비한다. 이곳저곳에 덕지덕지 붙어있는 validation 코드들을 바라보면, 과연 이 코드들이 여기에 있어도 되는 것인지 의문이 생긴다. 다양한 종류의 validation 코드들을 어디에 작성해야 하는지 정리해보자.
Python으로 kubernetes 환경에 app 배포하기 - Kubernetes deployment with python
Kubectl 명령어를 통해 kubernetes 환경에 다양한 구성요소들을 배포할 수 있지만, python으로 템플릿 코드를 짜두면 개발자들도 편리하게 배포를 자동화할 수 있다. Jinja2와 kubernetes 패키지를 통해 사용자 요청에 따라 kubernetes 환경에 pod들을 띄우는 기능을 만들어보자.
Domain model에서 repository를 직접 사용해도 될까? - 도메인 모델의 영속성 무지
Repository는 인프라적인 요소에 가깝다고 할 수 있다. 그런데, 이 repository를 도메인 모델에서 직접 사용해도 괜찮을까? DDD와 관련된 여러 참고 문서들에서는 이에 대해 통일되지 않은 견해를 보이고 있다. 이 글을 통해 여러 견해들을 한꺼번에 모아서 생각해보자.
Comments