Ruby 모델 클래스, 서비스 객체 및 인터랙터 사용
Source: Dev.to
번역할 텍스트가 제공되지 않았습니다. 번역을 원하는 본문을 입력해 주시면 한국어로 번역해 드리겠습니다.
Source: …
왜 기능에 모델 클래스를 사용할까?
Rails에서 모델 클래스(예: app/models에 있는 간단한 PORO)를 사용하면 여러 가지 장점이 있습니다.
개발자들은 로직을 컨트롤러, 헬퍼, 초기화 파일 등에 흩어놓기보다 “모델 클래스”에 넣는 것을 선호합니다.
아래는 핵심 장점입니다:
1. 중앙 집중식, 재사용 가능한 로직
기능 로직이 모델 클래스(FeatureFlag, BetaAccess, Onboarding 등)에 있으면 다음 곳에서 재사용할 수 있습니다:
- 컨트롤러
- 뷰
- 백그라운드 잡
- 서비스
- Pundit 정책
…여러 곳에 로직을 중복해서 넣는 대신.
2. 컨트롤러와 뷰를 깔끔하게 유지
Rails 컨트롤러와 뷰는 얇게 유지해야 합니다.
도메인 로직을 모델에 넣으면 설계가 깔끔해집니다(Fat Model, Skinny Controller).
다음과 같이:
if user.admin? && SomeConfig.beta_enabled?
# …
end
대신:
if BetaAccess.allowed_for?(user)
# …
end
3. 테스트 용이성 향상
모델은 가장 테스트하기 쉬운 대상입니다:
RSpec.describe BetaAccess do
describe ".allowed_for?" do
# …
end
end
컨트롤러를 띄우거나 웹 요청을 시뮬레이션할 필요가 없습니다.
4. 규칙 캡슐화
기능 로직이 복잡해질 경우, 모델 하나에 모두 모아두면 관리가 쉽습니다.
class Onboarding
def completed?(user)
user.profile_filled? && user.verified? && user.tutorial_done?
end
end
새로운 온보딩 규칙을 추가하고 싶을 때는 클래스만 업데이트하면 됩니다.
5. 명확한 네이밍 + 가독성 향상
전용 모델은 의도를 명확히 전달합니다:
if FeatureFlag.enabled?(:new_ui)
# …
end
…은 다음보다 읽기 쉽습니다:
if Rails.configuration.x.new_ui_enabled
# …
end
6. 나중에 영속성 지원이 쉬움
처음에는 간단한 PORO로 시작할 수 있습니다:
class FeatureFlag
FLAGS = { new_ui: false }
end
나중에 인터페이스를 바꾸지 않고 ActiveRecord 모델로 전환할 수 있습니다:
class FeatureFlag e
context.fail!(error: e.message)
end
end
모델 vs. 서비스 vs. 인터랙터
| 개념 | 책임 | 예시 |
|---|---|---|
| 모델 | 도메인 상태와 규칙을 나타내며; 속성과 동작을 캡슐화한다 | FeatureFlag, User, Subscription |
| 서비스 | 개별 작업을 수행하며; 여러 모델을 사용할 수 있다 | PaymentProcessor, EmailSender |
| 인터랙터 | 모델 및 서비스를 사용하여 워크플로/트랜잭션을 조정하고; 성공/실패를 처리한다 | CreateOrder, SendWeeklyReport, EnrollUserInCourse |
핵심 차이점
- 서비스 = 한 가지 일을 수행한다.
- 인터랙터 = 여러 작업을 하나의 비즈니스 운영으로 조정한다.
Source: …
인터랙터가 모델과 서비스 사이에 어떻게 위치하는가
-
Models → 상태와 도메인 로직을 보유
FeatureFlag.enabled?(:new_ui) -
Services → 하나의 모델 또는 도메인과 관련된 작업을 수행
PaymentProcessor.charge(order) -
Interactors → 여러 모델과 서비스를 하나의 트랜잭션 워크플로우로 조정
CreateOrder.call(params: order_params)
비유
- Model = 레고 블록
- Service = 단일 레고 작품 (예: 문이나 바퀴)
- Interactor = 전체 레고 세트 (여러 작품을 결합해 작동하는 시스템을 만듦)
Interactor, Service, Model을 언제 사용해야 할까
| 사용 사례 | 권장 패턴 |
|---|---|
| 상태, 규칙, 계산, 또는 쿼리 | Model |
| 모델에 작용하는 단일 액션 | Service |
| 실패할 수 있고 깔끔한 오케스트레이션이 필요한 다단계 워크플로 | Interactor |
예시 워크플로
# Model
class User; end
class FeatureFlag; end
# Service
class WelcomeEmailSender; end
# Interactor
class OnboardNewUser
include Interactor
def call
user = User.create!(context.params)
WelcomeEmailSender.send(user)
context.success_message = "Welcome #{user.name}!"
rescue => e
context.fail!(error: e.message)
end
end
Source: Originally posted at DevBlog.
요약
| 패턴 | 적합한 경우 | 부적합한 경우 |
|---|---|---|
| Model (PORO or ActiveRecord) | 도메인 개념, 규칙, 상태 | 일회성 작업 |
| Service | 실행 가능한 동작 (“X 수행”) | 도메인 객체를 나타내는 것 |
| Initializer / config | 정적 규칙 | 확장될 수 있거나 의존성이 필요한 규칙 |
| Interactor | 다단계 워크플로/트랜잭션 조정 | 단일 목적 상태 또는 간단한 규칙 |