Tech Racho エンジニアの「?」を「!」に。
  • 開発

対決!Rails wayとHanami way: コントローラ編(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。


hanamirb.orgより

対決!Rails wayとHanami way: コントローラ編(翻訳)

このところ私の関心はHanamiに向かっており、作者のLuca Guidiがこれまで選んできたアーキテクチャ設計や決定が大のお気に入りです。

そこで私は、RailsとHanamiをコンポーネントごとに比較し、記事が長くなりすぎないようそれぞれ別記事を書くことに決めました。

本シリーズの第1回目です。MVCデザインパターンになぞらえて今回のタイトルは「コントローラ」で始めることにします。

それでは、RailsとHanamiそれぞれのフレームワークにおける実際のコントローラについて見ていきましょう。

コントローラの基本

Railsの場合

Railsのコントローラは、(その前にルーターが扱った)リクエストを受け取って処理し、出力をリクエスト元(クライアント)に送信する役割を担うクラスです。

コントローラ内にある複数のpublicインスタンスメソッドは、それぞれ異なるリクエストを処理します。次のコードをご覧ください。

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index; end     # GET /users
  def show; end      # GET /users/1
  def new; end       # GET /users/new
  def create; end    # POST /users
  def edit; end      # GET /users/1/edit
  def update; end    # PATCH /users/1
  def destroy; end   # DELETE /users/1
end

上でお見せしたのはRESTfulなコントローラの例です。これはRailsで何かを行う場合のベストプラクティスのひとつであり、アプリのリソースへのアクセスを提供する場合は常にRESTfulにします。

Hanamiの場合

Hanamiのコントローラはモジュールになっており、複数のアクション(これらはクラスです)を束ねる役割だけを担います。アクションは、(こちらもその前にルーターが扱った)特定のリクエストを処理して出力をクライアントに送信する役目を担います。

# apps/web/controllers/users/index.rb
module Web::Controllers::Users
  class Index
    include Web::Action

    def call(params)
    end
  end
end

# apps/web/controllers/users/show.rb
module Web::Controllers::Users
  class Show
    include Web::Action

    def call(params)
    end
  end
end
...

上の2つのアクションで、何が行われているかはだいたい見当がつくかと思います。

相違点

Railsの場合、必要に応じて1つのコントローラクラスにいくつものpublicインスタンスメソッドを持ちます。

Hanamiの場合、必要に応じて1つのコントローラモジュールにいくつものアクションクラスを持ちます。Hanamiのアクションクラスは、publicインスタンスメソッドである#callを定義するためだけに必要とされています。

どちらがどうよいのか

私の意見としては、Hanamiアーキテクチャの方がクリーン度が高いと思います。というのも、1つのアクションが1つのクラスになっていることで、「1つのクラスの責務はひとつでなければならない」という単一責任の原則を満たせるからです。

一方、Railsのコントローラの問題として非常によく知られているのは、コントローラクラスがだんだん巨大化して読むのもひと目で理解するのもつらくなってしまうという問題です。「コントローラは薄く、モデルは厚くせよ」というRailsのベストプラクティスはご存知かと思いますが、コントローラが肥大化する心配がまったくないに越したことはないとは思いませんか?

Hanamiの場合、アクションの肥大化はかなり簡単に回避できます。1つのクラスが1つのリクエストを扱うことが必須になっていますし、コントローラの複数のアクションでコードが重複したら、Ruby組み込みのソリューションであるモジュールが普通に使えます。繰り返し使われるコードを1つのモジュールに配置して、それを必要とするアクションクラスでincludeすればおしまいです。次の例をご覧ください。

# apps/web/controllers/users/set_user.rb
module Web::Controllers::Users
  module SetUser
    def self.included(action)
      action.class_eval do
        before :set_user
      end
    end

    private

    def set_user
      @user = UserRepository.new.find(params[:id])
      halt 404 if @user.nil?
    end
  end
end
# apps/web/controllers/users/show.rb
require_relative './set_user'

module Web::Controllers::Users
  class Show
    include Web::Action
    include SetUser

    def call(params)
      # ...
    end
  end
end
# apps/web/controllers/users/edit.rb
require_relative './set_user'

module Web::Controllers::Users
  class Edit
    include Web::Action
    include SetUser

    def call(params)
      # ...
    end
  end
end

キリリと引き締まったコードですね。

ビューテンプレートへの変数公開

コントローラは、データをビュー層に公開する役割を担います。HanamiとRailsのやり方は非常に似通っていますが、わずかに異なる点があります。

Railsの場合

Railsでは、コントローラ内で宣言したインスタンス変数はすべてテンプレート(ビュー)からアクセスできるようになっています。

Hanamiの場合

Hanamiの場合、exposeというクラスメソッドを使わないとインスタンス変数を公開できません。次の例をご覧ください。

# apps/web/controllers/users/index.rb
module Web::Controllers::Users
  class Index
    include Web::Action
    expose :users                     # @usersインスタンス変数を公開する

    def call(params)
      @users = UserRepository.new.all
      @another_instance_variable = {} #これはビュー/テンプレートからアクセス不可能
    end
  end
end

まとめ

ここまで読んだ方なら「RailsでやれることをHanamiでやろうとするとコード量が増える」とお思いかもしれませんし、実際そうかもしれません。Railsアーキテクチャの設計は「設定より規約(Convention over Configuration)」に依存する部分が多くなっています。Railsで作業していると魔法のように感じられるのはそのためです。

一方Hanamiの規約は少なく、意図を明確にする方向に開発者を強制します。私見ですが、コントローラのアクションを個別のクラスにするアイデアは実に素晴らしいと思います。1つのリクエストを受け取るときに何が起きるか、そこだけに注意を払えばよいのですから。このアーキテクチャは正しいように思えます。

本シリーズの次回では、Railsのモデル(ActiveRecord)とHanamiのモデルドメイン(エンティティとリポジトリ)が対決します。乞うご期待!

補足: RubyKaigi 2018にもHanami登場

明日からのRubyKaigi 2018の第一日目で、Anton Davydov氏がHanamiアプリのアーキテクチャをテーマに登壇します。


rubykaigi.org/2018/より

追記: ツイートより

関連記事

Hanamiフレームワークに寄せる私の想い(翻訳)

Ruby/Railsのプロ開発者としての5年間を振り返る(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。