1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】Fat Controller解消のためのServiceクラスと責務について考えた

1
Posted at

はじめに

Serviceクラスを使ったリファクタリングと、その後Serviceクラスを撤廃した経験をもとに、責務について考えたことをまとめます。

前提

  • Rails API
  • Ruby
  • RSpec

なぜServiceクラスを使ったのか

「Fat Controller(ファットコントローラ)」解消のために導入しました。

もともと全てのロジックを含めて、コアとなる部分もコントローラーに書いていました。しかし、開発が進んでコード量が増えるにつれて、Fat Controllerになっていきました。

その後、責務を考え直し、Model側へリファクタリングしました。

Serviceクラスを使ってみて

Serviceクラスという名前だけでは責務が明確にならないため、何でもロジックを集める場所になってしまうことが問題だと感じました。

特に学習を始めた段階では、設計原則や基礎知識が十分に身についていないことから、Serviceクラスの責務が曖昧なまま使い続けてしまう懸念があります。

個人開発においてServiceクラスは使っていけないのか?

コード量が少ない段階であれば、Serviceクラスの責務も把握しやすいため、必要に応じて使ってもよいと思います。

しかし、開発が進んでコードが増えてきた場合、Service クラスの中身の把握が難しくなるかもしれません。

その際、Serviceクラスを使い続けるか、別の場所にリファクタリングするか改めて考える必要があると思います。

Serviceクラス以外の選択肢

いくつか方法があると考えており、選択肢に入れたものについてまとめます。

  • Form Object
  • concerns
  • Model

※ コントローラーへ記述を戻すというのもありですが、振り出しに戻るため挙げません。よりコードを短くできるならいいかもしれませんが。

Form Object

Form Objectは入力値の受け取りやバリデーション、複数のModelにまたがるフォーム処理などを扱うための選択肢だと思いました。

今回扱うような、特定のModelに紐づくビジネスロジックを配置する目的には適さないと判断しました。

Concern

Concernは、共通のロジックや複数の場所で再利用できるもので、モジュールを通じて利用することができます。

今回のビジネスロジックは、複数の場所で共通して利用する処理ではなく、特定のModelに紐づく独自のロジックだったため、適さないと判断しました。

Model

今回の自分のケースでは、ビジネス上のルールが特定のModelに紐づいていたため、ServiceではなくModel側へ移すことにしました。

そもそもビジネスロジックとは?

ビジネスロジックについて、「システムのコア」や「システムの目的となる処理」と捉える考え方を学びました。

そして、データアクセス層(ファイルやデータへのアクセスのための読み書き)でもなく、プレゼンテーション層(アプリケーションとユーザーとのやり取り)でもないものだと理解しました。

私自身も「ビジネスロジック」という言葉を曖昧に使っていたため、今回のリファクタリングをきっかけに認識し直しました。

実際にServiceからModel側へリファクタリングしてみる

ここから、Serviceクラスに切り出していた処理を、app/models 配下のオブジェクトへ移した例を紹介します。

今回は、文字列を受け取って特定の文字列を数える処理を例にします。

文字列にhogeがどれくらい含まれているのかカウントするプログラム

Service クラス

app/services/hoge_count_service.rb
class HogeCountService
  def initialize(chars)
    @chars = chars
  end

  def call
    @chars.scan("hoge").length
  end
end

コントローラー

app/controller/api/v1/hoge_controller.rb
class Api::V1::HogeController < ApplicationController

  - 省略 -

  def create
    chars = params[:chars]
    hoge_count = HogeCountService.new(chars).call

    render json: hoge_count
  end

  - 省略 -
end

Model側にビジネスロジックをまとめる

今回はActiveRecordのModelそのものにメソッドを追加するのではなく、app/models配下に責務を持ったオブジェクトを配置しました。

Model側のテストが通ることを確認したら、不要になったServiceクラスとテストコードを削除します。

Model側にビジネスロジックを作成

app/models/hoge/count.rb
module Hoge
  class Count
    def initialize(chars)
      @chars = chars
    end

    def call
      @chars.scan("hoge").length
    end
  end
end

コントローラー

app/controller/api/v1/hoge_controller.rb
class Api::V1::HogeController < ApplicationController

  - 省略 -

  def create
    chars = params[:chars]
    hoge_count = Hoge::Count.new(chars).call

    render json: hoge_count, status: :created
  end

  - 省略 -
end

おまけ:テストコードの修正(RSpec)

spec/models/hoge/count_spec.rbで動作確認後、spec/services/hoge_count_service_spec.rbを削除します。

Service クラス

spec/services/hoge_count_service_spec.rb
require 'rails_helper'

RSpec.describe HogeCountService do
  describe '#call' do
    let(:chars) { 'hogehogehogehogehoge' }

    it 'returns 5' do
      expect(described_class.new(chars).call).to eq(5)
    end
  end
end

Model

spec/models/hoge/count_spec.rb
require 'rails_helper'

RSpec.describe Hoge::Count do
  describe '#call' do
    let(:chars) { 'hogehogehogehogehoge' }

    it 'returns 5' do
      expect(described_class.new(chars).call).to eq(5)
    end
  end
end

おわりに

Serviceクラスが絶対にダメというわけではありませんが、今回の自分のケースでは、Fat Controllerを解消するためにServiceオブジェクトへ処理を移したものの、結果的に責務が曖昧になっていました。

その過程で、名前空間の使い方やオブジェクト指向について理解を深めるきっかけになったのはよかったと思います。

こうした試行錯誤を通じて、良くないパターンに気づき、時には壊して、改善していく姿勢が大事だと思いました。

個人的には、責務が曖昧なServiceクラスを増やすことは避け、まずはそのロジックがどのオブジェクトの責務なのかを考えて、適切な場所にまとめていきたいと思います。

この記事が少しでも参考になりますと幸いです。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?