Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Rails 디자인 패턴 완벽 가이드: 프리젠터(Presenter)와 서비스 객체(Service Object)

왜 디자인 패턴이 필요할까요?

Rails의 기본 아키텍처인 MVC(Model-View-Controller)는 코드를 담을 수 있는 기본적인 골격을 제공합니다.

하지만 이것만으로는 충분하지 않습니다.

정보를 표현하는 것이 본래 목적인 뷰(View)는 로직으로 가득 차며 점점 비대해집니다.

컨트롤러 역시 본질적인 역할 수행에 필요한 것 이상의 세부 구현까지 떠안게 됩니다.

그렇다면 해결책은 무엇일까요?

이러한 문제를 해결하기 위해 두 가지 디자인 패턴이 널리 사용되고 있습니다.

  • 프리젠터 패턴(Presenter Pattern)
  • 서비스 객체 패턴(Service Object Pattern)

구현 방식에 대해서는 개발자마다 의견이 다르지만, 이 글에서는 실제로 잘 작동하는 검증된 방식을 소개해 드리겠습니다.

지금부터 두 패턴을 자세히 살펴보겠습니다!

Rails에서 프리젠터(Presenter) 활용하기

뷰는 오직 '표현'을 위한 존재입니다. 즉, HTML, CSS, ERB(Embedded Ruby)로만 구성되어야 합니다.

따라서 뷰 안에는 ActiveRecord 쿼리가 있어서는 안 되며,

대부분의 로직도 빼내야 뷰를 최대한 깔끔하고 유지보수하기 쉽게 만들 수 있습니다.

여기서 말하는 "로직"이란 if문이나 삼항 연산자를 사용한 조건 판단을 의미합니다.

그렇다면 어떻게 해야 할까요?

방법은 다음과 같습니다.

뷰의 로직을 처리하는 첫 번째 도구는 바로 헬퍼(Helper)입니다.

여러 뷰에서 공통으로 사용하는 전역 포맷팅 메서드가 있다면 헬퍼가 아주 유용합니다.

예를 들어:

Markdown 렌더링, 특정 형식의 날짜 표시, 텍스트에서 특정 단어 제거 등이 대표적입니다.

실제 코드는 다음과 같습니다:

module DateHelper
  def date_as_month_and_year(date)
    date.strftime("%B %Y")
  end
end

이 코드는 app/helpers 폴더 아래 date_helper.rb 파일로 저장하면 됩니다.

유용한 팁 하나:

헬퍼 메서드에는 항상 인자(argument)로 입력값을 전달하고, 인스턴스 변수에 의존하지 마세요. 이 습관 하나만으로도 나중에 큰 시행착오를 피할 수 있습니다.

헬퍼에도 한계는 있습니다. 특히 뷰의 모든 포맷팅 요구를 헬퍼로 처리하기 시작하면 문제가 됩니다.

메서드가 계속 쌓이면서 체계적인 구조 없이 뒤엉키기 쉽상입니다.

이제 해결책을 소개합니다!

프리젠터 객체로 복잡한 조건문과 포맷팅 메서드 대체하기

다음과 같은 뷰가 있다고 가정해 보겠습니다:

<p>
  Post title: <%= post.title.gsub("forbidden word", "") %>

  <%= link_to "Read post", post, class: "w-75 p-3 text-#{post.draft? ? "orange" : "green"} border-#{post.draft? ? "orange" : "green"}" %>
</p>

꽤 짧은 뷰죠?

하지만 삼항 연산자와 중복된 코드 때문에 상당히 복잡해 보입니다.

좋지 않습니다!

프리젠터 클래스를 만들어 이 문제를 해결해 보겠습니다.

방법은 다음과 같습니다:

class PostPresenter
  def initialize(post)
    @post = post
  end

  def title_without_forbidden_words
    @post.title.gsub("forbidden word", "")
  end

  def css_color
    @post.draft? ? "orange" : "green"
  end
end

이 파일은 app/presenters/post_presenter.rb 경로에 저장하세요. presenters 폴더가 없다면 새로 생성하면 됩니다.

이제 뷰를 다음과 같이 수정할 수 있습니다.

수정된 코드:

<% presenter = PostPresenter.new(post) %>

<p>
  Post title: <%= presenter.title_without_forbidden_words %>

  <%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
</p>

완성입니다!

  • 뷰에서 모든 로직을 제거했습니다
  • 포맷팅과 조건 판단 작업에 의미 있는 이름을 부여해 가독성을 높였습니다
  • 코드 중복 없이 다른 뷰에서도 이 클래스를 자유롭게 재사용할 수 있습니다

이것이 바로 Rails에서 프리젠터를 활용하는 방법입니다 🙂

Rails에서 서비스 객체(Service Object) 활용하기

컨트롤러는 '무엇을 할지'만 지시해야 하며, 트윗 전송, 고객 결제 처리, PDF 생성 같은 작업의 세부 구현 방법을 알아서는 안 됩니다.

이런 작업들은 서비스 객체에 위임해야 합니다.

제가 정의하는 서비스 객체란, 하나의 동작을 완료하기 위한 로직을 캡슐화한 Ruby 모듈입니다.

예시:

module TwitterService
  def self.send_welcome_message(twitter_handle)
    client.update("@#{twitter_handle} welcome to 'Oranges & Apples', we hope you enjoy our juicy fruit!")
  end

  def self.client
    @client ||= Twitter::REST::Client.new do |config|
      config.consumer_key        = "..."
      config.consumer_secret     = "..."
      config.access_token        = "..."
      config.access_token_secret = "..."
    end
  end
end

관례상 이 코드는 app/services 폴더 아래 twitter_service.rb 같은 파일로 저장합니다.

그럼 어떻게 사용할까요?

Rails는 app/ 디렉토리의 모든 코드를 자동으로 로드(autoload)하기 때문에, 별도의 설정 없이 컨트롤러에서 바로 호출할 수 있습니다.

예시:

class UsersController
  def create
    # ...
    TwitterService.send_welcome_message(user.twitter_handle)
  end
end

컨트롤러는 단순히 서비스 객체에게 환영 메시지 전송을 지시할 뿐, API 클라이언트 설정이나 메시지 내용 같은 세부 사항은 전혀 알 필요가 없습니다.

이것이 서비스 객체 패턴의 실제 활용 사례입니다.

마무리

지금까지 프로젝트의 코드 품질을 한 단계 끌어올릴 수 있는 두 가지 유용한 Rails 패턴을 배웠습니다.

  • 프리젠터: 뷰의 조건 로직과 포맷팅을 깔끔하게 분리
  • 서비스 객체: 컨트롤러의 비즈니스 로직을 독립된 모듈로 위임

두 패턴을 현명하게 적용하면 코드의 가독성, 재사용성, 테스트 용이성이 모두 향상됩니다.

이제 직접 프로젝트에 적용해 볼 차례입니다 🙂

읽어주셔서 감사합니다.