TotT: 도메인 객체로 변경에 유연한 코드 작성하기
원문: https://testing.googleblog.com/2024/09/write-change-resilient-code-with-domain.html (Translated by Google Gemini)
제품의 요구사항은 자주 바뀔 수 있지만, 그 근본적인 아이디어는 보통 천천히 변합니다. 이는 흥미로운 통찰로 이어집니다. 만약 우리가 제품의 근본적인 아이디어와 일치하는 코드를 작성한다면, 그 코드는 미래의 제품 변경에도 살아남을 가능성이 더 높습니다.
도메인 객체(Domain objects) 는 우리 코드 안에서 제품의 근본적인 아이디어와 일치하는 구성 요소(클래스나 인터페이스 같은)입니다. 제품 요구사항에 따른 원하는 행동(“텍스트를 흰색으로 설정”)에 맞춰 코드를 작성하는 대신, 우리는 기저에 깔린 아이디어(“텍스트 색상 설정”)에 맞춥니다.
예를 들어, 당신이 배고픈 구글러들에게 맛있고 신선한 피자를 파는 gPizza 팀의 일원이라고 상상해 보세요. 폭발적인 수요로 인해, 팀은 배달 서비스를 추가하기로 결정했습니다.
도메인 객체 없이 피자 배달을 구현하는 가장 빠른 길은 단순히 deliverPizza 메서드를 만드는 것입니다.
Bad#
public class DeliveryService {
public void deliverPizza(List<Pizza> pizzas) { ... }
}
이것은 처음에는 잘 작동하겠지만, 만약 gPizza가 다른 음식으로 메뉴를 확장한다면 어떻게 될까요?
새로운 메서드를 추가할 수도 있습니다:
public void deliverWithDrinks(List<Pizza> pizzas, List<Drink> drinks) { ... }
하지만 요구사항 목록이 늘어날수록(스낵, 디저트 등), 당신은 점점 더 많은 메서드를 추가해야 하는 늪에 빠질 것입니다. 이 지속적인 유지보수 부담을 피하기 위해 초기 구현을 어떻게 바꿀 수 있을까요?
요구사항 대신 제품의 아이디어를 모델링하는 도메인 객체를 추가할 수 있습니다.
- 유스케이스(Use case) 는 제품이 비즈니스 요구사항을 충족하도록 돕는 구체적인 행동입니다. (이 경우, “돈을 더 벌기 위해 피자를 배달한다"입니다.)
- 도메인 객체(Domain object) 는 여러 유사한 유스케이스들이 공유하는 공통된 아이디어를 나타냅니다.
적절한 도메인 객체를 식별하기 위해 스스로에게 질문해 보세요:
- 제품이 지원하는 관련 유스케이스는 무엇이며, 미래에 무엇을 지원할 계획인가?
- A: gPizza는 지금 피자를 배달하고 싶고, 나중에는 음료나 스낵 같은 다른 제품도 배달하고 싶어 합니다.
- 이 유스케이스들이 공유하는 공통된 아이디어는 무엇인가?
- A: gPizza는 고객이 주문한 음식을 고객에게 보내고 싶어 합니다.
- 이 공통된 아이디어를 나타내기 위해 사용할 수 있는 도메인 객체는 무엇인가?
- A: 도메인 객체는 음식 주문(food order) 입니다. 우리는 유스케이스들을
FoodOrder클래스 안에 캡슐화할 수 있습니다.
- A: 도메인 객체는 음식 주문(food order) 입니다. 우리는 유스케이스들을
도메인 객체는 유용한 일반화가 될 수 있습니다. 하지만 너무 포괄적인 객체를 선택하는 것은 피하세요. 유지보수성 향상과 코드가 더 복잡하고 모호해지는 것 사이에는 트레이드오프가 있기 때문입니다. 일반적으로 모든 가능한 유스케이스가 아니라, 계획된 유스케이스만 지원하는 것을 목표로 하세요 (YAGNI 원칙 참조).
Good#
// GOOD: 우리가 무엇을 배달하는지 명확합니다.
public void deliver(FoodOrder order) {}
Bad#
// BAD: 가구 배달은 지원하지 마세요. (너무 포괄적임)
public void deliver(DeliveryList items) {}
도메인 객체와 더 심화된 주제인 도메인 주도 설계(Domain-Driven Design)에 대해 더 알고 싶다면 Eric Evans의 책 Domain-Driven Design을 참고하세요.