1. 빈약한 도메인 모델의 함정과 문제점
많은 Rails 애플리케이션에서 모델은 단순히 ActiveRecord::Base를 상속받아 데이터베이스 테이블과 매핑되는 역할에 그치곤 합니다. 이 경우 비즈니스 로직은 모델 외부의 컨트롤러나 서비스 객체로 흩어지게 됩니다. 이러한 ‘빈약한 도메인 모델’ 구조는 다음과 같은 부작용을 초래합니다.
- 캡슐화 파괴: 객체의 내부 상태를 외부에서 직접 확인하고 수정하게 되어 데이터 무결성을 보장하기 어렵습니다.
- 코드 중복: 동일한 로직이 여러 컨트롤러나 서비스에 흩어져 재사용성이 떨어집니다.
- 도메인 지식의 파편화: 시스템이 ‘무엇을 하는지’ 파악하기 위해 여러 파일을 뒤져야 하므로 가독성이 저하됩니다.
2. ‘Tell, Don’t Ask’ 원칙의 실천
객체 지향 설계의 정수인 ‘Tell, Don’t Ask’ 원칙은 객체에게 상태를 묻지 말고(Ask), 원하는 동작을 수행하도록 명령(Tell)하라는 것입니다.
- 나쁜 예시: 외부에서 모델의 속성을 조회하여 조건문을 실행하는 방식
if user.subscription_status == 'active' && user.balance > 100…user.update(level: 'premium')
- 좋은 예시: 모델 내부에 비즈니스 로직을 캡슐화한 메서드를 호출하는 방식
user.promote_to_premium!
이처럼 로직을 모델 내부로 숨기면, 비즈니스 규칙이 변경될 때 오직 모델 내부의 메서드만 수정하면 되므로 변경의 영향 범위를 최소화할 수 있습니다.
3. 의미 있는 메서드명을 통한 도메인 언어 확립
단순히 필드 값을 바꾸는 update 메서드 대신, 비즈니스 맥락이 담긴 명시적인 메서드명을 사용해야 합니다.
publish!,archive!,suspend_account!와 같은 메서드는 단순한 상태 값 변경 이상의 의미를 갖습니다.- 이러한 메서드들은 유비쿼터스 언어(Ubiquitous Language)를 코드에 투영하여, 개발자와 기획자가 동일한 개념으로 소통할 수 있게 돕습니다.
- 내부적으로 복잡한 유효성 검사나 콜백 로직이 수반되더라도 외부에 노출되는 인터페이스는 단순하게 유지됩니다.
4. 테스트 용이성과 유지보수성 향상
행위 중심의 모델 설계는 테스트 전략에도 긍정적인 영향을 미칩니다.
- 단위 테스트의 집중: 복잡한 통합 테스트나 컨트롤러 테스트 대신, 모델의 행위를 검증하는 가벼운 유닛 테스트에 집중할 수 있습니다.
- 의존성 관리: 모델이 스스로의 상태를 책임지므로, 테스트 환경 구축 시 복잡한 모킹(Mocking)이나 스터빙(Stubbing)이 줄어듭니다.
- 가독성: 테스트 코드가 비즈니스 요구사항을 설명하는 문서의 역할을 수행하게 됩니다.
5. 서비스 객체와 모델의 적절한 역할 분담
모든 로직을 모델에 넣는 ‘뚱뚱한 모델(Fat Model)’을 경계해야 합니다. 여러 모델 간의 상호작용이 필요하거나 외부 API 연동, 이메일 발송과 같은 부수적인 작업은 서비스 객체가 담당하되, 각 모델은 여전히 자신의 상태 변화에 대한 ‘행위’를 스스로 정의하고 책임져야 합니다. 서비스 객체는 오케스트레이터 역할을 수행하고, 실제 도메인 로직의 핵심은 모델이 보유하는 균형 잡힌 설계가 필요합니다.