서비스 객체는 Rails 애플리케이션 내에서 복잡한 비즈니스 로직을 처리하는 데 매우 유용합니다. 이는 단순히 Plain Ruby 객체로, 하나의 특정 비즈니스 프로세스 또는 액션을 수행하도록 설계됩니다.
서비스 객체의 주요 이점
- 단일 책임 원칙(SRP) 준수: 각 서비스 객체는 하나의 명확한 비즈니스 로직만을 담당하여 코드의 응집도를 높입니다.
- 테스트 용이성: 복잡한 비즈니스 로직을 독립적인 객체로 분리함으로써 단위 테스트를 훨씬 쉽게 작성하고 실행할 수 있습니다.
- 재사용성: 컨트롤러, 백그라운드 작업(Job), 콘솔 등 다양한 애플리케이션 계층에서 동일한 비즈니스 로직을 재사용할 수 있습니다.
- 가독성 및 유지보수성 향상: 모델과 컨트롤러의 코드를 간결하게 유지하고, 비즈니스 로직의 흐름을 명확하게 파악할 수 있게 합니다.
서비스 객체 구현 가이드라인
- 위치: 일반적으로
app/services또는app/operations디렉토리에 저장됩니다. Rails는 기본적으로 이 경로를 자동으로 로드하지 않으므로,config/application.rb에config.autoload_paths << Rails.root.join('app/services')와 같이 추가해야 할 수 있습니다. - 구조:* 단일 공용 메서드: 서비스 객체는
call,perform,execute와 같은 단일 공용 메서드를 가지는 것이 일반적입니다. 이 메서드는 해당 서비스의 핵심 로직을 실행합니다. * 초기화: 필요한 인자(예: 사용자, 파라미터)는 생성자(initialize)를 통해 받거나, 공용 메서드의 인자로 직접 전달할 수 있습니다. * 결과 반환: 작업의 성공 여부, 생성된 객체, 오류 메시지 등을 명확하게 반환해야 합니다.ActiveRecord::Base객체를 반환하거나,Result객체와 같은 패턴을 사용하여 성공/실패 상태와 데이터를 함께 전달할 수 있습니다.```ruby
예시: 사용자 생성 서비스class UserCreatorService def initialize(params) @params = params end def call user = User.new(@params) if user.save Result.success(user) # Result 객체 사용 예시 else Result.failure(user.errors.full_messages) end endend
컨트롤러에서 사용class UsersController < ApplicationController def create result = UserCreatorService.new(user_params).call if result.success? redirect_to result.value, notice: ‘사용자가 성공적으로 생성되었습니다.’ else render :new, alert: result.errors.join(‘, ‘) end endend```### 언제 서비스 객체를 사용해야 하는가?* 여러 모델에 걸쳐 복잡한 데이터 조작이 필요한 경우
- 외부 서비스와 연동하는 비즈니스 로직
- 컨트롤러나 모델에 직접 두기에는 너무 큰 비즈니스 규칙
- 백그라운드 작업, API 엔드포인트 등 여러 곳에서 재사용될 가능성이 있는 로직