모델이 단순한 데이터 구조를 넘어 능동적으로 행동하게 설계하는 방법

Teach your models to act, not just be

작성자
발행일
2026년 02월 07일

핵심 요약

  • 1 모델을 단순한 데이터 저장소로 취급하는 대신 비즈니스 로직의 주체로 설계하여 객체 지향의 핵심인 캡슐화를 실현해야 합니다.
  • 2 객체의 상태를 외부에서 조회하여 판단하기보다 객체에게 직접 동작을 요청하는 'Tell, Don't Ask' 원칙을 철저히 준수해야 합니다.
  • 3 도메인 맥락이 담긴 메서드명을 통해 코드의 의도를 명확히 드러냄으로써 유지보수성과 테스트 용이성을 획기적으로 향상시킬 수 있습니다.

도입

본 글은 Ruby on Rails 개발 환경에서 흔히 발생하는 '빈약한 도메인 모델(Anemic Domain Model)' 현상을 경계하고, 모델이 단순한 데이터베이스 레코드의 투영을 넘어 도메인 로직을 수행하는 능동적인 객체가 되어야 함을 역설합니다. 많은 개발자들이 비즈니스 로직을 컨트롤러나 서비스 객체에 분산시키면서 모델을 수동적인 데이터 컨테이너로 남겨두는 실수를 범하곤 합니다. 이러한 설계 방식은 코드의 응집도를 낮추고 변경에 취약한 구조를 만듭니다. 따라서 본문에서는 객체 지향 설계 원칙을 바탕으로 모델에 생명력을 불어넣고, 진정한 의미의 캡슐화를 달성하기 위한 구체적인 전략과 사고의 전환을 제안하며 기술적 깊이를 더합니다.

1. 빈약한 도메인 모델의 함정과 문제점

많은 Rails 애플리케이션에서 모델은 단순히 ActiveRecord::Base를 상속받아 데이터베이스 테이블과 매핑되는 역할에 그치곤 합니다. 이 경우 비즈니스 로직은 모델 외부의 컨트롤러나 서비스 객체로 흩어지게 됩니다. 이러한 ‘빈약한 도메인 모델’ 구조는 다음과 같은 부작용을 초래합니다.

  • 캡슐화 파괴: 객체의 내부 상태를 외부에서 직접 확인하고 수정하게 되어 데이터 무결성을 보장하기 어렵습니다.
  • 코드 중복: 동일한 로직이 여러 컨트롤러나 서비스에 흩어져 재사용성이 떨어집니다.
  • 도메인 지식의 파편화: 시스템이 ‘무엇을 하는지’ 파악하기 위해 여러 파일을 뒤져야 하므로 가독성이 저하됩니다.

2. ‘Tell, Don’t Ask’ 원칙의 실천

객체 지향 설계의 정수인 ‘Tell, Don’t Ask’ 원칙은 객체에게 상태를 묻지 말고(Ask), 원하는 동작을 수행하도록 명령(Tell)하라는 것입니다.

  • 나쁜 예시: 외부에서 모델의 속성을 조회하여 조건문을 실행하는 방식
    • if user.subscription_status == 'active' && user.balance > 100user.update(level: 'premium')
  • 좋은 예시: 모델 내부에 비즈니스 로직을 캡슐화한 메서드를 호출하는 방식
    • user.promote_to_premium!

이처럼 로직을 모델 내부로 숨기면, 비즈니스 규칙이 변경될 때 오직 모델 내부의 메서드만 수정하면 되므로 변경의 영향 범위를 최소화할 수 있습니다.

3. 의미 있는 메서드명을 통한 도메인 언어 확립

단순히 필드 값을 바꾸는 update 메서드 대신, 비즈니스 맥락이 담긴 명시적인 메서드명을 사용해야 합니다.

  • publish!, archive!, suspend_account!와 같은 메서드는 단순한 상태 값 변경 이상의 의미를 갖습니다.
  • 이러한 메서드들은 유비쿼터스 언어(Ubiquitous Language)를 코드에 투영하여, 개발자와 기획자가 동일한 개념으로 소통할 수 있게 돕습니다.
  • 내부적으로 복잡한 유효성 검사나 콜백 로직이 수반되더라도 외부에 노출되는 인터페이스는 단순하게 유지됩니다.

4. 테스트 용이성과 유지보수성 향상

행위 중심의 모델 설계는 테스트 전략에도 긍정적인 영향을 미칩니다.

  1. 단위 테스트의 집중: 복잡한 통합 테스트나 컨트롤러 테스트 대신, 모델의 행위를 검증하는 가벼운 유닛 테스트에 집중할 수 있습니다.
  2. 의존성 관리: 모델이 스스로의 상태를 책임지므로, 테스트 환경 구축 시 복잡한 모킹(Mocking)이나 스터빙(Stubbing)이 줄어듭니다.
  3. 가독성: 테스트 코드가 비즈니스 요구사항을 설명하는 문서의 역할을 수행하게 됩니다.

5. 서비스 객체와 모델의 적절한 역할 분담

모든 로직을 모델에 넣는 ‘뚱뚱한 모델(Fat Model)’을 경계해야 합니다. 여러 모델 간의 상호작용이 필요하거나 외부 API 연동, 이메일 발송과 같은 부수적인 작업은 서비스 객체가 담당하되, 각 모델은 여전히 자신의 상태 변화에 대한 ‘행위’를 스스로 정의하고 책임져야 합니다. 서비스 객체는 오케스트레이터 역할을 수행하고, 실제 도메인 로직의 핵심은 모델이 보유하는 균형 잡힌 설계가 필요합니다.

결론

결론적으로 모델을 단순한 데이터 구조체로 보지 않고 행위의 주체로 격상시키는 것은 견고하고 확장 가능한 애플리케이션을 구축하는 핵심입니다. 'Tell, Don't Ask' 원칙을 실천하고 모델 내부에 비즈니스 언어를 반영한 메서드를 구축함으로써, 개발자는 더 직관적이고 변화에 유연한 시스템을 설계할 수 있습니다. 이는 단순히 코드의 위치를 옮기는 작업이 아니라, 도메인 지식을 코드에 직접적으로 각인시키는 과정입니다. 이러한 접근법은 장기적으로 기술 부채를 줄이고 팀의 생산성을 높이는 데 기여하며, 객체 지향 프로그래밍의 진정한 가치를 실현하는 길임을 시사하며 글을 맺습니다.

댓글 0

댓글 작성

댓글 삭제 시 비밀번호가 필요합니다.

이미 계정이 있으신가요? 로그인 후 댓글을 작성하세요.

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!